确定消息发送给哪个 window (SetWindowsHookEx & WH_KEYBOARD)

Determine which window the message was sent (SetWindowsHookEx & WH_KEYBOARD)

我需要能够确定消息的发送对象 window,但我不知道如何正确地做到这一点。在WH_MOUSE中有一个特殊的结构(MOUSEHOOKSTRUCT)存储window的hwnd,但是在WH_KEYBOARD中从哪里得到hwnd?

LRESULT CALLBACK messageHandler(int nCode, WPARAM wParam, LPARAM lParam)
{
    // ???
}
 
DWORD WINAPI messageDispatcher(LPVOID thread)
{
    hookHandle = SetWindowsHookEx(WH_KEYBOARD, messageHandler, GetModuleHandle(nullptr), *reinterpret_cast<DWORD*>(thread));
 
    if (!hookHandle)
    {
        return GetLastError();
    }
 
    MSG message{};
 
    while (GetMessage(&message, 0, 0, 0) > 0)
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
 
    return 0;
}

理论上,我可以使用 GetForegroundWindow,但在我看来这是一个糟糕的选择,因为 window 可以从其他进程接收键盘消息(如果另一个进程发送SendMessage 到此 window) 而不是当前 window 将正是消息的目标。

我认为您可能正在寻找 WH_JOURNALRECORD.

类型的挂钩

这样,Windows 将响应该钩子拦截的各种事件调用的回调过程是类型 JournalRecordProc, and the lparam parameter passed to this function points to an EVENTMSG 结构,如下所示:

typedef struct tagEVENTMSG {
  UINT  message;
  UINT  paramL;
  UINT  paramH;
  DWORD time;
  HWND  hwnd;
} EVENTMSG;

还有你的hwnd

在生成键盘操作时,OS 还不知道哪个 window 最终会收到消息。这就是 WH_KEYBOARD 挂钩不像 WH_MOUSE 挂钩那样提供目标 HWND 的原因(因为鼠标消息携带 window 相关坐标)。

当键盘消息被路由到目标时,该消息被传递到当前具有输入焦点的window。

根据 About Keyboard Input:

The system posts keyboard messages to the message queue of the foreground thread that created the window with the keyboard focus. The keyboard focus is a temporary property of a window. The system shares the keyboard among all windows on the display by shifting the keyboard focus, at the user's direction, from one window to another. The window that has the keyboard focus receives (from the message queue of the thread that created it) all keyboard messages until the focus changes to a different window.

由于你的hook运行在目标线程的消息队列内部,此时你可以使用GetFocus()获取目标HWND

Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue.

否则,您可以改用 WH_CALLWNDPROC/RET 挂钩,它会在消息实际传递到 window 时调用。但是,您不能使用此挂钩阻止消息(正如您在 中询问的那样)。