winapisetwindowshookex

SetWindowsHookEx for WH_KEYBOARD_LL handler PostThreadMessage message not received?


I setup a handler to catch keystrokes for a short time while using a private message loop for the window. And while it works, if I try the PostThreadMessage the window never receives the message?

The callback looked like (I tried other messages to, this one sample was with a private message):

LRESULT CALLBACK LLKeyboardHookProc(int ncode, WPARAM wp, LPARAM lp) 
{
  if (ncode ==  HC_ACTION) {
    if (wp==WM_KEYDOWN) {
      KBDLLHOOKSTRUCT *kbs=reinterpret_cast<KBDLLHOOKSTRUCT*>(lp);
      if (kbs->vkCode==VK_ESCAPE) {
        PostThreadMessage (GetCurrentThreadId(), UWM_MYCLOSEMESSAGE, 0, 0);
      }
    }
  }
  return CallNextHookEx(0, ncode, wp, lp);
}

HHOOK kbhook = SetWindowsHookEx(WH_KEYBOARD_LL, LLKeyboardHookProc, GetModuleHandle(NULL), 0);

The message loop for the WS_EX_LAYERED window looks like:

  while (IsWindow(hwnd)) {
    MSG msg;
    while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
      ::TranslateMessage(&msg);
      ::DispatchMessage(&msg);
    }
    // let MFC do its idle processing
    LONG lIdle=0;
    while (AfxGetApp()->OnIdle(lIdle++));

    WaitMessage();
  }

  // clean up
  UnhookWindowsHookEx(kbhook);

I changed it to use a volatile variable for now to close the window but I'd really like to send it a message. Using a global HWND wouldn't be thread-safe without locks so didn't want to go that way.

P.S. I did check the PostThreadMessage result and it was not FALSE (no debug messages when I added them) so it seemed to post it somewhere.


Solution

  • PostThreadMessage() posts a thread message to the queue, not a window message. Your message loop is ignoring thread messages, only dispatching window messages. A window will NEVER see a thread message, so you need to handle such messages directly in your message loop instead, eg:

    MSG msg;
    while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        if (msg.hwnd == NULL && msg.message == UWM_MYCLOSEMESSAGE) { // <-- add this
            // do something...
        }
        else {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }
    

    However, as Hans Passant stated in a comment:

    Never use PostThreadMessage() when you also created a window, something as simple as resizing the window causes the message to get lost.

    Raymond Chen has posted several blog articles on this subject:

    Thread messages are eaten by modal loops

    Watching thread messages disappear

    Rescuing thread messages from modal loops via message filters

    Why do messages posted by PostThreadMessage disappear?

    You could try using a WH_MSGFILTER hook, like Raymond demonstrated. But the best way to solve this is to simply not post a thread message at all, post a window message instead. Have your keyboard hook use PostMessage() (or even SendMessage()) to an HWND that your app owns, and then you can handle the message in that window's message procedure.