wh_keyboard_ll是唯一稳定、无需dll且兼容windows 10/11的全局键盘监听方案,因其在键盘驱动层后、消息队列前捕获输入,支持控制台与gui程序,但无法拦截ctrl+alt+del等sas组合键。

用 SetWindowsHookEx + WH_KEYBOARD_LL 是唯一稳定、无需 DLL、且兼容 Windows 10/11 的方案。别碰 WH_KEYBOARD,它在现代系统上基本失效,且必须走 DLL 注入,极易被杀软拦截或 UAC 拦截。
为什么必须用 WH_KEYBOARD_LL 而不是 WH_KEYBOARD
WH_KEYBOARD 依赖目标线程的消息循环和钩子 DLL 注入,而现代应用(尤其是无窗口控制台、UWP、沙盒进程)根本不走传统消息泵,导致钩子完全收不到消息。更麻烦的是,从 Windows 8.1 起,WH_KEYBOARD 在非前台进程或高完整性级别下常返回空回调 —— 不报错,也不触发,纯静默失败。
WH_KEYBOARD_LL 是内核级预处理钩子,由 csrss.exe 统一派发,在键盘驱动层之后、消息队列之前捕获扫描码转虚拟键码的结果,只要按键物理发生,就一定进回调。
- 回调函数可直接写在主程序里,不用单独编译 DLL
- 不需要
GetModuleHandle(NULL)传模块句柄(传 0 也行,但建议显式传) - 不依赖目标进程是否运行 GetMessage/PeekMessage 循环
- 但注意:它无法拦截 Ctrl+Alt+Del、Win+L 等 Secure Attention Sequence(SAS)组合键,这是系统硬性限制
LowLevelKeyboardProc 回调里怎么判断真实按键
很多人只检查 wParam == WM_KEYDOWN,结果发现 Alt、Ctrl、Shift 单独按没反应,或者重复触发(长按自动连发)。关键在 KBDLLHOOKSTRUCT 的 flags 字段和 scanCode 配合判断。
- 必须先确认
nCode == HC_ACTION,否则直接CallNextHookEx -
wParam只能是WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP四种,别用==判其他值 - 过滤重复键:检查
((KBDLLHOOKSTRUCT*)lParam)->flags & LLKHF_REPEAT,为真说明是自动连发,不是用户新按 - 区分普通键和系统键:用
wParam == WM_SYSKEYDOWN捕获 Alt+Tab、Alt+F4 等,但注意 Win 键单独按也会走这里 - 获取字符需自己映射:
MapVirtualKey(vkCode, MAPVK_VK_TO_CHAR)不可靠(受键盘布局影响),更稳妥是用ToUnicodeEx配合当前线程键盘布局
控制台程序也能用,但消息循环不能少
很多教程说“控制台程序没法用钩子”,其实是误传。WH_KEYBOARD_LL 本身不要求窗口,但 GetMessage 必须有有效的消息队列 —— 控制台默认没有。解决方案很简单:
- 调用
AllocConsole()创建控制台窗口(如果还没分配) - 确保主线程调用
GetMessage(&msg, NULL, 0, 0),不能用cin或scanf替代 - 别漏掉
TranslateMessage和DispatchMessage,否则某些修饰键状态(如 Shift)可能不同步 - 如果程序需要后台运行(无控制台),改用隐藏窗口:创建不可见的
HWND(WS_POPUP+SW_HIDE),再用该窗口句柄调GetMessage
卸载钩子时容易忽略的两个坑
多数示例把 UnhookWindowsHookEx 放在 main 结尾,看似没问题,但实际场景中几乎总出问题:
- 用户直接关终端窗口(X 按钮)或 Ctrl+C 中断,
main退出前根本来不及执行卸载 —— 钩子残留,后续同名程序可能复用旧钩子句柄,行为异常 -
SetConsoleCtrlHandler是唯一可靠的清理入口:注册一个控制台事件处理器,在CTRL_CLOSE_EVENT或CTRL_C_EVENT里调UnhookWindowsHookEx并exit(0) - 钩子句柄
HHOOK必须是全局变量或静态存储期,不能放在局部作用域里,否则回调里无法访问 - 多线程环境下,确保安装/卸载都在同一线程执行(Windows 要求钩子安装和卸载线程一致)
真正难的不是装上钩子,而是让它在各种退出路径下干净卸载、不残留、不干扰其他程序 —— 这部分逻辑往往比监听本身更费调试时间。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











