继承了一个基于 MFC 的应用程序,该应用程序同时托管有窗口和无窗口 ActiveX 控件。在这个应用程序中,用户可以设计他/她能够动态创建和删除这些 AX 控件的 SCADA 页面。控件大多是简单的小部件,如按钮、读数、线条、圆圈等。
自从升级到较新版本的 Visual Studio (2017) 后,程序现在在删除 AX 控件时崩溃。原来是VC6。
更具体地说,大多数崩溃发生在您尝试删除无窗口 AX 控件时。如果这些 AX 控件托管在另一个主机应用程序(例如 Internet Explorer)中,则不会发生崩溃。
程序退出时也可能会崩溃,因为这时页面上的所有AX控件也会被一一删除。
更具体地说,创建对象的顺序很重要。如果您删除了一个无窗口对象并且下一个焦点对象将是一个窗口对象,那么您是安全的:没有崩溃。我认为“在此处路由事件”(焦点)框架代码然后选择窗口对象作为窗口消息的有效目标,从那时起一切都会好起来的。但是,如果新对象也是无窗口的,那么您就不走运了。
请注意,我们使用该WS_DISABLED
标志创建所有控件,否则按钮小部件将在用户调整大小并在小部件周围移动时开始对鼠标单击做出反应。
当 GPF 在删除无窗口 AX 控件后发生时,调试器会中断并停止在 OLE 容器文件occcont.cpp
中,函数 COleControlContainer::HandleWindowlessMessage
。此时它想调用 m_pSiteFocus->m_pWindowlessObject->OnWindowMessage
.
这是在承载所有 AX 控件的“容器”窗口的上下文中。一个窗口消息即将被路由到当前聚焦的 AX 控件。
但是这里选择了不正确的目的地,因为m_pWindowlessObject
是0xdddddddd
(这意味着释放了内存)。
在我看来,不应再选择该站点焦点对象,因为它的所有内容已与删除的 AX 控件一起被破坏!
有趣的是,控件本身的删除操作不会导致任何(直接)问题。间接“删除后”窗口消息现在希望传递到无效的 OLE 控件容器。并且即使在程序关闭时,程序仍可能由于较早的删除 AX 控制操作而崩溃。
顺便说一句:AX 控件是使用 a 创建的,CWnd::CreateControl
并通过调用再次删除CWnd::DestroyWindow()
.
作为一种解决方法,就在销毁窗口调用之后,我将有问题的指针强制为空指针:
COleControlContainer * pContainer = m_wndContainer.GetControlContainer();
const COleControlContainer * pFreedMemory(reinterpret_cast<COleControlContainer*>(0xdddddddd));
if (pContainer != nullptr && pContainer != pFreedMemory) {
// After we delete the control, the library wants to focus another control.
// But if there is no more control or the next control is also windowless/disabled
// it will bump into a stale pointer (0xdddddddd). The code does check for a null pointer,
// so we force a null pointer.
pContainer->m_pSiteFocus = 0;
}
现在程序不再崩溃了,但这很难看!
所以问题是:我做错了什么,我是否面临框架中的错误,甚至可能是无窗口 AX 控件中的错误?
更新#1:
在 MFC 中找到此代码片段occsite.cpp
BOOL COleControlSite::DestroyControl()
...
//VBBUG: VB controls will crash if IOleObject::Close is called on them
// when they have focus (and unfortunately, deactivating them does not
// always move the focus). To work around this problem, we always hide
// the control before closing it.
ShowWindow(SW_HIDE);
...
显然,这是针对类似(如果不相同)问题的解决方法。
更新#2:
我发现对于 AX 控件,它们的析构函数甚至没有被调用。然后确实发现了一个没有正确处理的引用计数接口指针。正如iinspectable在评论中预测的那样!