| 參數 | 線程級鉤子設定 | 說明 |
idHook | WH_KEYBOARD (2) | 鍵盤訊息鉤子 |
lpfn | @KeyHook 或 Addr(KeyHook) | 取得函數進入點位址 |
hmod | HInstance 或 GetModuleHandle(nil) | 當前模組 (EXE/DLL) 句柄 |
dwThreadId | GetCurrentThreadId | **只關聯當前線程**,0 才是全域 |
鍵盤鉤子回調函數標準原型
function KeyHook(
nCode: Integer; // 處理代碼 (HC_ACTION=0 表示正常處理)
wParam: WPARAM; // 虛擬鍵碼
lParam: LPARAM // 鍵盤訊息詳細資訊 (掃描碼、擴展鍵旗標、轉換狀態等)
): LRESULT; stdcall; // **強制 stdcall**
為什麼必須 stdcall? 回調函數由**系統**呼叫,非應用程式主動呼叫。Windows API 規範所有回調均為 stdcall (參數由被呼叫者清理堆疊,右至左壓棧),確保跨模組/跨語言二進位相容。
完整可執行範例:攔截字母 A 發聲
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
end;
{ 介面區聲明:指定 stdcall }
function KeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
Form1: TForm1;
implementation
{$R *.DFM}
var
hook: HHOOK;
{ 實作鍵盤鉤子回調 }
function KeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
if (wParam = 65) then // 虛擬鍵碼 65 = 'A'
Beep; // 發出蜂鳴
Result := CallNextHookEx(hook, nCode, wParam, lParam); // 傳遞鏈
end;
{ 視窗建立時安裝線程級鍵盤鉤子 }
procedure TForm1.FormCreate(Sender: TObject);
begin
hook := SetWindowsHookEx(WH_KEYBOARD, @KeyHook, HInstance, GetCurrentThreadId);
end;
{ 視窗銷毀時移除鉤子 }
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(hook);
end;
end.
關鍵細節補充
- 介面區聲明 vs 直接實作:
- 介面區聲明 + 實作區實作:編譯器自動處理呼叫慣例。
- 直接實作區實作:**必須手寫
stdcall**,否則預設 register 導致堆疊破壞崩潰。
- 參數取址:
@KeyHook、Addr(KeyHook)、直接 KeyHook (函數名即位址) 效果相同。
- 模組句柄:線程級鉤子
hmod 可為 0 (系統自動用當前模組),但明確給 HInstance 更清晰。
- 攔截條件:範例僅攔截
wParam=65,實務中常結合 lParam 判斷按下/釋放、擴展鍵、Alt 鍵狀態。
lParam 位元結構 (鍵盤訊息詳細資訊)
Bit 0-15: 掃描碼
Bit 16-23: 擴展鍵旗標 (1=擴展鍵如右 Ctrl/Alt)
Bit 24: 保留
Bit 25-28: 保留
Bit 29: 內容代碼 (1=Alt 鍵按下時的字元鍵)
Bit 30: 前次鍵狀態 (1=按下前已按住)
Bit 31: 轉換狀態 (0=按下 WM_KEYDOWN, 1=釋放 WM_KEYUP) **關鍵區分依據**
常見問題排查
| 現象 | 原因 | 解決 |
| 程式啟動即崩潰 | 回調函數缺少 stdcall | 聲明/實作加上 stdcall |
| 鉤子不觸發 | idHook 錯誤或線程 ID 錯誤 | 確認 WH_KEYBOARD=2, GetCurrentThreadId |
| 系統按鍵失效 | 返回非 0 攔截所有鍵 | 僅目標鍵返回非 0,其餘返回 CallNextHookEx |
| Beep 重複兩次 | KeyDown + KeyUp 皆觸發 | 檢查 lParam Bit 31 區分 |
下一步學習路徑
- 掌握
lParam 位元解析,精確區分按下/釋放、控制鍵組合。
- 進階至全域鉤子 (需 DLL,
WH_KEYBOARD_LL=13)。
- 實作熱鍵系統、按鍵記錄器、遊戲防外掛偵測。
線程級鉤子雖範圍有限,但無需 DLL、除錯容易、適合單一應用程式內部訊息監控與熱鍵處理,是鉤子技術入門的最佳起點。
- 上一篇文章: Delphi 鉤子開發實戰 (3):建立全域滑鼠鉤子 DLL 完整流程與動態載入技巧
- 下一篇文章: Delphi 鉤子開發實戰 (1):核心概念、三大 API 與最簡線程級鍵盤鉤子入門
Copyright ©
程式碼之家 All Rights Reserved