microsoft / microsoft/microsoft-ui-xaml

Memory leaks in MFC App that is using WinUI3 Xaml Island in DLL

Open
#10,046 0 comments 0 reactions 0 assignees View on GitHub
area-Islands area-Lifetime bug team-Core
Dominant language
C++
Stars
8.4k
Forks
942
Avg merge
2d 7h
Merged PRs (30d)
105

Description

### Describe the bug

I have a exe (MFC app) and dll (hosting xaml island code isolated from main app) projects written completly in C++ with help of C++/WinRT and WinAppSDK 1.6. Here is the simplified [repository](https://github.com/PetrMinar/PublicWinUI3Experiment), where the problem can be observed.

When the application exits it reports several memory leaks that originate to the xaml framework. For example:
```
{2840} normal block at 0x0000017C5D4494B0, 32 bytes long.
Data: < [ Z > 20 5B C2 DD FB 7F 00 00 F0 5A C2 DD FB 7F 00 00
{1204} normal block at 0x0000017C596D8060, 80 bytes long.
Data: < U| ` 4]| > A0 C3 E0 55 7C 01 00 00 60 A5 34 5D 7C 01 00 00
{1203} normal block at 0x0000017C5D4C7AB0, 160 bytes long.
Data: 4D 00 69 00 63 00 72 00 6F 00 73 00 6F 00 66 00
{1202} normal block at 0x0000017C59710A00, 16 bytes long.
Data: < mY| > 80 80 6D 59 7C 01 00 00 00 00 00 00 00 00 00 00
{1198} normal block at 0x0000017C596D7880, 80 bytes long.
Data: < U| ` G]| > A0 C3 E0 55 7C 01 00 00 60 EB 47 5D 7C 01 00 00
{1197} normal block at 0x0000017C597037E0, 112 bytes long.
Data: 4D 00 69 00 63 00 72 00 6F 00 73 00 6F 00 66 00
...
```

I was able to identify the last leak {2840} (first in list) - it is a `WeakRefPtr` named `g_previousApplicationWeak` in `FrameworkApplication_Partial.cpp`. When the app exits it calls `FrameworkApplication::GlobalDeinit()` responsible to free it. Comments in the xaml framework code clarifies the situation:
_// Properly releasing the weakptr here requires vccorlib to be loaded, because it's handled
// by vccorlib140[d]!Platform::Details::ControlBlock. But this function is called during DLL
// unload, and vccorlib isn't always around. So for now we just leak the IWeakReference.
// http://task.ms/39377823 XAML can fully unload its own DLL and dependencies after use_
I have found an ugly workaround to prevent this leak (reinitializing `DispatcherQueueController` / `WindowsXamlManager `after framework shutdown). The workaround can be seen in `XamlIslands::Shutdown` in the repository.

