问题
为什么pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd);
偶尔会在CWnd :: DestroyWindow()函数中返回NULL,导致ASSERT语句在尝试销毁模式对话框时失败?
此外,在导致问题和尝试销毁窗口之间,为什么PreTranslateMessage()
函数停止被调用?
初始信息
我有一个实现屏幕键盘的ActiveX控件。 此键盘接受来自连接的硬件键盘的触摸/单击事件和击键事件。 使用由创建键盘的模块定义的算法验证对控件的输入。
触摸屏幕上的按钮会生成
之后的输入OnButtonUP()->AddCharacter()->ValidateText()
在物理键盘上键入会生成
之后的输入PreTranslateMessage()->AddCharacter()->ValidateText()
ValidateText()函数触发由创建键盘的模块处理的事件。
我在事件触发周围添加了一个try-catch块来处理事件处理过程中发生的任何错误。
令人困惑的行为
我发现如果使用USB键盘导致错误,当用户尝试关闭键盘时,CWnd :: DestroyWindow()中的调试断言语句将失败。 如果使用触摸屏界面导致相同的错误,则断言语句成功。
我还发现,如果使用USB键盘导致错误,则ActiveX控件的PreTranslateMessage()
函数将停止调用。我通过在函数的第一行设置断点并监视发送到窗口的消息来确认这一点。导致错误后仍然有大量消息(主要是wm_mousemoves),但断点不再导致程序中断。
源代码段摘要
触发事件的代码如下:
try
{
m_control->OnValidate(&text, &isValid);
}
catch (...)
{
LOG_CRITICAL_ERROR(/*Format(*/L"Keyboard::ValidateText: Error occured while validating"/*, badText.c_str()).c_str()*/);
}
OnValidate()函数定义为:
void OnValidate(BSTR* text, LONG* isValid)
{
FireEvent(eventidOnValidate, EVENT_PARAM(VTS_PBSTR VTS_PI4), text, isValid);
}
CWnd :: DestroyWindow()函数是(删除了一些位预处理器位):
BOOL CWnd::DestroyWindow()
{
CWnd* pWnd;
CHandleMap* pMap;
HWND hWndOrig;
BOOL bResult;
if ((m_hWnd == NULL) && (m_pCtrlSite == NULL))
return FALSE;
bResult = FALSE;
pMap = NULL;
pWnd = NULL;
hWndOrig = NULL;
if (m_hWnd != NULL)
{
pMap = afxMapHWND();
ENSURE(pMap != NULL);
pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd); //<- This returns NULL before the Assert fails
hWndOrig = m_hWnd;
}
if ((m_hWnd != NULL) || (m_pCtrlSite != NULL))
{
if (m_pCtrlSite == NULL)
bResult = ::DestroyWindow(m_hWnd);
else
bResult = m_pCtrlSite->DestroyControl();
}
if (hWndOrig != NULL)
{
// Note that 'this' may have been deleted at this point,
// (but only if pWnd != NULL)
if (pWnd != NULL)
{
// Should have been detached by OnNcDestroy
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL);
}
else
{
ASSERT(m_hWnd == hWndOrig); //<-This is the ASSERT that fails!
// Detach after DestroyWindow called just in case
Detach();
}
}
return bResult;
}
从我通过跟踪点获得的,当断言失败时,pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd);
返回NULL。
使用来自跟踪点的信息,键盘控件的DestroyWindow()调用似乎如下:
何时起作用(由触摸输入处理引起的错误)
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL);
被调用并成功。何时中断(处理硬件输入引起的错误)
ASSERT(m_hWnd == hWndOrig);
被调用并失败。