全局键盘挂钩

Posted

技术标签:

【中文标题】全局键盘挂钩【英文标题】:Global keyboard hook 【发布时间】:2013-03-21 12:47:48 【问题描述】:

我需要捕获全局键盘消息,因此我将 SetWindowsHookEx() 与 WH_KEYBOARD_LL 一起使用。但它仅在应用程序处于焦点时有效,并且不会全局触发回调。几乎相同的代码适用于 mouse_LL(具有其他结构等)请帮助!

public const int WH_KEYBOARD_LL = 13;
public const int VK_INSERT = 0x2D;
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
HookProc KeyboardHookProcedure;

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern int SetWindowsHookEx(int idHook, HookProc lpfn,
  IntPtr hInstance, int threadId);

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern bool UnhookWindowsHookEx(int idHook);

[DllImport("user32.dll", CharSet = CharSet.Auto,
   CallingConvention = CallingConvention.StdCall)]
  public static extern int CallNextHookEx(int idHook, int nCode,
  IntPtr wParam, IntPtr lParam);

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

[StructLayout(LayoutKind.Sequential)]
  private struct KBDLLHOOKSTRUCT
  
     public uint vkCode;
     public uint scanCode;
     public uint flags;
     public uint time;
     public IntPtr dxExtraInfo;
  

private void SetHookKeyboard()
  
     if (kHook == 0)
     
        KeyboardHookLL();

        //If the SetWindowsHookEx function fails.
        if (kHook == 0)
        
           MessageBox.Show("SetWindowsHookEx Failed");
           return;
        
        button1.Text = "UnHook Windows Hook";
     
     else
     
        bool ret = UnhookWindowsHookEx(kHook);
        //If the UnhookWindowsHookEx function fails.
        if (ret == false)
        
           MessageBox.Show("UnhookWindowsHookEx Failed");
           return;
        
        kHook = 0;
        this.Text = "Keyboard Hook";
     
  

private void KeyboardHookLL()
  
     KeyboardHookProcedure = new HookProc(MainForm.KeyboardHookProc);
     kHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure,   GetModuleHandle("user32"), 0);
  

public static int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
  
     KBDLLHOOKSTRUCT MyKeyboardHookStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
     if (nCode < 0)
     
        return CallNextHookEx(hHook, nCode, wParam, lParam);
     
     else
     
        Form tempForm = Form.ActiveForm;
        tempForm.Text = MyKeyboardHookStruct.vkCode.ToString();
        if (MyKeyboardHookStruct.vkCode == VK_INSERT)
        
           MainForm.botAlive = false;
           MessageBox.Show(MainForm.botAlive.ToString());
        
        return CallNextHookEx(hHook, nCode, wParam, lParam);
     
  

【问题讨论】:

一般来说使用热键而不是钩子更安全。您需要挂钩而不是热键是否有特殊原因? 是的,我需要捕捉每一个输入,因为它将成为一个“记录器”。 如果这是你的主窗体的代码,那么你不应该在某个地方注册事件处理程序吗? 这是从整体代码中选择的。 KeyboardHookProc 是一个回调方法,所以我不必注册任何事件处理程序。它捕获键盘输入(但仅当调用应用程序处于焦点并且我需要它来捕获全局键盘消息时) 【参考方案1】:

这里归功于 Jon:

int vs IntPtr when you have a handle?

我知道这是一篇旧文章,我有点离题了,但我注意到原始代码中有一个险恶的缺点,它可能会在适当的时候转过来咬你非常非常糟糕(我知道在我的情况下确实如此):

  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

最好是:

  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
   static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);

也应该对其他签名进行类似的更改。 即使 microsoft 本身有资源显示 SetwindowsHookEx 以 int 作为返回类型 (https://support.microsoft.com/en-us/kb/318804),您也不应该轻松地将 'IntPtr' 替换为其他类型。在这种情况下,'int' 仅在 32 位操作系统中等同于 'IntPtr'。在 64 位平台中,'IntPtr' 是 64 位,而 'int' 继续只有 32 位,这会打开一整罐蠕虫。使用 'int' 最糟糕的方面之一是,如果 SetWindowsHookEx 的返回值不适合它,那么您很可能最终会得到一个损坏的 int 句柄(它很少见,但并非不可想象)。

这意味着,如果您的应用程序的生命周期超过了您取消挂钩/处置挂钩的时间点(并不是说取消挂钩调用将在开始时...),那么您最终可能会冻结鼠标和/或键盘完全关闭,直到主机进程被杀死。大多数人会在此时简单地重新启动机器并完全丢弃您的应用程序。所有这些“键盘/鼠标冻结”的事情发生是因为未处置的钩子需要到达您的主机应用程序的消息泵,这显然不可能发生,因为它所属的组件在处置期间已经崩溃 - 哇哦!

TL;DR:请注意您正在使用的 p/invoke 方法的签名,尤其是 IntPtrs。

【讨论】:

【参考方案2】:

问题出在“调试”功能中。

Form tempForm = Form.ActiveForm;

【讨论】:

以上是关于全局键盘挂钩的主要内容,如果未能解决你的问题,请参考以下文章

Windows 全局键盘挂钩 - Delphi

MFC 全局键盘挂钩

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

我无法在提升的 Windows 应用程序中全局挂钩键盘

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

如何在C#中制作全局键盘钩子