首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 全域鍵盤鉤子實作:使用 SetWindowsHookEx 攔截鍵盤訊息

     編輯:關於C語言

前言

在 Windows 應用程式中實作全域熱鍵、鍵盤記錄或輸入攔截,需使用 Win32 API SetWindowsHookEx 安裝鍵盤鉤子 (WH_KEYBOARD 或 WH_KEYBOARD_LL)。本文提供完整的 C# P/Invoke 封裝類別、鉤子處理流程、執行緒親和性注意事項與範例應用。

P/Invoke 宣告

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class KeyboardHook : IDisposable
{
    // 鉤子類型
    private enum HookType : int
    {
        WH_KEYBOARD = 2,       // 執行緒鉤子 (需 DLL 注入)
        WH_KEYBOARD_LL = 13    // 低階鉤子 (全域、無需 DLL、建議使用)
    }

    // 訊息常數
    private const int WM_KEYDOWN = 0x0100;
    private const int WM_KEYUP = 0x0101;
    private const int WM_SYSKEYDOWN = 0x0104;
    private const int WM_SYSKEYUP = 0x0105;

    // 委派簽名 (對應 HOOKPROC)
    private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    // Win32 API
    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(HookType idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll")]
    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);

    // 鍵盤結構 (WH_KEYBOARD_LL 使用)
    [StructLayout(LayoutKind.Sequential)]
    private struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;      // 虛擬鍵碼
        public uint scanCode;    // 掃描碼
        public uint flags;       // 標誌
        public uint time;        // 時間戳
        public IntPtr dwExtraInfo;
    }

    // 實例欄位
    private IntPtr _hookHandle = IntPtr.Zero;
    private HookProc _hookProc;
    private readonly object _lock = new();

    // 事件:鍵盤按下/釋放
    public event EventHandler KeyDown;
    public event EventHandler KeyUp;

    public KeyboardHook()
    {
        _hookProc = HookCallback; // 必須保持委派參考,防止 GC 回收
    }

    public void Install()
    {
        if (_hookHandle != IntPtr.Zero) return;

        // 使用 WH_KEYBOARD_LL (低階鉤子) - 全域、不需 DLL、當前執行緒即可
        _hookHandle = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, _hookProc, 
            GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);

        if (_hookHandle == IntPtr.Zero)
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
    }

    public void Uninstall()
    {
        if (_hookHandle != IntPtr.Zero)
        {
            UnhookWindowsHookEx(_hookHandle);
            _hookHandle = IntPtr.Zero;
        }
    }

    private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0) // HC_ACTION = 0
        {
            int msg = wParam.ToInt32();
            var kb = Marshal.PtrToStructure(lParam);
            Keys key = (Keys)kb.vkCode;

            var args = new KeyEventArgs(key);
            switch (msg)
            {
                case WM_KEYDOWN:
                case WM_SYSKEYDOWN:
                    KeyDown?.Invoke(this, args);
                    break;
                case WM_KEYUP:
                case WM_SYSKEYUP:
                    KeyUp?.Invoke(this, args);
                    break;
            }

            // 若 Handled=true,攔截鍵盤訊息不再傳遞
            if (args.Handled) return (IntPtr)1;
        }
        return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
    }

    public void Dispose() => Uninstall();
}

// 使用範例
public class DemoForm : Form
{
    private KeyboardHook _hook;

    public DemoForm()
    {
        _hook = new KeyboardHook();
        _hook.KeyDown += (s, e) => 
        {
            if (e.KeyCode == Keys.F12) 
            {
                MessageBox.Show("全域熱鍵 F12 被觸發!");
                e.Handled = true; // 攔截
            }
        };
        _hook.Install();
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        _hook.Dispose();
        base.OnFormClosed(e);
    }
}

WH_KEYBOARD vs WH_KEYBOARD_LL 比較

重要注意事項

  • 委派必須保持參考:_hookProc = HookCallback 存為欄位,否則 GC 回收導致崩潰。
  • 低階鉤子超時:若回呼超過 ~300ms,系統會自動移除鉤子。處理邏輯必須極快,耗時操作請 Task.Run 非同步處理。
  • 管理員權限:在 UAC 啟用環境,若目標進程為高完整性等級 (以系統管理員執行),鉤子進程也需提升權限。
  • 64 位元相容:使用 IntPtr、SetLastError=true、CallingConvention.StdCall (預設) 確保 x64 正確。
  • 應用程式結束時必須 Unhook:實作 IDisposable、FormClosed、AppDomain.ProcessExit 多重保障。

進階應用:WPF 全域熱鍵註冊替代方案

若僅需註冊特定熱鍵 (非攔截所有鍵盤),建議使用 RegisterHotKey / UnregisterHotKey API,效能更好、無超時風險、不需鉤子:

[DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

// 在 Window 視窗程序中處理 WM_HOTKEY (0x0312)
protected override void WndProc(ref Message m)
{
    if (m.Msg == 0x0312) { /* 熱鍵觸發 */ }
    base.WndProc(ref m);
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性WH_KEYBOARD (2)WH_KEYBOARD_LL (13)
範圍特定執行緒全域 (所有執行緒)
需 DLL 注入是 (若跨進程)否
效能影響較低較高 (每鍵盤事件切換至安裝進程)
適用場景同進程內部攔截全域熱鍵、跨進程監聽
UAC/完整性等級受限需相同或較高完整性等級