Hang when disposing winform in mixed C++/C# interop scenario
See original GitHub issue.NET version
7.0
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
No
Issue description
I am trying to debug an issue in code I inherited from someone else, where a form is instantiated using a .NET7 plugin. This plugin ia used to localise other .NET applications, and is able to provide an emulation of a third-party .NET app’s UI, by creating a blank form and populating it with the controls that exist in the third party app. This emulation allows translators to assess the visual impact of translations on particular controls with text, like buttons, labels etc.
Our plugin is invoked using COM Interop by a C++ MFC application, and the handle of the form is used by the native side so the form can be parented by or re-located/re-sized on the screen relative to other MFC UI components. The native code also starts a timer which periodically polls the .NET7 plugin for changes made by a user, such as changes in control selection, edits made to text etc - an event based approach which notifies the native code would probably be better but as I said, this is code I inherited and has worked to date.
This code worked without issue in .NET Framework, but in .NET, we’re seeing a problem when re-drawing our own blank form. We call Form.Close() on it, which eventually calls our override of Dispose, and it’s here that it hangs. It doesn’t seem to happen when the form is first created, but on repopulation of its content instead.
I thought it might be something to do with our timer which executes asycnchronously with respect to user-initiated UI events causing some kind of race condition which corrupts the form in some way, such as dispose being called while a UI event is being handled, but it doesn’t seem to be the case.
Our .NET plugin loads third-party assemblies into their own assembly load contexts, so I made sure that our code and the assembly code was located in the same context, but that made no difference.
I also investigated to see if Dispose was being called multiple times on the same object but it doesn’t seem to be the case. The call stack is quite deep and disappears into Windows DLLs so I can’t see anything other than function names:
```
win32u.dll!NtUserMsgWaitForMultipleObjectsEx() Unknown user32.dll!RealMsgWaitForMultipleObjectsEx() Unknown combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 2156 C++ combase.dll!ModalLoop(CSyncClientCall * pClientCall) Line 166 C++ combase.dll!ClassicSTAThreadDispatchCrossApartmentCall(tagRPCOLEMESSAGE * pMessage, OXIDEntry * pOXIDEntry, CSyncClientCall * pClientCall) Line 319 C++ [Inline Frame] combase.dll!CSyncClientCall::SwitchAptAndDispatchCall(tagRPCOLEMESSAGE * pMessage) Line 5856 C++ combase.dll!CSyncClientCall::SendReceive2(tagRPCOLEMESSAGE * pMessage, unsigned long * pstatus) Line 5459 C++ [Inline Frame] combase.dll!SyncClientCallRetryContext::SendReceiveWithRetry(tagRPCOLEMESSAGE *) Line 1542 C++ [Inline Frame] combase.dll!CSyncClientCall::SendReceiveInRetryContext(SyncClientCallRetryContext ) Line 565 C++ combase.dll!ClassicSTAThreadSendReceive(CSyncClientCall * pClientCall, tagRPCOLEMESSAGE * pMsg, unsigned long * pulStatus) Line 547 C++ combase.dll!CSyncClientCall::SendReceive(tagRPCOLEMESSAGE * pMessage, unsigned long * pulStatus) Line 726 C++ combase.dll!CClientChannel::SendReceive(tagRPCOLEMESSAGE * pMessage, unsigned long * pulStatus) Line 655 C++ combase.dll!NdrExtpProxySendReceive(void * pThis, _MIDL_STUB_MESSAGE * pStubMsg) Line 2002 C++ rpcrt4.dll!NdrpClientCall3() Unknown combase.dll!ObjectStublessClient(void * ParamAddress, __int64 * FloatRegisters, long Method) Line 369 C++ combase.dll!ObjectStubless() Line 176 Unknown combase.dll!CObjectContext::InternalContextCallback(HRESULT()(void *) pfnCallback, void * pParam, const _GUID & riid, int iMethod, IUnknown * pUnk) Line 4328 C++ combase.dll!CGIPTable::GetInterfaceFromGlobal(unsigned long dwCookie, const _GUID & riid, void * * ppv) Line 1694 C++ UIAutomationCore.dll!00007ff804649779() Unknown UIAutomationCore.dll!00007ff80460e763() Unknown UIAutomationCore.dll!00007ff804630b86() Unknown UIAutomationCore.dll!00007ff804630361() Unknown [Managed to Native Transition] System.Windows.Forms.dll!System.Windows.Forms.Control.ReleaseUiaProvider(nint handle) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.WmDestroy(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(nint hWnd, Interop.User32.WM msg, nint wparam, nint lparam) Unknown [Native to Managed Transition] user32.dll!UserCallWinProcCheckWow() Unknown user32.dll!DispatchClientMessage() Unknown user32.dll!__fnDWORD() Unknown ntdll.dll!00007ff83df90ef4() Unknown win32u.dll!NtUserDestroyWindow() Unknown System.Windows.Forms.Primitives.dll!00007fffb2858e60() Unknown [Managed to Native Transition] System.Windows.Forms.Primitives.dll!Interop.User32.DestroyWindow(IHandle hWnd) Unknown System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DestroyHandle() Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.DestroyHandle() Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.Dispose(bool disposing) Unknown System.Windows.Forms.dll!System.Windows.Forms.Form.Dispose(bool disposing) Unknown MyDll.dll!MyForm.Dispose(bool disposing) Line 194 C# System.ComponentModel.Primitives.dll!System.ComponentModel.Component.Dispose() Unknown System.Windows.Forms.dll!System.Windows.Forms.Form.WmClose(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(nint hWnd, Interop.User32.WM msg, nint wparam, nint lparam) Unknown [Native to Managed Transition] user32.dll!UserCallWinProcCheckWow() Unknown user32.dll!DispatchClientMessage() Unknown user32.dll!__fnDWORD() Unknown ntdll.dll!00007ff83df90ef4() Unknown win32u.dll!NtUserMessageCall() Unknown user32.dll!SendMessageWorker(struct tagWND *,unsigned int,unsigned __int64,int64,int) Unknown user32.dll!SendMessageW() Unknown Microsoft.VisualStudio.Debugger.Runtime.Impl.dll!00007fffbc9819f7() Unknown System.Windows.Forms.Primitives.dll!00007fffb285efe6() Unknown [Managed to Native Transition] System.Windows.Forms.Primitives.dll!Interop.User32.SendMessageW(IHandle hWnd, Interop.User32.WM Msg, nint wParam, nint lParam) Unknown System.Windows.Forms.dll!System.Windows.Forms.Form.Close() Unknown MyDll.dll!MyForm.Close() Line 67 C# pDotNetInterface) Line 717 C++ mfc140u.dll!00007fffe28487ef() Unknown mfc140u.dll!00007fffe284608e() Unknown mfc140u.dll!00007fffe2846454() Unknown mfc140u.dll!00007fffe26e95c9() Unknown user32.dll!UserCallWinProcCheckWow() Unknown user32.dll!CallWindowProcW() Unknown Microsoft.VisualStudio.Debugger.Runtime.Impl.dll!00007fffbc981a76() Unknown ToolkitPro2030vc170x64U.dll!CXTPHookManager::HookWndProc(HWND * hWnd, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 449 C++ user32.dll!UserCallWinProcCheckWow() Unknown user32.dll!DispatchMessageWorker() Unknown mfc140u.dll!00007fffe282ea82() Unknown mfc140u.dll!00007fffe282f385() Unknown MyApp::Run() Line 995 C++ mfc140u.dll!00007fffe2862380() Unknown [Inline Frame] Catalyst.exe!invoke_main() Line 118 C++ MyApp.exe!__scrt_common_main_seh() Line 288 C++ kernel32.dll!00007ff83c147614() Unknown ntdll.dll!00007ff83df426a1() Unknown
### Steps to reproduce
I don't have any easy way to repro this as I don't have an easy way to create a sample MFC/.NET interop project.
Issue Analytics
- State:
- Created 9 months ago
- Reactions:1
- Comments:6 (4 by maintainers)
Top GitHub Comments
Thanks for your replies all. I’ve resolved this on my side.
After a week of digging around and wild goose chases, I found out that our app (MDI +. NET via COM interop) was launching a modal file picker dialog from within an open child frame instance but outside of the main app thread. Once I interacted with a visible WinForm afterwards, the whole UI would hang.
When I refactored to open the dialog from inside the child frame’s thread, the hang resolved.
The call stack I posted was produced when I clicked “break all” in the debugger, but it seems it was a red herring. I’m guessing the creation of the dialog was gumming up a message pump of the main frame or a child frame, and had a knock on effect in winforms, or corrupting the state of the open child frame.
Will close this shortly unless someone has something to add.
@ericcdub thanks for an update, glad it was resolved! I think we are safe to close it then.