【问题标题】:Global keyboard hook with WH_KEYBOARD_LL and keybd_event (windows)带有 WH_KEYBOARD_LL 和 keybd_event 的全局键盘钩子(windows)
【发布时间】:2014-04-09 23:37:17
【问题描述】:

我正在尝试编写一个简单的全局键盘挂钩程序来重定向一些键。例如,当程序执行时,我按下键盘上的'a',程序可以禁用它并模拟'b'点击。我不需要图形用户界面,只需一个控制台就足够了(保持运行)

我的计划是使用全局钩子捕捉按键输入,然后使用keybd_event模拟键盘。但是我有一些问题。

第一个问题是程序可以正确地阻止'A',但是如果我在键盘上按一次'A',回调函数中的printf会执行两次,以及keybd_event。因此,如果我打开一个 txt 文件,我单击“A”一次,有两个“B”输入。这是为什么呢?

第二个问题是为什么使用WH_KEYBOARD_LL的钩子可以在没有dll的情况下在其他进程上工作?在我写这个例子之前,我认为我们必须使用一个 dll 来制作一个全局钩子......

#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);
}

非常感谢!

【问题讨论】:

  • 全局挂钩不会阻止输入,它们只是让您预览它。
  • @CodyGray 根据msdn.microsoft.com/en-us/library/windows/desktop/… - “...可能返回一个非零值以防止系统将消息传递给钩子链的其余部分或目标窗口过程”。对我来说,阻止系统将消息传递给目标窗口过程看起来就像阻塞一样。
  • 对于那些试图让它工作但收到 ERROR_HOOK_NEEDS_HMOD (1428) 的人:根据SetWindowsHookEx doc,“如果 hMod 参数为 NULL 并且 dwThreadId 参数为零,则可能会发生错误” .因此,您必须指定hMod,但在这种情况下,您可以使用任何合法值,因为无论如何都不会为低级挂钩注入 DLL。例如,您可以使用GetModuleHandle("kernel32.dll")

标签: c++ hook keyboard-events keyboard-hook


【解决方案1】:

由于WM_KEYDOWNWM_KEYUP,您的回调函数执行了两次。 当您按下键盘的某个键时,windows 会使用WM_KEYDOWN 消息调用回调函数,当您释放该键时,windows 会使用WM_KEYUP 消息调用回调函数。这就是你的回调函数执行两次的原因。

你应该把你的 switch 语句改成这样:

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;
}

关于你的第二个问题,我想你已经从@Ivan Danilov 那里得到了答案。

【讨论】:

    【解决方案2】:

    第一个很简单。你得到一个用于按键,另一个用于按键。 :)

    至于为什么它可以在没有 DLL 的情况下工作 - 那是因为它是一个全局挂钩。与线程特定的不同,它在您自己的进程中执行,而不是在发生键盘事件的进程中。它是通过向安装了钩子的线程发送消息来完成的——这正是你需要在这里进行消息循环的原因。没有它,你的钩子就无法运行,因为没有人可以监听传入的消息。

    线程特定的钩子需要 DLL,因为它们是在另一个进程的上下文中调用的。为此,应将您的 DLL 注入该进程。只是这里不是这样。

    【讨论】:

    • 这里唯一奇怪的是 MSDN 说“所有全局挂钩函数都必须在库中。”
    • 这都是错误的。所有全局挂钩都必须在 DLL 中,除非它是低级挂钩。缺少这方面的文档。这里的语言是 C++,但 C# 可以使用低级挂钩,因为它们不需要 DLL。
    【解决方案3】:
    1. 我已经运行了您的代码,但没有任何反应?我怎么了?
    2. 基于msdn WH_KEYBOARD_LL 消息是“仅限全球”这意味着更多。

      The system calls this function .every time a new keyboard input event is about to be posted into a thread input queue. 此消息是特殊情况。您还需要一个 DLL 来为其他消息创建一个真正的全局挂钩。

    【讨论】:

      猜你喜欢
      • 2010-12-10
      • 1970-01-01
      • 1970-01-01
      • 2022-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-26
      相关资源
      最近更新 更多