C 当我使用SetWindowsHookEx WH_KEYBOARD_LL交换键时,为什么我的程序会进入一个键盘输入事件过多的循环?

C 当我使用SetWindowsHookEx WH_KEYBOARD_LL交换键时,为什么我的程序会进入一个键盘输入事件过多的循环?,c,windows,visual-studio,keyboard-hook,C,Windows,Visual Studio,Keyboard Hook,我正在尝试为Windows系统编写一个交换a键和B键的程序,即当我按a键时,B会被键入,反之亦然 为此,我首先将A键映射为与B键类似的行为。这是我写的代码 #include <stdio.h> #include <windows.h> HHOOK hook; LRESULT CALLBACK keyboardHook(int nCode, WPARAM wParam, LPARAM lParam) { KBDLLHOOKSTRUCT *p = (KBDLLHO

我正在尝试为Windows系统编写一个交换a键和B键的程序,即当我按a键时,
B
会被键入,反之亦然

为此,我首先将A键映射为与B键类似的行为。这是我写的代码

#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;
}
现在,我运行程序。我转到另一个程序,比如记事本,然后按A键,
B
被输入。发生这种情况时,我看到上面程序的以下输出

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
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
在这之后,当我恰好按下B键一次时,我看到以下输出

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
为什么这里有这么多活动?我猜这是因为当我按A键时

  • 通过按键的键盘输入事件调用
    keyboardHook
    函数
  • 它处理键盘输入事件并发送B键的键盘输入事件
  • B键再次调用
    keyboardHook
    函数
  • 它处理它并立即为A发送一个事件
  • 整个循环重复
  • 在10次这样的循环之后,Windows系统可能会干扰并停止循环
以上六点解释纯属想象。关于这一点,我有几个问题

  • 我们能解释一下为什么这么多事件是使用微软的官方文档生成的吗?如果没有官方文件,我们能否通过编写另一个程序或进行一些实验来证明产生如此多事件的原因
  • 如果事件确实是由于同一周期的重复而产生的(即消费
    A
    、生成
    B
    、消费
    B
    、生成
    A
    、重复),那么它如何在10个周期后自动停止?这也可以用官方文件、其他程序或任何实验来解释吗
  • 顺便说一下,我知道如何解决这个问题。对于使用
    keyboardHook
    功能中的
    SendInput
    功能发送的假钥匙事件,硬件扫描代码在以下语句中设置为
    0

        inputs[0].ki.wScan = 0;
    
    因此,我可以使用这个事实忽略
    keyboardHook
    函数中
    keyboardHook
    函数发送的事件,方法是修改代码的键映射部分,如下所示

        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;
        }
    
    "%vs80comntools%\vsvars32.bat"
    cl /D_WIN32_WINNT=0x0401 foo.c /link user32.lib
    
    现在,当我按下A键时,我得到了熟悉的输出

    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
    
    C:\foo>foo.exe
    正在等待消息。。。
    0-KEYUP-13()-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
    

    有更好的方法解决这个问题吗?

    根据Raymond Chen在对问题的评论中的建议,这里有一个完整的程序,它以正确的方式交换a和B,即通过检查低级键盘输入事件的注入标志。换句话说,我执行下面的测试来决定是否使用一个低级键盘输入事件并用另一个替换它

    (p->flags & LLKHF_INJECTED) == 0
    
    这是完整的程序

    #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;
    }
    
    现在,当我运行这个程序时,转到另一个程序,比如说记事本,然后按A键,
    B
    被键入。发生这种情况时,我看到上面程序的以下输出

    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
    
    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
    
    C:\lab\foo>foo.exe
    正在等待消息。。。
    0-KEYUP-13()-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
    
    那么,您的问题是为什么循环不会无限期地继续?因为您似乎已经非常清楚地证明了伪输入事件确实传递给了钩子,导致了理论上的无界递归。@IMSoP我有两个问题:(1)我们能解释为什么这么多事件是使用Microsoft的官方文档生成的吗?如果没有官方文件,我们能否通过编写另一个程序或进行一些实验来证明产生如此多事件的原因?(2) 如果事件确实是由于相同周期的重复而产生的(即消耗A、产生B、消耗B、产生A、重复),那么它如何在10个周期后自动停止?这也可以用官方文档、另一个程序或任何实验来解释吗?问题1我想你已经演示了如何使用你的程序在一次迭代中将扫描代码设置为零,然后在下一次迭代中将其读出;这非常确定地证明,每次生成一个伪输入事件时,您创建的事件都会被发送到您的钩子。如果您仍然不确定,您可以修改它,将扫描代码设置为1、2、3、4等。@IMSoP感谢您的评论。我同意我的程序和输出回答了我的第一个问题。程序将“假输入”事件中的硬件扫描代码设置为零,输出显示程序也正在接收这些“假输入”事件。输出每行的第四个字段显示硬件扫描代码,我们可以在那里看到扫描代码为0的事件。我还尝试将硬件扫描代码设置为其他数字,如11、12等,我看到这些数字也出现在输出中。所以我确信这一点。应用程序可能需要正确的扫描代码才能正常运行。相反,检查injected标志以检测递归。