【发布时间】:2015-05-15 23:50:25
【问题描述】:
昨天我遇到了我见过的最奇怪的问题。
我写了一个模块,应该会收到关于 USB 插头的通知。
为此,我创建了一个虚拟窗口,并使用某些接口的GUID 将其注册到设备更改通知。
调用PeekMessage 时出现奇怪的错误。
此时,某些原因,窗口的 WndProc 回调被调用,只有当偷看的消息是WM_DEVICECHANGE(我们在上面的代码中注册)。
在任何其他消息上,DispatchMessage 都会按预期触发回调。
代码:
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = guid;
not = RegisterDeviceNotification(
hWnd, // events recipient
&NotificationFilter, // type of device
DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
);
为了将此模块与我的其余异步代码合并,使用Reactor 设计模式和Windows Events,并遵循stackoverflow 社区成员的建议,我合并了MsgWaitForMultipleObjects 以监听事件和 windows 消息。
代码:
for (;;)
{
dwRetval = MsgWaitForMultipleObjects(cntEvents, arrEvents, FALSE, INFINITE, QS_ALLINPUT);
switch (dwRetval)
{
case WAIT_FAILED:
// failed. TODO: status
break;
// TODO: handle abandoned.
default:
if (dwRetval == cntEvents)
{
// Message has popped.
BOOL x = PeekMessage(&tMsg, hWnd, 0, 0, PM_REMOVE); <---- WM_DEVICECHANGE triggers the callback
if (x)
{
TranslateMessage(&tMsg);
DispatchMessage(&tMsg);
}
}
else if (dwRetval < cntEvents)
{
// event signaled
}
else
{
// TODO: status. unexpected.
return FALSE; // unexpected failure
}
break;
}
}
我反汇编了代码,并在调用NtUserPeekMessage之前比较了寄存器
成功调用时注册:
RAX = 00000059A604EFB0 RBX = 0000000000000000 RCX = 00000059A604EF18
RDX = 0000000000070C62 RSI = 00000059A604EF18 RDI = 0000000000070C62
R8 = 0000000000000000 R9 = 0000000000000000 R10 = 00007FF71A65D800
R11 = 0000000000000246 R12 = 0000000000000000 R13 = 0000000000000000
R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF954562AA1
RSP = 00000059A604EE70 RBP = 0000000000000000 EFL = 00000200
注册未知回调触发调用:
RAX = 00000059A604EFB0 RBX = 0000000000000000 RCX = 00000059A604EF18
RDX = 0000000000070C62 RSI = 00000059A604EF18 RDI = 0000000000070C62
R8 = 0000000000000000 R9 = 0000000000000000 R10 = 00007FF71A65D800
R11 = 0000000000000246 R12 = 0000000000000000 R13 = 0000000000000000
R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF954562AA1
RSP = 00000059A604EE70 RBP = 0000000000000000 EFL = 00000200
寄存器一模一样!(栈上不传参数,64bit..)
在这两种情况下(奇怪的错误和预期的流程)我进入NtUserPeekMessage,结果WndProc回调仅由内部触发系统调用!
00007FF954562A80 mov r10,rcx
00007FF954562A83 mov eax,1003h
00007FF954562A88 syscall
我在 MSDN 上找不到任何文档或在 Internet 上找到对该现象的解释。
我真的很想得到一些帮助, 提前致谢。
【问题讨论】:
-
WM_DEVICECHANGE 始终发送,从未发布。 SendMessage() 不会任意中断程序的 UI 线程,这会导致可怕的重入问题。它需要等到它收到线程空闲的信号并且直接调用窗口过程不会造成任何麻烦。 Get/PeekMessage() 就是那个信号。
-
@HansPassant 非常感谢你:)