Search code examples
c++windowswinapi

How to use GetMessage with SetWindowsHookEx and have it return early


I am writing a Windows c++ application, and currently running into issue with GetMessage, below is my simple program, which intercepts keyboard inputs to all programs.

LRESULT WINAPI keyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
    printf("Hey!\n");
    return 1;
    //return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main(int argc, char *argv[])
{
    HHOOK hook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)(keyboardProc), NULL, 0);
    MSG message;
    memset(&message, 0, sizeof(MSG));

    GetMessage(&message, NULL, 0, 0);

    UnhookWindowsHookEx(hook);

    return 0;
}

The issue is that GetMessage never returns. I cannot use PeekMessage because to use PeekMessage I need to add a Sleep function in the message loop, which slows down the hook significantly (with Sleep(1), you can notice a lot of lag when scanning barcodes), but without Sleep(1), the loop will consume a ton of CPU.

Since keyboard events does not cause GetMessage to return early, how can I cause GetMessage to return early? (For example perhaps posting a WM_USER / WM_QUIT message from another thread? I've tried different options to no avail and the documentation is quite confusing) I'm new to this so code example to show the following would be much appreciated:

  1. listen to user input in the same / different thread via cin.get()
  2. Once cin.get() returns, the program will cause GetMessage to return early

Solution

  • GetMessage only returns when a message is posted to your application, not when the hook is triggered.

    first, you need to decide when you are going to quit, then post a WM_QUIT (or any message) to your main thread, or the thread that installed the hook.

    you can call PostThreadMessage from any thread and pass the stored main thread id to post a message to your main thread, the following code does that when the user presses x.

    #include <iostream>
    #include <Windows.h>
    
    HHOOK g_hookHandle = nullptr;
    DWORD g_mainThreadId = 0;
    
    LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
        if (nCode == HC_ACTION)
        {
            KBDLLHOOKSTRUCT* pKeyboardData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
            DWORD vkCode = pKeyboardData->vkCode;
    
            if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
            {
                if (vkCode == 'X')  // quit when button is X
                {
                    std::cout << "Exiting the program." << std::endl;
    
                    PostThreadMessage(g_mainThreadId, WM_QUIT, 0 ,0); // post WM_QUIT to end app
                }
            }
        }
    
        return CallNextHookEx(g_hookHandle, nCode, wParam, lParam);
    }
    
    int main()
    {
        g_hookHandle = SetWindowsHookExA(WH_KEYBOARD_LL, KeyboardHookProc, nullptr, 0);
        g_mainThreadId = GetCurrentThreadId();  // to send a message to it later
        MSG message;
        GetMessage(&message, nullptr, 0, 0);
    
        UnhookWindowsHookEx(g_hookHandle);
        return 0;
    }