延續前兩篇鉤子教學,本文解決兩個關鍵問題:(1) 如何從 EXE 向 DLL 傳遞資料 (如視窗句柄);(2) 如何避免使用 Timer 輪詢改用 Windows 訊息機制主動推送資料。完整示範低層級鍵盤/滑鼠鉤子的雙向通訊架構。
SetHWnd 接收目標視窗句柄,鉤子回調中用 PostMessage 發送自訂訊息 (WM_USER+1) 推送資料。message WM_MyMessage 訊息處理函數,直接在訊息迴圈中接收資料,無需 Timer 輪詢。WH_MOUSE_LL = 14 (低層級),不需 DLL 注入即可全域攔截。library MyHook;
uses
SysUtils, Windows, Messages, Classes;
{$R *.res}
const
WM_MyMessage = WM_USER + 1; // 自訂訊息
var
hook: HHOOK;
info: string;
h: HWND; // 目標視窗句柄
{ 供 EXE 呼叫設定目標視窗句柄 }
function SetHWnd(hwnd: HWND): Boolean; stdcall;
begin
h := hwnd;
Result := True;
end;
{ 低層級滑鼠鉤子回調 }
function MouseHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
case wParam of
WM_MOUSEMOVE: info := '滑鼠移動';
WM_LBUTTONDOWN: info := '左鍵按下';
WM_LBUTTONUP: info := '左鍵釋放';
end;
{ 格式化座標資訊 }
info := Format('%s: %d,%d', [info, PMouseHookStruct(lParam)^.pt.X, PMouseHookStruct(lParam)^.pt.Y]);
{ 關鍵:用 PostMessage 非同步推送資料到目標視窗 }
PostMessage(h, WM_MyMessage, 0, Integer(PChar(info)));
Result := CallNextHookEx(hook, nCode, wParam, lParam);
end;
{ 安裝鉤子 }
function SetHook: Boolean; stdcall;
const
WH_MOUSE_LL = 14; // Delphi 缺少此常數需自定
begin
hook := SetWindowsHookEx(WH_MOUSE_LL, @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',
SetHWnd name 'SetHWnd';
begin
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
const
WM_MyMessage = WM_USER + 1;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure MyMessage(var msg: TMessage); message WM_MyMessage; // 訊息處理
end;
function SetHook: Boolean; stdcall;
function DelHook: Boolean; stdcall;
function SetHWnd(hwnd: HWND): Boolean; stdcall;
var
Form1: TForm1;
implementation
{$R *.dfm}
function SetHook; external 'MyHook.dll';
function DelHook; external 'MyHook.dll';
function SetHWnd; external 'MyHook.dll';
procedure TForm1.Button1Click(Sender: TObject);
begin
SetHook; // 安裝鉤子
SetHWnd(Handle); // 傳遞本視窗句柄給 DLL
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DelHook; // 移除鉤子
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.Caption := '安裝鉤子';
Button2.Caption := '移除鉤子';
FormStyle := fsStayOnTop; // 方便測試保持最上層
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DelHook;
end;
{ 訊息處理函數:直接接收 DLL 推送的資料 }
procedure TForm1.MyMessage(var msg: TMessage);
begin
Caption := PChar(msg.LParam); // 顯示滑鼠資訊於標題列
end;
end.
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 78
ClientWidth = 271
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 48
Top = 32
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object Button2: TButton
Left = 144
Top = 32
Width = 75
Height = 25
Caption = 'Button2'
TabOrder = 1
OnClick = Button2Click
end
end

| 傳統 Timer 輪詢 | 訊息機制 (本範例) |
|---|---|
| 固定間隔檢查 (如 100ms) | 事件驅動,即時觸發 |
| CPU 持續佔用 | 無事件時零負擔 |
| 資料延遲高達間隔時間 | 微秒級延遲 |
| 需共享記憶體/全域變數 | 訊息參數直接傳遞字串指標 |
info 為全域 string,PChar(info) 指標有效直到下次修改。若需傳遞多筆資料,建議用 GlobalAddAtom/GlobalGetAtomName 或共享記憶體。string 為 Unicode,PChar 為 PWideChar,接收端 PChar(msg.LParam) 需對應 WideChar 或用 string(msg.LParam) 自動轉換。WH_MOUSE_LL 回調在系統進程執行,避免耗時操作,否則導致全系統滑鼠延遲。SetWindowsHookEx 失敗回傳 0,需檢查 GetLastError (常見原因:DLL 未簽名、權限不足、系統關鍵進程保護)。OutputDebugString 輸出日誌,配合 DebugView 監控。WH_KEYBOARD_LL = 13 攔截鍵盤。WM_COPYDATA 結構化傳遞複雜資料。SetWindowsHookEx 呼叫偵測惡意鉤子。掌握 DLL 鉤子與訊息機制的雙向通訊模式,是開發系統級監控工具、熱鍵管理器、自動化測試框架的核心基礎。