全域鉤子**必須**置於 DLL 中,因為各 EXE 為獨立進程,唯有 DLL 為系統共享模組才能注入所有進程。本文完整示範從建立 DLL 專案、實作鉤子函數、編譯輸出,到 EXE 靜態/動態兩種載入調用方式的全流程。
SetWindowsHookEx 參數 hMod (模組句柄) 與 dwThreadId=0 (全域) 結合,系統會將 DLL 注入每個符合條件的進程執行回調。library MyHook;
uses
SysUtils, Windows, Messages, Classes;
{$R *.res}
var
hook: HHOOK;
{ 滑鼠鉤子回調:僅攔截左鍵按下發出蜂鳴 }
function MouseHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
if wParam = WM_LBUTTONDOWN then
MessageBeep(0); // 系統預設提示音
Result := CallNextHookEx(hook, nCode, wParam, lParam);
end;
{ 安裝全域滑鼠鉤子 }
function SetHook: Boolean; stdcall;
begin
{ WH_MOUSE=7 全域滑鼠鉤子;第4參數 0 表示全域 }
hook := SetWindowsHookEx(WH_MOUSE, @MouseHook, HInstance, 0);
Result := hook <> 0;
end;
{ 移除鉤子 }
function DelHook: Boolean; stdcall;
begin
Result := UnhookWindowsHookEx(hook);
end;
exports
SetHook name 'SetHook',
DelHook name 'DelHook',
MouseHook name 'MouseHook';
begin
end.
編譯:Ctrl+F9 生成 MyHook.dll 至專案目錄。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
{ DLL 函數聲明:stdcall 必須一致 }
function SetHook: Boolean; stdcall;
function DelHook: Boolean; stdcall;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ 靜態連結:external 指定 DLL 與匯出名稱 }
function SetHook; external 'MyHook.dll' name 'SetHook';
function DelHook; external 'MyHook.dll' name 'DelHook';
procedure TForm1.Button1Click(Sender: TObject);
begin
SetHook; // 安裝鉤子
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DelHook; // 移除鉤子
end;
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TDLLFun = function: Boolean; stdcall; // 統一函數原型
var
h: THandle; // DLL 模組句柄
SetHook, DelHook: TDLLFun; // 函數指標變數
procedure TForm1.Button1Click(Sender: TObject);
begin
h := LoadLibrary('MyHook.dll'); // 載入 DLL
if h <> 0 then
begin
@SetHook := GetProcAddress(h, 'SetHook'); // 取得函數位址
@DelHook := GetProcAddress(h, 'DelHook');
SetHook; // 呼叫
end
else
ShowMessage('DLL 載入失敗');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DelHook;
FreeLibrary(h); // 釋放 DLL
end;
end.
點擊按鈕 1 安裝鉤子 → 在螢幕任意位置點擊滑鼠左鍵 → 聽到「嗶」聲 → 點擊按鈕 2 移除鉤子 → 左鍵無聲。
| 項目 | 靜態載入 | 動態載入 |
|---|---|---|
| 程式碼複雜度 | 簡單 (聲明即用) | 需手動 Load/Free/GetProc |
| 啟動依賴 | DLL 必須存在否則啟動失敗 | 可選擇性載入,DLL 缺失不影響主程式 |
| 版本熱插拔 | 不支援 | 支援 (FreeLibrary 後換新 DLL 再 Load) |
| 除錯便利性 | IDE 可直接進入 DLL 斷點 | 需附加進程除錯 |
MyHook.dll 複製到 EXE 同目錄,或系統路徑 (System32/SysWOW64)。stdcall。滑鼠鉤子 (WH_MOUSE) 回調的 lParam 指向 MOUSEHOOKSTRUCT:
type
PMouseHookStruct = ^TMouseHookStruct;
TMouseHookStruct = record
pt: TPoint; // 螢幕座標
hwnd: HWND; // 目標視窗句柄
wHitTestCode: UINT; // 擊測代碼 (HTCLIENT, HTCAPTION...)
dwExtraInfo: ULONG_PTR;
end;
低層級鉤子 (WH_MOUSE_LL) 則指向 MSLLHOOKSTRUCT,包含時間戳記與注入旗標。
完成此流程即掌握 Delphi 全域鉤子開發的核心骨架,後續擴展鍵盤記錄、熱鍵、滑鼠手勢識別皆建立於此基礎之上。