As for the other leaks - They are somehow related to loading compiled xaml resources. Callstack of their allocation looks like this:
```
ZXamlslands.dll!std::make_shared() Line 2896
ZXamlslands.dll!winrt::ZXamlIslands::implementation::XamlMetaDataProvider::Provider() Line 54
ZXamlslands.dll!winrt::ZXamlIslands::implementation::XamlMetaDataProvider::GetXamlType(const winrt::hstring & fullName) Line 42
ZXamlslands.dll!winrt::ZXamlIslands::implementation::AppT::GetXamlType(const winrt::hstring & fullName) Line 49
ZXamlslands.dll!winrt::impl::produce::GetXamlTypeByFullName(void * fullName, void * * result) Line 858
Microsoft.ui.xaml.dll!ABI::Microsoft::UI::Xaml::Phone_XamlTypeInfo::XamlLibMetadataProvider::GetXamlTypeByFullName(HSTRING__ * fullName, ABI::Microsoft::UI::Xaml::Markup::IXamlType * * value) Line 126
Microsoft.ui.xaml.dll!DirectUI::MetadataAPI::ImportClassInfoFromMetadataProvider(const xstring_ptr_view & strTypeFullName, const CClassInfo * * ppType) Line 718
Microsoft.ui.xaml.dll!DirectUI::MetadataAPI::TryGetClassInfoByFullName(const xstring_ptr_view & strTypeFullName, bool bSearchCustomTypesOnly, const CClassInfo * * ppType) Line 1228
Microsoft.ui.xaml.dll!DirectUI::MetadataAPI::TryGetClassInfoByName(KnownNamespaceIndex eNamespaceIndex, const xstring_ptr_view & strTypeName, const CClassInfo * * ppType) Line 1270
Microsoft.ui.xaml.dll!XamlManagedTypeInfoProvider::TryGetTypeWithSpecifiedNamespaceId(const KnownNamespaceIndex nNamespaceIndex, const xstring_ptr & spTypeName, XamlTypeToken * ptokType) Line 691
Microsoft.ui.xaml.dll!XamlManagedTypeInfoProvider::ResolveTypeName(const XamlTypeNamespaceToken inNamespaceToken, const xstring_ptr & inTypeName, XamlTypeToken & rXamlType) Line 240
Microsoft.ui.xaml.dll!XamlNativeTypeInfoProvider::ResolveTypeName(const XamlTypeNamespaceToken inNamespaceToken, const xstring_ptr & inTypeName, XamlTypeToken & rXamlType) Line 251
Microsoft.ui.xaml.dll!XamlTypeNamespace::GetXamlType(const xstring_ptr & inTypeName, std::shared_ptr & outType) Line 43
Microsoft.ui.xaml.dll!XamlXmlNamespace::GetXamlTypeImpl(const xstring_ptr & inTypeName, std::shared_ptr & outType) Line 86
Microsoft.ui.xaml.dll!XamlXmlNamespace::GetXamlType(const xstring_ptr & inTypeName, std::shared_ptr & outType) Line 39
Microsoft.ui.xaml.dll!XamlBinaryMetadataReader2::LoadType(unsigned int index) Line 252
Microsoft.ui.xaml.dll!XamlBinaryMetadataReader2::GetType(const unsigned int index, std::shared_ptr & result) Line 600
[Inline Frame] Microsoft.ui.xaml.dll!XamlBinaryMetadataReader2::GetType(const PersistedXamlNode2 &) Line 660
Microsoft.ui.xaml.dll!XamlBinaryFormatSubReader2::ReadXamlType() Line 810
Microsoft.ui.xaml.dll!XamlBinaryFormatSubReader2::ReadCreateTypeBeginInitNode() Line 607
Microsoft.ui.xaml.dll!XamlBinaryFormatSubReader2::TryRead(ObjectWriterNode & currentXamlNode) Line 186
Microsoft.ui.xaml.dll!XamlBinaryFormatSubReader2::TryReadHRESULT(ObjectWriterNode & currentXamlNode, bool * endOfStream) Line 40
Microsoft.ui.xaml.dll!CParser::LoadXamlCore(CCoreServices * pCore, const CParserSettings & parserSettings, const Parser::XamlBuffer & buffer, CDependencyObject * * ppDependencyObject, const xstring_ptr_view & strSourceAssemblyName, const std::array &) Line 269
[Inline Frame] Microsoft.ui.xaml.dll!CParser::LoadXaml(CCoreServices *) Line 34
Microsoft.ui.xaml.dll!CCoreServices::ParseXamlWithExistingFrameworkRoot(const Parser::XamlBuffer & buffer, CDependencyObject * pExistingFrameworkRoot, const xstring_ptr_view & strSourceAssemblyName, const xstring_ptr_view & strXamlResourceUri, const bool expandTemplatesDuringParse, CDependencyObject * * ppDependencyObject) Line 3628
Microsoft.ui.xaml.dll!CApplication::LoadComponent(CCoreServices * pCore, CDependencyObject * pComponent, IPALUri * pUri) Line 493
Microsoft.ui.xaml.dll!CoreImports::Application_LoadComponent(CCoreServices * pCore, CDependencyObject * pComponent, const xstring_ptr & strUri, ComponentResourceLocation resourceLocation) Line 264
Microsoft.ui.xaml.dll!DirectUI::FrameworkApplication::LoadComponent(IInspectable * pComponent, const xstring_ptr & strUri, ABI::Microsoft::UI::Xaml::Controls::Primitives::ComponentResourceLocation resourceLocation) Line 553
Microsoft.ui.xaml.dll!DirectUI::FrameworkApplicationFactory::LoadComponentWithResourceLocationImpl(IInspectable * pComponent, ABI::Windows::Foundation::IUriRuntimeClass * pUri, ABI::Microsoft::UI::Xaml::Controls::Primitives::ComponentResourceLocation resourceLocation) Line 492
Microsoft.ui.xaml.dll!DirectUI::FrameworkApplicationFactory::LoadComponentWithResourceLocation(IInspectable * pComponent, ABI::Windows::Foundation::IUriRuntimeClass * pResourceLocator, ABI::Microsoft::UI::Xaml::Controls::Primitives::ComponentResourceLocation componentResourceLocation) Line 879
[Inline Frame] Microsoft.ui.xaml.dll!DirectUI::FrameworkApplicationFactory::LoadComponentImpl(IInspectable *) Line 475
Microsoft.ui.xaml.dll!DirectUI::FrameworkApplicationFactory::LoadComponent(IInspectable * pComponent, ABI::Windows::Foundation::IUriRuntimeClass * pResourceLocator) Line 861
ZXamlslands.dll!winrt::impl::consume_Microsoft_UI_Xaml_IApplicationStatics::LoadComponent(const winrt::Windows::Foundation::IInspectable & component, const winrt::Windows::Foundation::Uri & resourceLocator) Line 188
ZXamlslands.dll!winrt::Microsoft::UI::Xaml::Application::LoadComponent::__l2::::operator()(const winrt::Microsoft::UI::Xaml::IApplicationStatics & f) Line 12465
ZXamlslands.dll!winrt::impl::factory_cache_entry::call<`winrt::Microsoft::UI::Xaml::Application::LoadComponent'::`2':: &>(winrt::Microsoft::UI::Xaml::Application::LoadComponent::__l2:: & callback) Line 6419
ZXamlslands.dll!winrt::impl::call_factory>(winrt::Microsoft::UI::Xaml::Application::LoadComponent::__l2:: && callback) Line 6441
ZXamlslands.dll!winrt::Microsoft::UI::Xaml::Application::LoadComponent(const winrt::Windows::Foundation::IInspectable & component, const winrt::Windows::Foundation::Uri & resourceLocator) Line 12465
ZXamlslands.dll!winrt::ZXamlIslands::implementation::AppT::InitializeComponent() Line 38
ZXamlslands.dll!winrt::impl::create_and_initialize() Line 7917
ZXamlslands.dll!winrt::make() Line 7998
ZXamlslands.dll!XamlIslands::XxxIslandApp() Line 16
ZXamlslands.dll!XamlIslands::XxxInitializeForCurrentThread::__l2::::operator()() Line 44
ZXamlslands.dll!XamlIslands::XxxInitializeForCurrentThread() Line 35
ZXamlslands.dll!XamlIslands::IslandWndT::Create(std::shared_ptr & Island, HWND__ * Parent, XamlIslands::PageT Page) Line 68
ZXamlslands.dll!XamlIslands::Create(std::shared_ptr & Island, HWND__ * Parent, XamlIslands::PageT Page) Line 171
MFCApp.exe!CMainFrame::OnCreate(tagCREATESTRUCTW * lpCreateStruct) Line 64
mfc140ud.dll!CWnd::OnWndMsg(unsigned int message, unsigned __int64 wParam, __int64 lParam, __int64 * pResult) Line 2409
mfc140ud.dll!CWnd::WindowProc(unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 2100
mfc140ud.dll!AfxCallWndProc(CWnd * pWnd, HWND__ * hWnd, unsigned int nMsg, unsigned __int64 wParam, __int64 lParam) Line 265
mfc140ud.dll!AfxWndProc(HWND__ * hWnd, unsigned int nMsg, unsigned __int64 wParam, __int64 lParam) Line 418
mfc140ud.dll!AfxWndProcBase(HWND__ * hWnd, unsigned int nMsg, unsigned __int64 wParam, __int64 lParam) Line 299
user32.dll!UserCallWinProcCheckWow()
user32.dll!DispatchClientMessage()
user32.dll!__fnINLPCREATESTRUCT()
ntdll.dll!00007ffca9874114()
win32u.dll!00007ffca6e32294()
user32.dll!VerNtUserCreateWindowEx()
user32.dll!CreateWindowInternal()
user32.dll!CreateWindowExW()
mfc140ud.dll!IsolationAwareCreateWindowExW(unsigned long dwExStyle, const wchar_t * lpClassName, const wchar_t * lpWindowName, unsigned long dwStyle, int X, int Y, int nWidth, int nHeight, HWND__ * hWndParent, HMENU__ * hMenu, HINSTANCE__ * hInstance, void * lpParam) Line 449
mfc140ud.dll!CWnd::CreateEx(unsigned long dwExStyle, const wchar_t * lpszClassName, const wchar_t * lpszWindowName, unsigned long dwStyle, int x, int y, int nWidth, int nHeight, HWND__ * hWndParent, HMENU__ * nIDorHMenu, void * lpParam) Line 727
mfc140ud.dll!CFrameWnd::Create(const wchar_t * lpszClassName, const wchar_t * lpszWindowName, unsigned long dwStyle, const tagRECT & rect, CWnd * pParentWnd, const wchar_t * lpszMenuName, unsigned long dwExStyle, CCreateContext * pContext) Line 622
mfc140ud.dll!CFrameWnd::LoadFrame(unsigned int nIDResource, unsigned long dwDefaultStyle, CWnd * pParentWnd, CCreateContext * pContext) Line 755
MFCApp.exe!CMFCWinUI3App::InitInstance() Line 102
mfc140ud.dll!AfxWinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, wchar_t * lpCmdLine, int nCmdShow) Line 37
MFCApp.exe!wWinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, wchar_t * lpCmdLine, int nCmdShow) Line 26
MFCApp.exe!invoke_main() Line 123
MFCApp.exe!__scrt_common_main_seh() Line 288
MFCApp.exe!__scrt_common_main() Line 331
MFCApp.exe!wWinMainCRTStartup(void * __formal) Line 17
kernel32.dll!00007ffca93d257d()
ntdll.dll!00007ffca982af08()
```

### Steps to reproduce the bug

Run the app in provided repository.

### Expected behavior

No leaks after application exit.
Or having a "shutdown function" to do final cleanup before app exit would be enough.
Or mark the allocations as [_IGNORE_BLOCK](https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-debug-heap-details?view=msvc-170) so they are not reported as leak by vc runtime.

### Screenshots

_No response_

### NuGet package version

WinUI 3 - Windows App SDK 1.6.1: 1.6.240923002

### Windows version

Windows 11 (22H2): Build 22621

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the shutdown leaks in the simplified PublicWinUI3Experiment repository with WinAppSDK 1.6, then compare the behavior with and without the workaround in XamlIslands::Shutdown. Inspect FrameworkApplication_Partial.cpp and the supplied allocation call stack, especially the g_previousApplicationWeak path and compiled-XAML loading path. Done means identifying whether the leaks are framework-owned and providing a verified resolution or limitation.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
desktop
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.