Windows启用LowLevelKeyboardProc钩子时SendInput无法发送键盘输入问题
问题根因
代码存在3个核心逻辑错误,导致钩子生效时SendInput无法正常工作:
- 钩子回调分支缺失兜底传递逻辑:仅在
kbdStruct.flags != LLKHF_INJECTED的分支内调用了CallNextHookEx,自身SendInput产生的带LLKHF_INJECTED标记的注入按键事件,走到回调后没有被传递给钩子链下一环,直接被丢弃。 - 消息循环被阻塞:把
sendString直接放在GetMessageW循环内,且每个字符发送前加250ms Sleep,直接卡死了低级键盘钩子依赖的消息泵。低级键盘钩子的回调运行在安装钩子的线程中,消息泵阻塞时,不仅钩子会失效,SendInput注入的事件也无法被系统正常派发。 - 按键拦截不完整:仅拦截了按键按下事件(
WM_KEYDOWN/WM_SYSKEYDOWN),没有拦截对应的抬起事件,真实物理按键的抬起事件仍会传递到前台窗口,会造成全局按键状态错乱。
修复方案
按以下规则调整代码即可正常运行:
- 修正钩子回调逻辑:所有不需要拦截的事件(包括自身注入的按键事件),必须调用
CallNextHookEx传递给钩子链,仅对需要屏蔽的真实物理按键(无LLKHF_INJECTED标记)返回非0值吞掉事件。 - 保证消息循环畅通:不要在消息循环、钩子回调中加入长耗时Sleep、阻塞操作,消息循环仅负责正常分发消息,字符串发送逻辑通过定时器或者其他异步方式触发,不要阻塞消息泵。
- 补全按键拦截范围:同时拦截真实物理按键的按下、抬起事件,避免按键状态异常。
修复后的完整可运行代码:
#include <Windows.h> #include <vector> #include <string> HHOOK kHook; LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam; // 非注入事件为真实物理按键,全部拦截 if (!(kbdStruct->flags & LLKHF_INJECTED)) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: return 1; } } } // 其余事件(含自身SendInput注入的事件)正常传递 return CallNextHookEx(kHook, nCode, wParam, lParam); } void sendString(const std::wstring& message) { std::vector<INPUT> inputList; for (auto ch : message) { INPUT keyDown = {0}; keyDown.type = INPUT_KEYBOARD; keyDown.ki.dwFlags = KEYEVENTF_UNICODE; keyDown.ki.wScan = ch; inputList.push_back(keyDown); INPUT keyUp = {0}; keyUp.type = INPUT_KEYBOARD; keyUp.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; keyUp.ki.wScan = ch; inputList.push_back(keyUp); } SendInput(inputList.size(), inputList.data(), sizeof(INPUT)); } int main() { kHook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0); MSG msg; // 示例:安装钩子3秒后发送测试字符串,不阻塞消息循环 SetTimer(NULL, 0, 3000, [](HWND, UINT, UINT_PTR, DWORD) { sendString(L"Some String"); }); while (GetMessageW(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageW(&msg); } UnhookWindowsHookEx(kHook); return 0; }
注意:
WH_KEYBOARD_LL属于线程级全局钩子,不需要封装到DLL中,但系统对钩子响应时间有严格限制,如果安装钩子的线程消息泵阻塞超过一定时间,系统会自动绕过该钩子,直接传递键盘事件,因此必须保证该线程的消息循环随时处于可响应状态。
内容的提问来源于stack exchange,提问作者LC0
相关产品推荐
相关产品推荐

