C#键盘钩子实现:屏蔽系统热键与全局事件捕获

橘子海
橘子海
发布时间:2026-10-14 | 5人浏览过
原创

本文介绍如何使用C#结合Windows API实现键盘钩子(Keyboard Hook),以拦截并屏蔽系统级热键或捕获全局按键事件。核心逻辑通过SetWindowsHookEx安装WH_KEYBOARD_LL钩子,在回调函数中解析消息并选择是否调用CallNextHookEx放行。

原理概述

C#实现的键盘钩子主要依赖 user32.dll 中的非托管函数。通过安装低级别键盘钩子(WH_KEYBOARD_LL),程序可以在消息到达目标窗口之前截获按键信号。关键点在于判断是否阻止消息继续传递:
1. 若需要屏蔽热键,不调用 CallNextHookEx 或直接返回特定值。
2. 若仅需监听而不停止输入,则调用 CallNextHookEx 将控制权交还给下一个钩子或系统。

核心类实现

以下代码展示了完整的 Hooks 类实现,包含私有变量定义、WinAPI引用、钩子处理逻辑以及安装/卸载方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;

namespace KeyboardHookDemo
{
    class Hooks
    {
        #region 私有变量
        private IntPtr m_pKeyboardHook = IntPtr.Zero; // 键盘钩子句柄
        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); // 钩子委托声明
        private HookProc m_KeyboardHookProcedure; // 键盘钩子委托实例
        
        public const int idHook = 13; // WH_KEYBOARD_LL 的低级别键盘钩子标识
        
        // 导入WinAPI函数
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr pInstance, int threadId); // 安装钩子
        
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(IntPtr pHookHandle); // 卸载钩子
        
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(IntPtr pHookHandle, int nCode, Int32 wParam, IntPtr lParam); // 传递给下一个钩子
        
        // 结构体用于接收钩子数据
        [StructLayout(LayoutKind.Sequential)]
        public struct KeyMSG
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }
        
        protected const int WM_QUERYENDSESSION = 0x0011;
        protected const int WM_KEYDOWN = 0x100;
        protected const int WM_KEYUP = 0x101;
        protected const int WM_SYSKEYDOWN = 0x104;
        protected const int WM_SYSKEYUP = 0x105;
        
        // 虚拟键码常量
        protected const byte VK_SHIFT = 0x10;
        protected const byte VK_CAPITAL = 0x14;
        protected const byte VK_NUMLOCK = 0x90;
        protected const byte VK_LSHIFT = 0xA0;
        protected const byte VK_RSHIFT = 0xA1;
        protected const int VK_LWIN = 91;
        protected const int VK_RWIN = 92;
        protected const byte VK_LCONTROL = 0xA2;
        protected const byte VK_RCONTROL = 0x3;
        protected const byte VK_LALT = 0xA4;
        protected const byte VK_RALT = 0xA5;
        protected const byte LLKHF_ALTDOWN = 0x20;
        
        // 状态标志
        public bool PowerKeyBlocked = true; // 是否屏蔽Power键
        public static int pp = 0; // 热键返回值: 1为屏蔽, 0为允许(根据具体需求调整)
        public static bool isSet = false; // 是否设置屏蔽热键
        public static bool isHotkey = false;
        public static bool isInstall = false; // 是否安装钩子
        #endregion

        #region 事件的声明
        public event KeyEventHandler KeyDown; // 键盘按下事件
        public event KeyEventHandler KeyUp;   // 键盘松开事件
        public event KeyPressEventHandler KeyPress; // 键盘单击事件
        #endregion

        #region 方法
        /// <summary>
        /// 钩子捕获消息后,对消息进行处理
        /// </summary>
        private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam)
        {
            if (nCode > -1 && (KeyDown != null || KeyUp != null || KeyPress != null))
            {
                // 将非托管内存转换为结构体
                KeyMSG keyboardHookStruct = (KeyMSG)Marshal.PtrToStructure(lParam, typeof(KeyMSG));
                KeyEventArgs e = new KeyEventArgs((Keys)(keyboardHookStruct.vkCode));
                
                switch (wParam)
                {
                    case WM_KEYDOWN:
                    case WM_SYSKEYDOWN:
                        if (KeyDown != null)
                        {
                            KeyDown(this, e);
                        }
                        break;
                    case WM_KEYUP:
                    case WM_SYSKEYUP:
                        if (KeyUp != null)
                        {
                            KeyUp(this, e);
                        }
                        break;
                }
            }
            
            // 关键判断:如果需要屏蔽,不执行CallNextHookEx或返回非零值
            // 这里示例中直接返回pp的值,若需完全屏蔽通常return 1即可
            return pp;
        }
        #endregion

        #region 安装、卸载钩子
        /// <summary>
        /// 安装钩子
        /// </summary>
        public bool Start()
        {
            // 注意:对于低级别钩子(WH_KEYBOARD_LL),pInstance通常传入当前模块句柄或0
            // 此处保留原代码逻辑:IntPtr pInstance = (IntPtr)4194304; 
            // 实际工程中建议使用 Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0])
            IntPtr pInstance = IntPtr.Zero; 
            
            if (this.m_pKeyboardHook == IntPtr.Zero)
            {
                this.m_KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                // 安装全局键盘钩子
                this.m_pKeyboardHook = SetWindowsHookEx(idHook, m_KeyboardHookProcedure, pInstance, 0);
                
                if (this.m_pKeyboardHook == IntPtr.Zero)
                {
                    this.Stop(); // 安装失败则清理
                    return false;
                }
            }
            isInstall = true;
            return true;
        }

        /// <summary>
        /// 卸载钩子
        /// </summary>
        public bool Stop()
        {
            if (isInstall == false)
            {
                return true;
            }
            bool result = true;
            if (this.m_pKeyboardHook != IntPtr.Zero)
            {
                result = (UnhookWindowsHookEx(this.m_pKeyboardHook) && result);
                this.m_pKeyboardHook = IntPtr.Zero;
            }
            return result;
        }
        #endregion
    }
}

使用注意事项

  1. 权限要求:安装全局钩子通常需要足够的权限,部分安全软件可能会拦截此类操作。
  2. 内存管理:确保在应用程序退出时调用 Stop() 方法,否则可能导致 InvalidOperation 异常或钩子残留。
  3. 性能影响:全局钩子会在每个进程的消息队列中触发,过于频繁的回调可能影响系统性能,建议仅在必要时启用。
  4. 线程安全:钩子回调运行在注入进程的上下文中(虽然LL钩子在本地进程),事件触发时需考虑UI线程的跨线程访问问题。

常见问题排查

如果钩子安装后立即失败(Start() 返回 false):

  • 检查是否已存在其他冲突的钩子。
  • 确认 pInstance 参数是否正确,对于LL钩子通常传 IntPtr.Zero 即可由系统自动处理。

以上就是基于C#实现键盘钩子(HOOK)以屏蔽系统热键或捕获全局按键的详细内容,更多关于C# WinAPI编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表