When I swap keys using SetWindowsHookEx WH_KEYBOARD_LL, why does my program get into a cycle of too many keyboard input events?

让人想犯罪 __ 提交于 2020-08-22 06:41:31

问题


I am trying to write a program for Windows system that swaps the A and B keys, i.e. when I press the A key, B gets typed, and vice versa.

To do so, I first map the A key to behave like the B key. Here is the code I wrote.

#include <stdio.h>
#include <windows.h>

HHOOK hook;

LRESULT CALLBACK keyboardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *) lParam;
    DWORD newVkCode;
    INPUT inputs[1];
    UINT ret;

    char wParamStr[16];
    char vkStr[16] = "";

    if (wParam == WM_KEYDOWN)
        strcpy(wParamStr, "KEYDOWN");
    else if (wParam == WM_KEYUP)
        strcpy(wParamStr, "KEYUP");
    else if (wParam == WM_SYSKEYDOWN)
        strcpy(wParamStr, "SYSKEYDOWN");
    else if (wParam == WM_SYSKEYUP)
        strcpy(wParamStr, "SYSKEYUP");
    else
        strcpy(wParamStr, "UNKNOWN");

    if (p->vkCode == 10)
        strcpy(vkStr, "<LF>");
    else if (p->vkCode == 13)
        strcpy(vkStr, "<CR>");
    else
        vkStr[0] = p->vkCode;

    printf("%d - %s - %lu (%s) - %d - %lu\n",
           nCode, wParamStr, p->vkCode, vkStr, p->scanCode, p->time);

    inputs[0].type = INPUT_KEYBOARD;
    inputs[0].ki.wScan = 0;
    inputs[0].ki.dwFlags = 0;
    inputs[0].ki.time = 0;
    inputs[0].ki.dwExtraInfo = 0;

    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
        inputs[0].ki.dwFlags = KEYEVENTF_KEYUP;
    }

    if (p->vkCode == 'A') {
        inputs[0].ki.wVk = 'B';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    }
    /*
    else if (p->vkCode == 'B') {
        inputs[0].ki.wVk = 'A';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    }
    */

    return CallNextHookEx(hook, nCode, wParam, lParam);
}

int main(int argc, char **argv)
{
    MSG messages;

    hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHook, NULL, 0);
    if (hook == NULL) {
        printf("Error %d\n", GetLastError());
        return 1;
    }

    printf("Waiting for messages ...\n");
    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }
    return 0;
}

This works as expected. I first compile this code with the following commands.

"%vs80comntools%\vsvars32.bat"
cl /D_WIN32_WINNT=0x0401 /EHsc foo.c /link user32.lib

Now, I run the program. I go to another program, say, Notepad, and press the A key and B gets typed. I see the following output from the above program when this happens.

C:\foo>foo.exe
Waiting for messages ...
0 - KEYUP - 13 (<CR>) - 28 - 874702239
0 - KEYDOWN - 65 (A) - 30 - 874703752
0 - KEYDOWN - 66 (B) - 0 - 874703752
0 - KEYUP - 65 (A) - 30 - 874703877
0 - KEYUP - 66 (B) - 0 - 874703877

Now, I move on to the next step of mapping the B key to behave like the A key. To do this, I simply uncomment the five lines of code that is commented above, compile it and run it again.

This also behaves as expected. When I press the A key, B gets typed, and when I press the B key, A gets typed. However, when I check the output of the program, I see too many events.

Here is the output of the program, when I press the A key exactly once.

C:\foo>foo.exe
Waiting for messages ...
0 - KEYUP - 13 (<CR>) - 28 - 874746621
0 - KEYDOWN - 65 (A) - 30 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYDOWN - 66 (B) - 0 - 874749008
0 - KEYDOWN - 65 (A) - 0 - 874749008
0 - KEYUP - 65 (A) - 30 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101
0 - KEYUP - 66 (B) - 0 - 874749101
0 - KEYUP - 65 (A) - 0 - 874749101

After this, when I press the B exactly once, I see the following output.

0 - KEYDOWN - 66 (B) - 48 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYDOWN - 65 (A) - 0 - 874824590
0 - KEYDOWN - 66 (B) - 0 - 874824590
0 - KEYUP - 66 (B) - 48 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637
0 - KEYUP - 65 (A) - 0 - 874824637
0 - KEYUP - 66 (B) - 0 - 874824637

Why are there so many events here? I am guessing this happens because when I press the A key,

  • The keyboardHook function is called with the keyboard input event for the A key press.
  • It processes the keyboard input event and sends a keyboard input event for the B key.
  • The keyboardHook function is called again for the B key.
  • It processes it and sends an event for the A now.
  • The whole cycle repeats.
  • After 10 such cycles, the Windows system probably interferes and stops the cycle.

