Global keyboard hook with WH_KEYBOARD_LL and keybd_event (windows)
Asked Answered
D

3

15

I am trying to write a simple global keyboard hook program to redirect some keys. For example, when the program is executed, I press 'a' on the keyboard, the program can disable it and simulate a 'b' click. I do not need a graphic ui, just a console is enough (keep it running)

My plan is to use global hook to catch the key input, and then use keybd_event to simulate the keyboard. But I have some problems.

The first problem is that the program can correctly block 'A' but if I hit 'A' on the keyboard once, the printf in the callback function is executed twice, as well as the keybd_event. So if i open a txt file, i click 'A' once, there are two 'B's input. why is that?

The second question is that why the hook using of WH_KEYBOARD_LL can work on other process without a dll? I thought that we had to use a dll to make a global hook until I wrote this example...

#include "stdafx.h"
#include <Windows.h>
#define _WIN32_WINNT 0x050

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    BOOL fEatKeystroke = FALSE;

    if (nCode == HC_ACTION)
    {
        switch (wParam)
        {
        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYUP:
            PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
            if (fEatKeystroke = (p->vkCode == 0x41)) {     //redirect a to b
            printf("Hello a\n");
            keybd_event('B', 0, 0, 0);
            keybd_event('B', 0, KEYEVENTF_KEYUP, 0);
            break;
            }
            break;
        }
    }
    return(fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam));
}

int main()
{
    // Install the low-level keyboard & mouse hooks
    HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, 0, 0);

    // Keep this app running until we're told to stop
    MSG msg;
    while (!GetMessage(&msg, NULL, NULL, NULL)) {    //this while loop keeps the hook
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    UnhookWindowsHookEx(hhkLowLevelKybd);

    return(0);
}

Many thanks!

Detroit answered 9/4, 2014 at 23:37 Comment(3)
Global hooks do not block input, they simply let you preview it.Clockwork
@CodyGray according to msdn.microsoft.com/en-us/library/windows/desktop/… - "...may return a nonzero value to prevent the system from passing the message to the rest of the hook chain or the target window procedure". For me preventing the system from passing the message to the target window procedure looks exactly like blocking.Pinchbeck
For those who are trying to get this to work, but receives ERROR_HOOK_NEEDS_HMOD (1428): according to SetWindowsHookEx doc, "an error may occur if the hMod parameter is NULL and the dwThreadId parameter is zero". Therefore, you must specify hMod, but in this case you can use any legal value, since no DLL gets injected anyway for low-level hooks. You can use, for example, GetModuleHandle("kernel32.dll").Toluene
K
11

Your callback function execute twice because of WM_KEYDOWN and WM_KEYUP. When you down a key of your keyboard, windows calls the callback function with WM_KEYDOWN message and when you release the key, windows calls the callback function with WM_KEYUP message. That's why your callback function execute twice.

You should change your switch statement to this:

switch (wParam)
{
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
    case WM_KEYUP:
    case WM_SYSKEYUP:
        PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
        if (fEatKeystroke = (p->vkCode == 0x41))  //redirect a to b
        {     
            printf("Hello a\n");

            if ( (wParam == WM_KEYDOWN) || (wParam == WM_SYSKEYDOWN) ) // Keydown
            {
                keybd_event('B', 0, 0, 0);
            }
            else if ( (wParam == WM_KEYUP) || (wParam == WM_SYSKEYUP) ) // Keyup
            {
                keybd_event('B', 0, KEYEVENTF_KEYUP, 0);
            }
            break;
        }
        break;
}

About your second question, I think you have already got from @Ivan Danilov answer.

Knighterrantry answered 21/1, 2015 at 17:51 Comment(0)
P
8

First one is easy. You get one for key down and another for key up. :)

As for the why it can work without a DLL - that's because it is a global hook. Unlike thread-specific ones it is executed in your own process, not in the process where keyboard event happened. It is done via message sending to the thread which has installed the hook - that's precisely why you need message loop here. Without it your hook can't be ran as there would be no one to listen for incoming messages.

The DLL is required for thread-specific hooks because they're called in the context of another process. For this to work, your DLL should be injected into that process. It is just not the case here.

Pinchbeck answered 18/6, 2014 at 3:49 Comment(2)
The only weirdness here is that MSDN says "All global hook functions must be in libraries."Toluene
This is all wrong. All global hooks must be in a DLL unless it is a low-level hook. The documentation is lacking on this. The language here is C++ but C# can use low-level hooks because they don't require a DLL.Duroc
C
1
  1. I have run your code but nothing happend? What wrong with me?
  2. Base on msdn that WH_KEYBOARD_LL message is "Global only" It mean more than that.

    The system calls this function .every time a new keyboard input event is about to be posted into a thread input queue. This message is special case. You also need an DLL to make a real global hook for other message.

Casper answered 30/10, 2015 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.