首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

C++ Builder 鍵盤鉤子 DLL 開發全攻略:從入門到實戰

     編輯:VC 教學

鍵盤鉤子 DLL 架構優勢

全域鍵盤鉤子 (WH_KEYBOARD) 必須置於 DLL 中,才能注入所有目標進程位址空間。C++ Builder 建立 DLL 專案,導出安裝/移除函數,主程式動態載入調用,實現模組化與職責分離。

完整 DLL 原始碼解析

1. 全域狀態管理

HINSTANCE g_hinstDll = NULL;   // DLL 模組實例
HHOOK g_hhook = NULL;          // 鉤子控制代碼
HWND g_hwndPost = NULL;        // 接收通知的視窗控制代碼
UINT g_uMsgNotify = WM_USER;   // 自訂通知訊息

2. 鉤子回呼函數

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` 包含掃描碼、擴充鍵標記、上下文碼等。

3. 導出函數:安裝鉤子

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);
}

4. 導出函數:移除鉤子

BOOL WINAPI ReleaseKeyboardHook() {
    BOOL fOK = TRUE;
    if (g_hhook != NULL) {
        fOK = UnhookWindowsHookEx(g_hhook);
        g_hhook = NULL;
    }
    return fOK;
}

5. DLL 進入點

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);
}

進階議題與最佳實務

1. 鉤子類型選擇對照

鉤子類型用途注入範圍效能影響
WH_KEYBOARD鍵盤訊息 (WM_KEYDOWN/UP)所有執行緒中等
WH_KEYBOARD_LL低階鍵盤 (原始輸入)僅安裝執行緒低 (不注入)
WH_JOURNALRECORD記錄輸入流系統全域高 (易導致輸入延遲)
WH_JOURNALPLAYBACK播放輸入流系統全域高

建議:現代應用優先使用 `WH_KEYBOARD_LL` (LowLevel) ,無需 DLL、不注入進程、相容性更佳、不易觸發防毒軟體誤報。

2. 64 位元相容性

  • C++ Builder 64 位元編譯器產生的 DLL 僅能注入 64 位元進程
  • 需提供 32/64 位元兩版 DLL,主程式依據自身位元寬度載入對應版本
  • 或改用 `WH_KEYBOARD_LL` 完全避免位元寬度問題

3. 安全性與簽章

  • 鉤子 DLL 易被視為惡意軟體特徵,**必須申請 EV 代碼簽章憑證** 簽署
  • 在 `DllMain` 中最小化初始化邏輯,避免載入順序死結
  • 提供明確的安裝/移除介面,避免殘留鉤子導致系統不穩

4. 訊息傳遞優化

// 使用 RegisterWindowMessage 避免訊息 ID 衝突
static UINT g_uMsgHookNotify = RegisterWindowMessage("MyApp_KeyboardHook_Notify");

// 改用 SendMessageTimeout 避免目標視窗無回應導致阻塞
SendMessageTimeout(g_hwndPost, g_uMsgNotify, wParam, lParam, SMTO_ABORTIFHUNG, 100, NULL);

除錯技巧

  • 使用 DebugView 捕捉 `OutputDebugString` 輸出
  • Process Monitor 追蹤 `LoadLibrary`/`SetWindowsHookEx` 呼叫
  • 在鉤子函數開頭設置斷點 (需附加至目標進程)
  • 檢查 `GetLastError()`:`ERROR_HOOK_NEEDS_HMOD` (需 DLL)、`ERROR_ACCESS_DENIED` (權限/位元寬度不符)

結語

C++ Builder 開發鍵盤鉤子 DLL 是經典技術,核心在於 `SetWindowsHookEx` 與 DLL 注入機制。現代開發建議轉向 `WH_KEYBOARD_LL` 低階鉤子,簡化部署、提升相容性、降低安全風險。無論採用何種方案,嚴謹的資源管理、錯誤處理、跨架構測試與代碼簽章都是生產環境部署的必要條件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved