PeekMessage触发WndProc回调

Cod*_*nja 5 c winapi msdn internals

昨天我遇到了我见过的最奇怪的问题.我写了一个模块,应该在USB插头上收到通知.为此,我创建了一个虚拟窗口,并使用某个界面将其注册到设备更改通知GUID.

PeekMessage调用时会发生奇怪的错误.在这一点上,为什么,只有当偷看的消息是WM_DEVICECHANGE(我们在上面的代码中注册)时才调用窗口的WndProc回调.在任何其他消息上,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
);
Run Code Online (Sandbox Code Playgroud)

为了将这个模块与我的其他异步代码,使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我反汇编代码,并在调用之前比较寄存器 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 
Run Code Online (Sandbox Code Playgroud)

注册未知回调触发器调用:

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 
Run Code Online (Sandbox Code Playgroud)

寄存器完全一样!(没有参数在堆栈上传递,64位..)

在这两种情况下(奇怪的错误预期的流量)我进入NtUserPeekMessage,事实证明WndProc回调只是从内部系统调用触发!

00007FF954562A80  mov         r10,rcx  
00007FF954562A83  mov         eax,1003h  
00007FF954562A88  syscall  
Run Code Online (Sandbox Code Playgroud)

我在MSDN上找不到任何文档或在互联网上解释这种现象.

我真的很想得到一些帮助,在此先感谢.

Dav*_*nan 7

这是预期的,并记录在案.PeekMessage是分派发送消息的功能之一.从文档:

调度传入的已发送消息,检查线程消息队列中是否有已发布的消息,并检索消息(如果存在).

然后在同一份文件中:

在此调用期间,系统会使用SendMessage,SendMessageCallback,SendMessageTimeout或SendNotifyMessage函数传递挂起的非排队消息,即发送到调用线程拥有的窗口的消息.

用于说明这一点的文件SendMessage(我强调):

如果指定的窗口是由调用线程创建的,则窗口过程将立即作为子例程调用.如果指定的窗口是由其他线程创建的,则系统会切换到该线程并调用相应的窗口过程.仅当接收线程执行消息检索代码时才处理线程之间发送的消息.

通过消息检索代码,文档意味着像GetMessage和的功能PeekMessage.还有其他几个,我手头没有全面的清单.