返回

解决 GetMessage 在 SetWindowsHookEx 使用时返回延迟问题

windows

GetMessage 与 SetWindowsHookEx:实现早期返回

简介

在 Windows C++ 应用程序中,GetMessage 用于获取消息并分发给适当的窗口过程。SetWindowsHookEx 则用于安装低级别键盘钩子,以便拦截所有程序的键盘输入。然而,在使用 SetWindowsHookEx 拦截键盘输入时,GetMessage 可能不会尽早返回,导致消息循环阻塞。本文将探讨这一问题并提供解决方法。

问题分析

当使用 SetWindowsHookEx 安装键盘钩子时,系统会将所有键盘事件重定向到指定的回调函数中。这意味着,当键盘输入发生时,回调函数将被调用,从而阻塞 GetMessage 返回。在消息循环中,这会导致程序无法处理其他消息,从而导致应用程序停止响应。

解决方案

为了让 GetMessage 尽早返回,有两种方法:

  1. 使用 PeekMessagePeekMessageGetMessage 类似,但它不会阻塞。它仅检索一个消息而不会将其从队列中删除。在消息循环中,我们可以使用 PeekMessage 检查是否有键盘钩子事件。如果有,我们再调用 GetMessage 检索并处理该事件。这允许消息循环在没有键盘输入时继续执行,从而避免阻塞。

  2. 使用 PostThreadMessagePostThreadMessage 可以从其他线程向当前线程发送消息。我们可以创建一个单独的线程来监听用户输入,例如通过 cin.get()。当 cin.get() 返回时,新线程可以向主线程发送一个自定义消息,例如 WM_USERWM_QUIT。主线程可以在消息循环中处理此消息,并导致 GetMessage 提前返回。

实现示例

使用 PeekMessage

while (GetMessage(&message, NULL, 0, 0))
{
    if (PeekMessage(&message, NULL, WM_KEYDOWN, WM_KEYUP, PM_REMOVE))
    {
        TranslateMessage(&message);
        DispatchMessage(&message);
        continue;
    }

    // 处理其他消息
}

使用 PostThreadMessage

监听用户输入的线程

DWORD WINAPI InputThreadProc(LPVOID lpParameter)
{
    // 监听用户输入
    std::cin.get();

    // 向主线程发送自定义消息
    PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0);

    return 0;
}

主线程的消息循环

while (GetMessage(&message, NULL, 0, 0))
{
    if (message.message == WM_USER)
    {
        break;
    }

    // 处理其他消息
}

结论

通过使用 PeekMessagePostThreadMessage,我们可以使 GetMessage 在收到键盘输入或用户输入时尽早返回。这允许消息循环继续执行,同时处理来自键盘钩子或其他线程的消息。这样,我们就避免了消息循环阻塞并确保应用程序保持响应。

常见问题解答

  1. 使用 PeekMessage 和 PostThreadMessage 哪种方法更好?

    这取决于具体的情况。PeekMessage 在消息循环中效率较高,但需要显式地轮询键盘输入。PostThreadMessage 允许我们在单独的线程中异步监听用户输入,但会引入线程同步的复杂性。

  2. 这些解决方案是否适用于所有版本的 Windows?

    PeekMessagePostThreadMessage 在所有版本的 Windows 中都可以使用。

  3. 使用这些解决方案会不会降低应用程序的性能?

    这取决于实现方式。如果使用得当,这些解决方案不会对应用程序的性能产生显着影响。

  4. 我可以用这些方法来处理来自其他钩子类型的消息吗?

    是的,这些方法可以用于处理来自其他低级别钩子的消息,例如鼠标钩子或窗口消息钩子。

  5. 为什么在使用键盘钩子时需要尽早返回 GetMessage?

    尽早返回 GetMessage 可以确保应用程序可以响应其他消息,例如窗口消息、计时器消息和用户输入。这可以防止应用程序在处理键盘输入时冻结。