2

This is the WH_KEYBOARD_LL code which works for me.

#include <Windows.h>
#include <iostream>

using namespace std;

HHOOK _hook;
KBDLLHOOKSTRUCT kbdStruct;
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
    cout << nCode;
    return CallNextHookEx(_hook, nCode, wParam, lParam);
}

void SetHook() {
    if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0))) {
        cout << "SetWindowsHookEx Fail";
    }
}

void ReleaseHook() {
    UnhookWindowsHookEx(_hook);
}

int main() {
    SetHook();

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        // do something here
    }
    return 0;
}

However when I change the SetWindowsHookEx line to if (!(_hook = SetWindowsHookEx(WH_KEYBOARD, HookCallback, NULL, GetCurrentThreadId()))), it does not work.

I have 3 questions.

  1. How do I get WH_KEYBOARD hook to work?

  2. If I replace the comment inside the loop, with a cout, I don't see any console output. So I am confused, does the body of the loop execute at all?

  3. I also read that inside the loop of GetMessage, I should add a call to DispatchMessage. What's the difference between DispatchMessage and CallNextHookEx? I read the docs about both of them, but couldn't understand.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
vicky
  • 23
  • 4
  • Both [C] and [C++] tags are okay, but you would get more windows geeks into the thread if you'd put [winapi] – Ivan Aksamentov - Drop Sep 17 '17 at 05:56
  • IMO, you should *only* tag with the language you're actually using, which in this case is C++. Additionally, in C++ you should use `nullptr` instead of `NULL`. I'd also recommend you to use [the initializer-if statement](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html) (available since MSVC 14.11), to make your `SetHook()` function easier to read. [`using namespace std;` is also a bad practice](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) and you should never use it. – tambre Sep 17 '17 at 07:11
  • PS: "An error may occur if the hMod parameter is NULL and the dwThreadId parameter is zero" – Harry Johnston Sep 17 '17 at 07:26

2 Answers2

1

GetMessage() never returns because without window handle associated with the process there is no associated message queue and no message processing magic happens (unless you post messages manually). This is insane, but I guess it explains the name of the operating system. Quote from GetMessage():

The GetMessage function retrieves messages associated with the window identified by the hWnd parameter or any of its children

You get WH_KEYBOARD_LL to work because it is called regardless GetMessage(). You don't get calls to WH_KEYBOARD hook because it is called only if GetMessage() is functioning.

Quote from Hooks Overview:

WH_KEYBOARD_LL

The WH_KEYBOARD_LL hook enables you to monitor keyboard input events about to be posted in a thread input queue. For more information, see the LowLevelKeyboardProc callback function.

WH_KEYBOARD

The WH_KEYBOARD hook enables an application to monitor message traffic for WM_KEYDOWN and WM_KEYUP messages about to be returned by the GetMessage or PeekMessage function. You can use the WH_KEYBOARD hook to monitor keyboard input posted to a message queue.

Q&A:

How do I get WH_KEYBOARD hook to work?

You need to create a window. Even message box will do. Even hidden.

If I replace the comment inside the loop, with a cout, I don't see any console output. So I am confused, does the body of the loop execute at all?

No. Because GetMessage() blocks forever. Because there are no incoming messages.

I also read that inside the loop of GetMessage, I should add a call to DispatchMessage. What's the difference between DispatchMessage and CallNextHookEx? I read the docs about both of them, but couldn't understand.

These two functionalities are orthogonal.

After you removed the message from the queue with GetMessage() you need to call DispatchMessage() to pass the message to WindowProc associated with the window. Otherwise WindowProc with not receive it.

CallNextHookEx passes message to the next hook in the chain if there are several hooks installed.

Additionally:

(1) You should not pass _hook into CallNextHookEx. The first parameter is ignored anyway. Passing hook handle is just confusing.

(2) You should always implement proper error checking.

(3) Don't forget to unhook the hook.

Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
  • If I understand correctly, you mean to say that for the `WH_KEYBOARD_LL` program, it would work even if I remove the call to `GetMessage()`? Because I tried removing the `GetMessage()` and it stopped getting the messages. – vicky Sep 17 '17 at 06:13
  • @vicky Yes that's what I meant. I cannot tell for sure what's happening without seeing the code, but you cannot "remove call" to `GetMessage()`, because you need to fill the loop with something. If you just run `while(true);` it will consume all the CPU time of this thread and there may be no room for the hook to run. So instead you could try to `Sleep()` the thread or to yield resources any other way to see if hook works. Alternatively, I think it is possible to bind the hook to a different thread. – Ivan Aksamentov - Drop Sep 17 '17 at 06:22
  • 1
    You have to have a message loop in order to receive the hook messages. The callback function is called from GetMessage(). – Harry Johnston Sep 17 '17 at 07:28
  • Incidentally, I don't think a hidden window will solve the OPs problem. I might be mistaken, but AFAIK you can't give a hidden window the keyboard focus. Also, I'm pretty sure the only reason the OP needs a window at all is that they're only hooking their own process, obviously a keyboard hook won't trigger if there are no keyboard events for the process(es) being monitored. – Harry Johnston Sep 17 '17 at 21:19
1

Per the KeyboardProc callback function documentation:

An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function whenever an application calls the GetMessage or PeekMessage function and there is a keyboard message (WM_KEYUP or WM_KEYDOWN) to be processed.

Your WH_KEYBOARD callback does not work because the thread that is hooked is calling GetMessage() but is not receiving any WM_KEYUP or WM_KEYDOWN messages. Those are window messages, but you have no visual window for them to be posted to.

A WH_KEYBOARD_LL hook operates at a lower level and so it does not require the hooked thread to call (Get|Peek)Message() to receive window messages. Per the LowLevelKeyboardProc callback function documentation:

An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function every time a new keyboard input event is about to be posted into a thread input queue.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770