全域鍵盤鉤子 (WH_KEYBOARD) 必須置於 DLL 中,才能注入所有目標進程位址空間。C++ Builder 建立 DLL 專案,導出安裝/移除函數,主程式動態載入調用,實現模組化與職責分離。
HINSTANCE g_hinstDll = NULL; // DLL 模組實例
HHOOK g_hhook = NULL; // 鉤子控制代碼
HWND g_hwndPost = NULL; // 接收通知的視窗控制代碼
UINT g_uMsgNotify = WM_USER; // 自訂通知訊息
HOOKPROC KeyboardHook_HookProc(int nCode, WPARAM wParam, LPARAM lParam) {
LRESULT lResult = CallNextHookEx(g_hhook, nCode, wParam, lParam);
if (nCode == HC_ACTION) {
// 轉發鍵盤訊息至指定視窗
PostMessage(g_hwndPost, g_uMsgNotify, wParam, lParam);
}
return (HOOKPROC)lResult;
}
關鍵點:必須呼叫 `CallNextHookEx` 維持鉤子鏈;使用 `PostMessage` 非同步通知避免阻塞系統輸入執行緒;`wParam` 為虛擬鍵碼,`lParam` 包含掃描碼、擴充鍵標記、上下文碼等。
BOOL WINAPI SetKeyboardHook(HWND hWndPost, UINT Msg) {
if (g_hhook != NULL) return FALSE; // 已安裝
g_hwndPost = hWndPost;
g_uMsgNotify = Msg;
Sleep(0); // 讓出時間片,確保執行緒狀態穩定
// 注意:原始碼有筆誤 g_hLogHook 應為 g_hhook
HHOOK hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardHook_HookProc, g_hinstDll, 0);
// 使用 InterlockedExchange 確保多執行緒下原子性寫入
InterlockedExchange((PLONG)&g_hhook, (LONG)hhook);
return (g_hhook != NULL);
}
BOOL WINAPI ReleaseKeyboardHook() {
BOOL fOK = TRUE;
if (g_hhook != NULL) {
fOK = UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
}
return fOK;
}
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
g_hinstDll = hinstDll; // 保存實例控制代碼供 SetWindowsHookEx 使用
break;
case DLL_PROCESS_DETACH:
// 可在此自動清理鉤子 (若主程式未正確呼叫 Release)
break;
}
return TRUE;
}
// 類型定義
typedef BOOL (WINAPI *PFN_SETKEYBOARDHOOK)(HWND, UINT);
typedef BOOL (WINAPI *PFN_RELEASEKEYBOARDHOOK)();
HMODULE hDll = LoadLibrary("KeyHook.dll");
if (hDll) {
auto SetHook = (PFN_SETKEYBOARDHOOK)GetProcAddress(hDll, "SetKeyboardHook");
auto ReleaseHook = (PFN_RELEASEKEYBOARDHOOK)GetProcAddress(hDll, "ReleaseKeyboardHook");
if (SetHook && SetHook(this->Handle, WM_USER + 1)) {
// 鉤子安裝成功
}
// 視窗程序處理 WM_USER+1
// wParam = 虛擬鍵碼, lParam = 鍵盤旗標
// 結束時
if (ReleaseHook) ReleaseHook();
FreeLibrary(hDll);
}
| 鉤子類型 | 用途 | 注入範圍 | 效能影響 |
|---|---|---|---|
| WH_KEYBOARD | 鍵盤訊息 (WM_KEYDOWN/UP) | 所有執行緒 | 中等 |
| WH_KEYBOARD_LL | 低階鍵盤 (原始輸入) | 僅安裝執行緒 | 低 (不注入) |
| WH_JOURNALRECORD | 記錄輸入流 | 系統全域 | 高 (易導致輸入延遲) |
| WH_JOURNALPLAYBACK | 播放輸入流 | 系統全域 | 高 |
建議:現代應用優先使用 `WH_KEYBOARD_LL` (LowLevel) ,無需 DLL、不注入進程、相容性更佳、不易觸發防毒軟體誤報。
// 使用 RegisterWindowMessage 避免訊息 ID 衝突
static UINT g_uMsgHookNotify = RegisterWindowMessage("MyApp_KeyboardHook_Notify");
// 改用 SendMessageTimeout 避免目標視窗無回應導致阻塞
SendMessageTimeout(g_hwndPost, g_uMsgNotify, wParam, lParam, SMTO_ABORTIFHUNG, 100, NULL);
C++ Builder 開發鍵盤鉤子 DLL 是經典技術,核心在於 `SetWindowsHookEx` 與 DLL 注入機制。現代開發建議轉向 `WH_KEYBOARD_LL` 低階鉤子,簡化部署、提升相容性、降低安全風險。無論採用何種方案,嚴謹的資源管理、錯誤處理、跨架構測試與代碼簽章都是生產環境部署的必要條件。