The above six point explanation is pure imagination. I have a few questions about this.

  1. Can we explain why so many events are generated using an official documentation from Microsoft? If an official documentation is not available, can we prove the reason behind the generation of so many events by writing another program or performing some experiment?
  2. If the events are generated indeed due to repetition of the same cycle (i.e. consuming A, generating B, consuming B, generating A, repeat), then how does it stop automatically after 10 cycles? Can this be also explained using an official documentation, another program or any experiment?

By the way, I know how to work around this. For the fake key events sent using the SendInput function in the keyboardHook function, the hardware scan code is set to 0 in the following statement.

    inputs[0].ki.wScan = 0;

So I can use this fact to ignore events sent by the keyboardHook function in the keyboardHook function by modifying the key mapping part of the code as follows.

    if (p->vkCode == 'A' && p->scanCode != 0) {
        inputs[0].ki.wVk = 'B';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    }
    else if (p->vkCode == 'B' && p->scanCode != 0) {
        inputs[0].ki.wVk = 'A';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    }

Now when I press the A key, I get the familiar output.

C:\foo>foo.exe
Waiting for messages ...
0 - KEYUP - 13 (<CR>) - 28 - 875183112
0 - KEYDOWN - 65 (A) - 30 - 875186310
0 - KEYDOWN - 66 (B) - 0 - 875186310
0 - KEYUP - 65 (A) - 30 - 875186388
0 - KEYUP - 66 (B) - 0 - 875186388

Is there a better way to solve this problem?


回答1:


Following the suggestion of Raymond Chen in the comments to the question, here is a complete program that swaps A and B in the right way, i.e. by checking the injected flag of the low-level keyboard input event. In other words, I perform the following test to decide whether to consume a low-level keyboard input event and replace it with another.

(p->flags & LLKHF_INJECTED) == 0

Here is the complete program.

#include <stdio.h>
#include <windows.h>

HHOOK hook;

LRESULT CALLBACK keyboardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *) lParam;
    DWORD newVkCode;
    INPUT inputs[1];
    UINT ret;

    char wParamStr[16];
    char vkStr[16] = "";

    if (wParam == WM_KEYDOWN)
        strcpy(wParamStr, "KEYDOWN");
    else if (wParam == WM_KEYUP)
        strcpy(wParamStr, "KEYUP");
    else if (wParam == WM_SYSKEYDOWN)
        strcpy(wParamStr, "SYSKEYDOWN");
    else if (wParam == WM_SYSKEYUP)
        strcpy(wParamStr, "SYSKEYUP");
    else
        strcpy(wParamStr, "UNKNOWN");

    if (p->vkCode == 10)
        strcpy(vkStr, "<LF>");
    else if (p->vkCode == 13)
        strcpy(vkStr, "<CR>");
    else
        vkStr[0] = p->vkCode;

    printf("%d - %s - %lu (%s) - %d - %lu\n",
           nCode, wParamStr, p->vkCode, vkStr, p->scanCode, p->time);

    inputs[0].type = INPUT_KEYBOARD;
    inputs[0].ki.wScan = 0;
    inputs[0].ki.dwFlags = 0;
    inputs[0].ki.time = 0;
    inputs[0].ki.dwExtraInfo = 0;

    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
        inputs[0].ki.dwFlags = KEYEVENTF_KEYUP;
    }

    if (p->vkCode == 'A' && (p->flags & LLKHF_INJECTED) == 0) {
        inputs[0].ki.wVk = 'B';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    } else if (p->vkCode == 'B' && (p->flags & LLKHF_INJECTED) == 0) {
        inputs[0].ki.wVk = 'A';
        ret = SendInput(1, inputs, sizeof (INPUT));
        return 1;
    }

    return CallNextHookEx(hook, nCode, wParam, lParam);
}

int main(int argc, char **argv)
{
    MSG messages;

    hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHook, NULL, 0);
    if (hook == NULL) {
        printf("Error %d\n", GetLastError());
        return 1;
    }

    printf("Waiting for messages ...\n");
    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }
    return 0;
}

This can be compiled as follows.

"%vs80comntools%\vsvars32.bat"
cl /D_WIN32_WINNT=0x0401 foo.c /link user32.lib

Now when I run this program, go to another program, say, Notepad, and press the A key, B gets typed. I see the following output from the above program when this happens.

C:\lab\foo>foo.exe
Waiting for messages ...
0 - KEYUP - 13 (<CR>) - 28 - 588708476
0 - KEYDOWN - 65 (A) - 30 - 588711206
0 - KEYDOWN - 66 (B) - 0 - 588711206
0 - KEYUP - 65 (A) - 30 - 588711284
0 - KEYUP - 66 (B) - 0 - 588711284


来源:https://stackoverflow.com/questions/27303878/when-i-swap-keys-using-setwindowshookex-wh-keyboard-ll-why-does-my-program-get

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!