如何正确卸载Windows钩子并让线程执行后续清理操作?
我有一个非DLL程序,程序创建了一个线程,在该线程中通过SetWindowsHookExW()创建WH_KEYBOARD_LL钩子捕获键盘事件,用全局变量bContinue控制线程关闭。
现在的问题是无法正确释放钩子并让线程执行后续清理操作:调用UnhookWindowsHookEx()后,后续代码的断点始终无法触发。MSDN的示例依赖app.h无法直接使用,网上教程大多针对DLL场景,部分实现了卸载包装函数却没实际用到。
我已经尝试了以下几种消息循环写法:
#include <Windows.h> bool bContinue; wchar_t vkTecla; HHOOK hHook; // 补充原代码缺失的全局变量定义 LRESULT CALLBACK KeyboardCallBack(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { vkTecla = ((KBDLLHOOKSTRUCT*)lParam)->vkCode; if (wParam == WM_KEYDOWN) { // 执行相关操作 } // ... } if (bContinue) { UnhookWindowsHookEx(hHook); return 0; } return CallNextHookEx(hHook, nCode, wParam, lParam); } void MainThread(HANDLE hfile) { hHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardCallBack, 0, 0); MSG msg; // 我测试了以下三种情况 /*************/ while(GetMessageW(&msg, 0, 0, 0)); // 或者 GetMessageW(&msg, 0, 0, 0); /*************/ while(GetMessageW(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /*************/ while (bContinue) { // PeekMessage(...); GetMessageW(&msg, 0, 0, 0); TranslateMessage(&msg); DispatchMessage(&msg); } // 这种写法下我会移除回调里的卸载代码 UnhookWindowsHookEx(hHook); /*************/ CloseHandle(hfile); }
注:补充了原代码中缺失的HHOOK hHook全局变量定义,否则编译会报错。
无论是在回调中检查bContinue并调用UnhookWindowsHookEx,还是在线程的消息循环中判断bContinue后调用卸载函数,CloseHandle(hfile)处的断点始终无法触发。
现在需要明确两个问题:
UnhookWindowsHookEx()应该放在回调函数内还是其他位置?- 卸载钩子后线程能否继续执行后续的清理代码?
问题解答
UnhookWindowsHookEx()的放置位置
绝对不要在钩子回调函数内调用UnhookWindowsHookEx()。因为WH_KEYBOARD_LL是特殊的钩子,它的回调是在安装钩子的线程的消息循环中触发的——系统会向该线程发送WM_KEYBOARD_LL消息,线程的消息循环处理这个消息时才会调用回调。如果在回调里直接卸载钩子,此时线程的消息循环可能还处于等待消息的阻塞状态,后续清理代码根本没机会执行。正确做法是:在控制线程退出的逻辑中(比如设置
bContinue为false的地方),先向钩子线程发送自定义退出消息,然后在线程的消息循环中捕获这个消息,再调用UnhookWindowsHookEx(),最后发送WM_QUIT让消息循环退出,执行后续清理。卸载钩子后线程能否执行后续清理
完全可以,但必须保证线程的消息循环能正常退出。WH_KEYBOARD_LL钩子依赖安装它的线程的消息循环,只要钩子存在,系统就会持续向该线程发送钩子消息,导致GetMessage一直阻塞。只有卸载钩子后,再让消息循环收到退出信号(比如WM_QUIT),线程才能跳出循环,执行后续清理代码。
修正后的示例代码
#include <Windows.h> bool bContinue = true; wchar_t vkTecla; HHOOK hHook; const UINT WM_EXIT_HOOK = WM_USER + 1; LRESULT CALLBACK KeyboardCallBack(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { vkTecla = ((KBDLLHOOKSTRUCT*)lParam)->vkCode; if (wParam == WM_KEYDOWN) { // 执行相关操作 } // ... } return CallNextHookEx(hHook, nCode, wParam, lParam); } void MainThread(HANDLE hfile) { hHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardCallBack, NULL, 0); if (!hHook) { // 钩子创建失败,直接清理退出 CloseHandle(hfile); return; } MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { if (msg.message == WM_EXIT_HOOK) { // 收到退出消息,卸载钩子 UnhookWindowsHookEx(hHook); hHook = NULL; // 发送WM_QUIT让消息循环退出 PostQuitMessage(0); } else { TranslateMessage(&msg); DispatchMessage(&msg); } } // 消息循环退出后执行清理 CloseHandle(hfile); } // 外部触发线程退出的函数示例 void StopHookThread(HANDLE hThread) { bContinue = false; // 向钩子线程发送退出消息 PostThreadMessageW(GetThreadId(hThread), WM_EXIT_HOOK, 0, 0); // 等待线程退出 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); }
内容的提问来源于stack exchange,提问作者EnderMega

