【问题标题】:How to make a keyboard hook global across processes如何使键盘钩子跨进程全局化
【发布时间】:2010-10-02 01:17:21
【问题描述】:

我正在尝试创建一个实用的按键应用程序,这样我就可以执行诸如终止预编程进程或启动某些操作之类的操作。我想我应该在任何应用程序中按住 cmd,然后输入一个 4 位命令键,这样我就可以在编程、调试观看视频等时快速启动或杀死任何东西。

我想出了如何获得键盘回调,但是无论出于何种原因,一旦我点击另一个应用程序,我的击键工具就不再接收到任何键。即使我点击回到我的控制台窗口或 msvc,我也不会收到任何输入。这是除非它是全局的,那么如何将钩子设置为全局的?

我的代码是

int main()
{
    hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, GetModuleHandle(0), 0);
    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    UnhookWindowsHookEx(hook);
}

【问题讨论】:

  • 您最好使用RegisterHotkey 的常规热键。 WH_KEYBOARD 挂钩需要单独的 DLL,32 位代码不能挂钩到 64,反之亦然。 WH_KEYBOARD_LL 钩子没有这个问题,但是有性能问题。

标签: winapi api keyboard


【解决方案1】:

重新阅读 Win32 指南中的钩子介绍。 (一个好的起点是here。)

具体来说,如果你想在其他进程中挂钩事件,你需要将你的回调放在一个DLL中,即injected通过Win32到其他进程中。从您提供的代码中,我无法判断 KeyboardProc 是在 DLL 中还是在主程序中。考虑到您要通过的 HINSTANCE,它看起来不像。

【讨论】:

    【解决方案2】:

    当我编写“画中画”之类的应用程序时,我不得不处理全局挂钩。这篇文章和示例代码极大地帮助了我:

    http://www.codeproject.com/KB/system/WilsonSystemGlobalHooks.aspx

    【讨论】:

      【解决方案3】:

      为了让所有进程都可以访问您的键盘挂钩,必须将它放在一个 DLL 中,然后将其加载到每个进程的地址空间中。需要记住的重要一点是,由于 DLL 的每个实例都在单独的进程中加载​​,因此每个实例都将拥有自己的 DLL 中任何全局变量的副本。如果需要在这些实例之间共享数据,最简单的方法是在 DLL 中创建一个共享数据段。以下代码来自我编写的一个 RSI 监控程序。

      //
      // some data will be shared across all
      // instances of the DLL
      //
      #pragma comment(linker, "/SECTION:.SHARED,RWS")
      #pragma data_seg(".SHARED")
      int iKeyCount = 0;
      HHOOK hKeyboardHook = 0;
      HHOOK hMouseHook = 0;
      #pragma data_seg()
      
      //
      // instance specific data
      //
      HMODULE hInstance = 0;
      
      //
      // DLL load/unload entry point
      //
      BOOL APIENTRY DllMain(HANDLE hModule, 
                            DWORD  dwReason, 
                            LPVOID lpReserved)
      {
         switch (dwReason)
         {
         case DLL_PROCESS_ATTACH :
            hInstance = (HINSTANCE) hModule;
            break;
      
         case DLL_THREAD_ATTACH :
            break;
      
         case DLL_THREAD_DETACH :
            break;
      
         case DLL_PROCESS_DETACH :
            break;
         }
         return TRUE;
      }
      
      //
      // keyboard hook
      //
      LRESULT CALLBACK KeyboardProc(int code,       // hook code
                                    WPARAM wParam,  // virtual-key code
                                    LPARAM lParam)  // keystroke-message information
      {
         if ((lParam & 0x80000000) != 0)
         {
            ++iKeyCount;
         }
         return CallNextHookEx(hKeyboardHook, code, wParam, lParam);
      }
      
      //
      // mouse hook
      //
      LRESULT CALLBACK MouseProc(int code,       // hook code
                                 WPARAM wParam,  // message identifier
                                 LPARAM lParam)  // mouse coordinates
      {
         switch (wParam)
         {
         case WM_LBUTTONDOWN :
         case WM_MBUTTONDOWN :
         case WM_RBUTTONDOWN :
         case WM_LBUTTONDBLCLK :
         case WM_MBUTTONDBLCLK :
         case WM_RBUTTONDBLCLK :
            ++iKeyCount;
            break;
         }
         return CallNextHookEx(hMouseHook, code, wParam, lParam);
      }
      
      //
      // install keyboard/mouse hooks
      //
      void KBM_API InstallHooks(void)
      {
         hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hInstance, 0);
         hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, hInstance, 0);
      }
      
      //
      // remove keyboard/mouse hooks
      //
      void KBM_API RemoveHooks(void)
      {
         UnhookWindowsHookEx(hKeyboardHook);
         UnhookWindowsHookEx(hMouseHook);
         hKeyboardHook = hMouseHook = 0;
      }
      
      //
      // retrieve number of keystrokes
      //
      int KBM_API FetchKeyCount(bool bClear)
      {
         int kc = iKeyCount;
         if (bClear)
            iKeyCount = 0;
         return kc;
      }
      

      【讨论】:

      • 同步(锁定)对共享段变量的访问可能是明智的。在你的例子中绝对是iKeyCount。我会为此使用一个命名的互斥锁。
      【解决方案4】:

      避免使用代码项目示例。 (大量的错误,MSDN 的坏副本)

      在 MSDN 上查看大量完整的 hooks 示例(MSDN、SDK、KB 等)

      而且您不需要任何 DLL,只需使用 LL 挂钩

      【讨论】:

      • 公平警告:我认为无 DLL 的 WH_KEYBOARD_LL 挂钩直到 Windows XP 才起作用,有些人需要支持旧版本。不过,原则上,您是完全正确的。
      • 感谢@myst 的回答,之前不知道 WH_KEYBOARD_LL
      【解决方案5】:

      对于仅用于热键的全局键盘挂钩,则 注册热键 是最好的(由 Microsoft 完成):

      https://msdn.microsoft.com/en-us/library/windows/desktop/ms646309(v=vs.85).aspx

      下载示例 winform 应用程序并亲自查看:

      https://code.msdn.microsoft.com/CppRegisterHotkey-7bd897a8C++ https://code.msdn.microsoft.com/CSRegisterHotkey-e3f5061eC# https://code.msdn.microsoft.com/VBRegisterHotkey-50af3179VB.Net

      Winform 应用程序:

      /****************************** Module Header ******************************\
      * Module Name:  MainForm.cs
      * Project:      CSRegisterHotkey
      * Copyright (c) Microsoft Corporation.
      * 
      * This is the main form of this application. It is used to initialize the UI 
      * and handle the events.
      * 
      * This source is subject to the Microsoft Public License.
      * See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
      * All other rights reserved.
      * 
      * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
      * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
      * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
      \***************************************************************************/
      
      using System;
      using System.Runtime.InteropServices;
      using System.Windows.Forms;
      
      namespace CSRegisterHotkey
      {
          public partial class MainForm : Form
          {
              HotKeyRegister hotKeyToRegister = null;
              Keys registerKey = Keys.None;
              KeyModifiers registerModifiers = KeyModifiers.None;
      
              public MainForm()
              {
                  InitializeComponent();
              }
      
              /// <summary>
              /// Handle the KeyDown of tbHotKey. In this event handler, check the pressed keys.
              /// The keys that must be pressed in combination with the key Ctrl, Shift or Alt,
              /// like Ctrl+Alt+T. The method HotKeyRegister.GetModifiers could check whether 
              /// "T" is pressed.
              /// </summary>
              private void tbHotKey_KeyDown(object sender, KeyEventArgs e)
              {
                  // The key event should not be sent to the underlying control.
                  e.SuppressKeyPress = true;
      
                  // Check whether the modifier keys are pressed.
                  if (e.Modifiers != Keys.None)
                  {
                      Keys key = Keys.None;
                      KeyModifiers modifiers = HotKeyRegister.GetModifiers(e.KeyData, out key);
      
                      // If the pressed key is valid...
                      if (key != Keys.None)
                      {
                          this.registerKey = key;
                          this.registerModifiers = modifiers;
      
                          // Display the pressed key in the textbox.
                          tbHotKey.Text = string.Format("{0}+{1}",
                              this.registerModifiers, this.registerKey);
      
                          // Enable the button.
                          btnRegister.Enabled = true;
                      }
                  }
              }
      
      
              /// <summary>
              /// Handle the Click event of btnRegister.
              /// </summary>
              private void btnRegister_Click(object sender, EventArgs e)
              {
                  try
                  {
                      // Register the hotkey.
                      hotKeyToRegister = new HotKeyRegister(this.Handle, 100,
                          this.registerModifiers, this.registerKey);
      
                      // Register the HotKeyPressed event.
                      hotKeyToRegister.HotKeyPressed += new EventHandler(HotKeyPressed);
      
                      // Update the UI.
                      btnRegister.Enabled = false;
                      tbHotKey.Enabled = false;
                      btnUnregister.Enabled = true;
                  }
                  catch (ArgumentException argumentException)
                  {
                      MessageBox.Show(argumentException.Message);
                  }
                  catch (ApplicationException applicationException)
                  {
                      MessageBox.Show(applicationException.Message);
                  }
              }
      
              /// <summary>
              /// Show a message box if the HotKeyPressed event is raised.
              /// </summary>
              void HotKeyPressed(object sender, EventArgs e)
              {
                 //Here is the magic!!!!!!!!'
      
                 //DO SOMETHING COOL!!! Or Just activate this winform
      
                  if (this.WindowState == FormWindowState.Minimized)
                  {
                      this.WindowState = FormWindowState.Normal;
                  }
                  this.Activate();
      
      
      
              }
      
              /// <summary>
              /// Handle the Click event of btnUnregister.
              /// </summary>
              private void btnUnregister_Click(object sender, EventArgs e)
              {
                  // Dispose the hotKeyToRegister.
                  if (hotKeyToRegister != null)
                  {
                      hotKeyToRegister.Dispose();
                      hotKeyToRegister = null;
                  }
      
                  // Update the UI.
                  tbHotKey.Enabled = true;
                  btnRegister.Enabled = true;
                  btnUnregister.Enabled = false;
              }
      
      
              /// <summary>
              /// Dispose the hotKeyToRegister when the form is closed.
              /// </summary>
              protected override void OnClosed(EventArgs e)
              {
                  if (hotKeyToRegister != null)
                  {
                      hotKeyToRegister.Dispose();
                      hotKeyToRegister = null;
                  }
      
                  base.OnClosed(e);
              }
          }
      }
      

      HotKeyRegister.cs

      /****************************** Module Header ******************************\
      * Module Name:  HotKeyRegister.cs
      * Project:      CSRegisterHotkey
      * Copyright (c) Microsoft Corporation.
      * 
      * This class imports the method RegisterHotKey and UnregisterHotKey in 
      * user32.dll to define or free a system-wide hot key.
      * 
      * The method Application.AddMessageFilter is used to add a message filter to 
      * monitor Windows messages as they are routed to their destinations. Before a 
      * message is dispatched, the method PreFilterMessage could handle it. If a 
      * WM_HOTKEY messages was generated by the hot key that was registered by this 
      * HotKeyRegister object, then raise a HotKeyPressed event.
      * 
      * This class also supplies a static method GetModifiers to get the modifiers 
      * and key from the KeyData property of KeyEventArgs.
      * 
      * This source is subject to the Microsoft Public License.
      * See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
      * All other rights reserved.
      * 
      * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
      * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
      * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
      \***************************************************************************/
      
      using System;
      using System.Runtime.InteropServices;
      using System.Windows.Forms;
      using System.Security.Permissions;
      
      namespace CSRegisterHotkey
      {
          public class HotKeyRegister : IMessageFilter, IDisposable
          {
              /// <summary>
              /// Define a system-wide hot key.
              /// </summary>
              /// <param name="hWnd">
              /// A handle to the window that will receive WM_HOTKEY messages generated by the
              /// hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the 
              /// message queue of the calling thread and must be processed in the message loop.
              /// </param>
              /// <param name="id">
              /// The identifier of the hot key. If the hWnd parameter is NULL, then the hot 
              /// key is associated with the current thread rather than with a particular 
              /// window. 
              /// </param>
              /// <param name="fsModifiers">
              /// The keys that must be pressed in combination with the key specified by the 
              /// uVirtKey parameter in order to generate the WM_HOTKEY message. The fsModifiers
              /// parameter can be a combination of the following values.
              /// MOD_ALT     0x0001
              /// MOD_CONTROL 0x0002
              /// MOD_SHIFT   0x0004
              /// MOD_WIN     0x0008
              /// </param>
              /// <param name="vk">The virtual-key code of the hot key.</param>
              [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
              private static extern bool RegisterHotKey(IntPtr hWnd, int id, 
                  KeyModifiers fsModifiers, Keys vk);
      
              /// <summary>
              /// Frees a hot key previously registered by the calling thread. 
              /// </summary>
              /// <param name="hWnd">
              /// A handle to the window associated with the hot key to be freed. This parameter
              /// should be NULL if the hot key is not associated with a window.
              /// </param>
              /// <param name="id">
              /// The identifier of the hot key to be freed. 
              /// </param>
              [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
              private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
      
              /// <summary>
              /// Get the modifiers and key from the KeyData property of KeyEventArgs.
              /// </summary>
              /// <param name="keydata">
              /// The KeyData property of KeyEventArgs. The KeyData is a key in combination
              /// with modifiers.
              /// </param>
              /// <param name="key">The pressed key.</param>
              public static KeyModifiers GetModifiers(Keys keydata, out Keys key)
              {
                  key = keydata;
                  KeyModifiers modifers = KeyModifiers.None;
      
                  // Check whether the keydata contains the CTRL modifier key.
                  // The value of Keys.Control is 131072.
                  if ((keydata & Keys.Control) == Keys.Control)
                  {
                      modifers |= KeyModifiers.Control;
      
                      key = keydata ^ Keys.Control;
                  }
      
                  // Check whether the keydata contains the SHIFT modifier key.
                  // The value of Keys.Control is 65536.
                  if ((keydata & Keys.Shift) == Keys.Shift)
                  {
                      modifers |= KeyModifiers.Shift;
                      key = key ^ Keys.Shift;
                  }
      
                  // Check whether the keydata contains the ALT modifier key.
                  // The value of Keys.Control is 262144.
                  if ((keydata & Keys.Alt) == Keys.Alt)
                  {
                      modifers |= KeyModifiers.Alt;
                      key = key ^ Keys.Alt;
                  }
      
                  // Check whether a key other than SHIFT, CTRL or ALT (Menu) is pressed.
                  if (key == Keys.ShiftKey || key == Keys.ControlKey || key == Keys.Menu)
                  {
                      key = Keys.None;
                  }
      
                  return modifers;
              }
      
              /// <summary>
              /// Specify whether this object is disposed.
              /// </summary>
              bool disposed = false;
      
              /// <summary>
              /// This constant could be found in WinUser.h if you installed Windows SDK.
              /// Each windows message has an identifier, 0x0312 means that the mesage is 
              /// a WM_HOTKEY message.
              /// </summary>
              const int WM_HOTKEY = 0x0312;
      
              /// <summary>
              /// A handle to the window that will receive WM_HOTKEY messages generated by the
              /// hot key.
              /// </summary>
              public IntPtr Handle { get; private set; }
      
              /// <summary>
              /// A normal application can use any value between 0x0000 and 0xBFFF as the ID 
              /// but if you are writing a DLL, then you must use GlobalAddAtom to get a 
              /// unique identifier for your hot key. 
              /// </summary>
              public int ID { get; private set; }
      
              public KeyModifiers Modifiers { get; private set; }
      
              public Keys Key { get; private set; }
      
              /// <summary>
              /// Raise an event when the hotkey is pressed.
              /// </summary>
              public event EventHandler HotKeyPressed;
      
      
              public HotKeyRegister(IntPtr handle, int id, KeyModifiers modifiers, Keys key)
              {
                  if (key == Keys.None || modifiers == KeyModifiers.None)
                  {
                      throw new ArgumentException("The key or modifiers could not be None.");
                  }
      
                  this.Handle = handle;
                  this.ID = id;
                  this.Modifiers = modifiers;
                  this.Key = key;
      
                  RegisterHotKey();
      
                  // Adds a message filter to monitor Windows messages as they are routed to
                  // their destinations.
                  Application.AddMessageFilter(this);
              }
      
      
              /// <summary>
              /// Register the hotkey.
              /// </summary>
              private void RegisterHotKey()
              {
                  bool isKeyRegisterd = RegisterHotKey(Handle, ID, Modifiers, Key);
      
                  // If the operation failed, try to unregister the hotkey if the thread 
                  // has registered it before.
                  if (!isKeyRegisterd)
                  {
                      // IntPtr.Zero means the hotkey registered by the thread.
                      UnregisterHotKey(IntPtr.Zero, ID);
      
                      // Try to register the hotkey again.
                      isKeyRegisterd = RegisterHotKey(Handle, ID, Modifiers, Key);
      
                      // If the operation still failed, it means that the hotkey was already 
                      // used in another thread or process.
                      if (!isKeyRegisterd)
                      {
                          throw new ApplicationException("The hotkey is in use");
                      }
                  }
              }
      
      
              /// <summary>
              /// Filters out a message before it is dispatched.
              /// </summary>
              [PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
              public bool PreFilterMessage(ref Message m)
              {
                  // The property WParam of Message is typically used to store small pieces 
                  // of information. In this scenario, it stores the ID.
                  if (m.Msg == WM_HOTKEY
                      && m.HWnd == this.Handle
                      && m.WParam == (IntPtr)this.ID
                      && HotKeyPressed != null)
                  {
                      // Raise the HotKeyPressed event if it is an WM_HOTKEY message.
                      HotKeyPressed(this, EventArgs.Empty);
      
                      // True to filter the message and stop it from being dispatched.
                      return true;
                  }
      
                  // Return false to allow the message to continue to the next filter or 
                  // control.
                  return false;
              }
      
      
              public void Dispose()
              {
                  Dispose(true);
                  GC.SuppressFinalize(this);
              }
      
              /// <summary>
              /// Unregister the hotkey.
              /// </summary>
              protected virtual void Dispose(bool disposing)
              {
                  // Protect from being called multiple times.
                  if (disposed)
                  {
                      return;
                  }
      
                  if (disposing)
                  {
      
                      // Removes a message filter from the message pump of the application.
                      Application.RemoveMessageFilter(this);
      
                      UnregisterHotKey(Handle, ID);
                  }
      
                  disposed = true;
              }
          }
      }
      

      KeyModifiers.cs:

      /****************************** Module Header ******************************\
      * Module Name:  KeyModifiers.cs
      * Project:      CSRegisterHotkey
      * Copyright (c) Microsoft Corporation.
      * 
      * This enum defines the modifiers to generate the WM_HOTKEY message. 
      * See http://msdn.microsoft.com/en-us/library/ms646309(VS.85).aspx.
      * 
      * This source is subject to the Microsoft Public License.
      * See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
      * All other rights reserved.
      * 
      * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
      * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
      * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
      \***************************************************************************/
      
      using System;
      
      namespace CSRegisterHotkey
      {
          [Flags]
          public enum KeyModifiers
          {
              None = 0,
              Alt = 1,
              Control = 2,
              Shift = 4,
      
              // Either WINDOWS key was held down. These keys are labeled with the Windows logo.
              // Keyboard shortcuts that involve the WINDOWS key are reserved for use by the 
              // operating system.
              Windows = 8
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-26
        • 1970-01-01
        • 1970-01-01
        • 2012-02-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多