将托管控制托管为 CWnd 时应用程序挂起

Posted

技术标签:

【中文标题】将托管控制托管为 CWnd 时应用程序挂起【英文标题】:Application hangs when hosting managed control as CWnd 【发布时间】:2019-02-25 15:22:02 【问题描述】:

我的应用程序具有基于 ATL 的 GUI(CWnd、CDialog、...),它由多个页面 (CDialog) 组成。这些页面之一是空的,但它有一个占位符框架 (CWnd),可随对话框调整大小。一切都构建为 x64。

当页面加载时,它使用 COM 互操作从应用程序的托管 (C#) 端请求控制句柄,并将控件作为从该句柄创建的 CWnd 添加到对话框:

简化的托管实施:

// Class "ManagedControlProvider"
private Control myUserControl;
public long CreateControl()

  myUserControl = /*Create some new inheritant of UserControl */
  myUserControl.Dock = DockStyle.Fill;
  return myUserControl.Handle.ToInt64();

原生端简化:

// Call the managed class. Lifetime of m_pManagedControlProvider
// is ensured elsewhere.
LONGLONG lHandle = m_pManagedControlProvider->CreateControl();

// m_pUserCtrlAsCWnd is CWnd*
m_pUserCtrlAsCWnd = CWnd::FromHandle((HWND)lHandle);
m_pUserCtrlAsCWnd->SetParent(this);

// m_ControlFrame is just a native helper-CWnd the dialog that 
// resizes with it a so gives us the size we want to set for the
// managed control. This code is also call in every resize -event.
RECT winRect;
m_ControlFrame.GetWindowRect(&winRect); 
ScreenToClient(&winRect);

m_pUserCtrlAsCWnd->SetWindowPos(NULL, 
    winRect.left, winRect.top, winRect.right - winRect.left,
    winRect.bottom - winRect.top, 0);

我已经多次这样做了,它通常完全按原样工作。但有时,就像现在一样,我遇到应用程序挂起,没有任何明确的原因。在我目前的控制下,这似乎发生在焦点设置到其他桌面应用程序后大约 5 秒。

我已验证问题不在托管控件的生命周期或 GC 中。此外,它在调试版本中是可重现的,因此不应该归咎于优化。当挂起发生时,我可以附加调试器并看到一些 ATL 循环继续运行,但这是我能够在堆栈中看到的唯一一段代码(imo 这表明消息循环以某种方式陷入无限循环而没有与我的代码)。

现在修复脏东西:我在我的托管控件中添加了一个单独的线程,它在 UI 线程上每秒调用 this.Focus()。显然这是一个荒谬的技巧,但只要我在每次用户打开连击等时暂停聚焦(否则它们每秒都会关闭),它就可以工作。

我做错了什么或者什么可能导致这种有些不可预测的行为?

【问题讨论】:

【参考方案1】:

我不知道为什么或它与任何事情有什么关系,但应用程序挂起不知何故源自 WM_ACTIVATE。所以解决方案是在主 CDialog 覆盖 WINPROC 并阻止该消息的转发。从那时起,一切都在正常工作。

我不会将此标记为答案,因为我不知道为什么此解决方案有效。

【讨论】:

以上是关于将托管控制托管为 CWnd 时应用程序挂起的主要内容,如果未能解决你的问题,请参考以下文章

进程挂起从托管代码中调用 AmsiScanBuffer

垃圾收集时的本机线程行为

托管在 Windows 服务中的 WCF 服务在停止时挂起

错误异常大全:正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起。

错误异常大全:正尝试在 OS 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码,这样做会导致应用程序挂起。

将程序托管给Systemd运行