Windows 11全局键盘钩子失效求助:如何实现全局热键文本注入?
全局热键注入字符串:从WH_KEYBOARD_LL到WH_KEYBOARD的解决方案
问题背景
需求是实现LeftAlt+1和LeftAlt+2两组全局组合键,按下时向当前激活的程序注入指定字符串。现有基于WH_KEYBOARD_LL钩子的实现仅能在测试程序控制台内生效,文本会输出到测试程序窗口而非目标程序,计划改用WH_KEYBOARD钩子解决该问题。
现有代码
钩子DLL代码(PernumLib.dll)
#include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } #include <windows.h> #include <iostream> using namespace std; HHOOK hHook{ NULL }; const DWORD L1 = 49; const DWORD L2 = 50; extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) return CallNextHookEx(hHook, code, wParam, lParam); if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam; DWORD vkCode = kbdStruct->vkCode; // virtual-key code if (kbdStruct->flags & LLKHF_ALTDOWN && (vkCode == L1 || vkCode == L2)) { if (vkCode == L1) cout << "First text"; else cout << "Second text"; } } return CallNextHookEx(hHook, code, wParam, lParam); }
测试程序代码
#include <windows.h> #include <iostream> using namespace std; int main() { HHOOK hhook = 0; LPCWSTR dllPath = L"PernumLib.dll"; static HINSTANCE hinstDLL = LoadLibraryW(dllPath); HOOKPROC keyboardHookProc = (HOOKPROC)GetProcAddress(hinstDLL, "KeyboardHookProc"); hhook = SetWindowsHookExW(WH_KEYBOARD_LL, keyboardHookProc, hinstDLL, 0); if (hhook == NULL) { cout << "SetWindowsHookExA failure!\n"; Sleep(3000); return -1; } while (GetMessage(NULL, NULL, 0, WM_KEYLAST)); //if (hhook) UnhookWindowsHookEx(hhook); return 0; }
问题分析
WH_KEYBOARD_LL是用户态全局钩子,其回调函数始终在安装钩子的测试进程上下文执行,因此cout输出会直接写入测试程序控制台,而非当前激活的目标窗口。- 已排除32/64位架构不兼容问题,钩子本身可捕获全局按键,但输出上下文不符合需求。
WH_KEYBOARD是线程注入型全局钩子,钩子DLL会被注入到每个目标进程,回调函数在目标进程上下文执行,能实现向当前激活窗口注入文本。
解决方案:改用WH_KEYBOARD钩子
修改后的钩子DLL代码
#include "pch.h" #include <windows.h> // 模拟Unicode键盘输入,向当前窗口注入字符串 void SendString(const wchar_t* text) { size_t charCount = wcslen(text); INPUT* inputArray = new INPUT[charCount * 2]; ZeroMemory(inputArray, sizeof(INPUT) * charCount * 2); for (size_t i = 0; i < charCount; ++i) { // 按下字符 inputArray[i * 2].type = INPUT_KEYBOARD; inputArray[i * 2].ki.wVk = 0; inputArray[i * 2].ki.wScan = text[i]; inputArray[i * 2].ki.dwFlags = KEYEVENTF_UNICODE; // 释放字符 inputArray[i * 2 + 1].type = INPUT_KEYBOARD; inputArray[i * 2 + 1].ki.wVk = 0; inputArray[i * 2 + 1].ki.wScan = text[i]; inputArray[i * 2 + 1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; } SendInput(charCount * 2, inputArray, sizeof(INPUT)); delete[] inputArray; } extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) { return CallNextHookEx(NULL, code, wParam, lParam); } KBDHOOKSTRUCT* kbdStruct = (KBDHOOKSTRUCT*)lParam; // 检查LeftAlt是否处于按下状态 BOOL isLeftAltDown = (GetKeyState(VK_LMENU) & 0x8000) != 0; if (isLeftAltDown && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) { if (kbdStruct->vkCode == '1') { SendString(L"First text"); return 1; // 拦截原按键,避免输入"1" } else if (kbdStruct->vkCode == '2') { SendString(L"Second text"); return 1; // 拦截原按键,避免输入"2" } } return CallNextHookEx(NULL, code, wParam, lParam); } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hModule); // 减少不必要的线程通知 break; case DLL_PROCESS_DETACH: break; default: break; } return TRUE; }
修改后的测试程序代码
#include <windows.h> #include <iostream> #include <wchar.h> using namespace std; int main() { HHOOK hhook = NULL; WCHAR dllFullPath[MAX_PATH]; // 获取DLL绝对路径,避免其他进程找不到文件 GetFullPathNameW(L"PernumLib.dll", MAX_PATH, dllFullPath, NULL); HINSTANCE hinstDLL = LoadLibraryW(dllFullPath); if (!hinstDLL) { cout << "LoadLibraryW failed! Error: " << GetLastError() << endl; Sleep(3000); return -1; } HOOKPROC keyboardHookProc = (HOOKPROC)GetProcAddress(hinstDLL, "KeyboardHookProc"); if (!keyboardHookProc) { cout << "GetProcAddress failed! Error: " << GetLastError() << endl; FreeLibrary(hinstDLL); Sleep(3000); return -1; } // 安装WH_KEYBOARD全局钩子,0表示注入所有同架构线程 hhook = SetWindowsHookExW(WH_KEYBOARD, keyboardHookProc, hinstDLL, 0); if (!hhook) { cout << "SetWindowsHookExW failed! Error: " << GetLastError() << endl; FreeLibrary(hinstDLL); Sleep(3000); return -1; } cout << "Hook installed successfully. Press any key to exit..." << endl; MSG msg; while (GetMessageW(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessageW(&msg); } // 清理资源 if (hhook) { UnhookWindowsHookEx(hhook); } FreeLibrary(hinstDLL); return 0; }
关键注意事项
- 架构匹配:确保DLL和测试程序编译为相同架构(x86/x64),否则无法注入到对应架构的进程。
- 注入限制:部分高安全级进程(如带沙箱的浏览器)可能阻止DLL注入,此类场景下
WH_KEYBOARD钩子无法生效。 - DLL路径:必须使用绝对路径加载DLL,否则其他进程可能找不到DLL文件导致注入失败。
- 权限要求:若目标进程为管理员权限,测试程序也需以管理员权限运行才能完成注入。
内容的提问来源于stack exchange,提问作者Kalle Svensson
相关产品推荐
相关产品推荐

