MouseProc 是 Windows 滑鼠鉤子 (WH_MOUSE 或 WH_MOUSE_LL) 的核心回調函數。當應用程式安裝滑鼠鉤子後,系統會在每次滑鼠事件 (移動、按鍵、滾輪、雙擊) 發生時呼叫此函數,讓開發者能夠攔截、監控、記錄或修改滑鼠訊息。廣泛應用於宏錄製、遊戲輔助、無障礙工具、UI 測試自動化等場景。
LRESULT CALLBACK MouseProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
| 參數 | 類型 | 說明 |
|---|---|---|
| nCode | int | 鉤子處理代碼:HC_ACTION (0):正常處理,wParam/lParam 有效HC_NOREMOVE (3):訊息已被 PeekMessage 移除但未從佇列刪除若 nCode < 0:不得處理,必須呼叫 CallNextHookEx |
| wParam | WPARAM | 滑鼠訊息識別碼:WM_MOUSEMOVE、WM_LBUTTONDOWN/UP、WM_RBUTTONDOWN/UP、WM_MBUTTONDOWN/UPWM_MOUSEWHEEL、WM_MOUSEHWHEEL、WM_LBUTTONDBLCLK 等 |
| lParam | LPARAM | 指向結構的指標: WH_MOUSE:指向 MOUSEHOOKSTRUCTWH_MOUSE_LL:指向 MSLLHOOKSTRUCT (含時間戳、注入標誌) |
// 標準滑鼠鉤子 (WH_MOUSE)
typedef struct tagMOUSEHOOKSTRUCT {
POINT pt; // 螢幕座標 (x, y)
HWND hwnd; // 目標視窗控制代碼
UINT wHitTestCode; // Hit-test 代碼 (HTCLIENT, HTCAPTION 等)
ULONG_PTR dwExtraInfo; // 額外資訊
} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;
// 低階滑鼠鉤子 (WH_MOUSE_LL) - 更豐富資訊
typedef struct tagMSLLHOOKSTRUCT {
POINT pt; // 螢幕座標
DWORD mouseData; // 滾輪增量 (WHEEL_DELTA=120) 或 XButton 資訊
DWORD flags; // LLMHF_INJECTED (1) = 模擬輸入
DWORD time; // 訊息時間戳 (GetMessageTime)
ULONG_PTR dwExtraInfo;
} MSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT;
nCode < 0:返回 CallNextHookEx(hHook, nCode, wParam, lParam)CallNextHookEx(...)| 特性 | WH_MOUSE (標準) | WH_MOUSE_LL (低階) |
|---|---|---|
| 安裝需求 | 需 DLL 注入 (SetWindowsHookEx 第4參數=0 時) | 同進程即可,無需 DLL (HInstance 可為 NULL) |
| 攔截範圍 | 特定執行緒 (第4參數=ThreadId) 或全系統 (0) | 全系統所有滑鼠輸入 |
| 座標系統 | 螢幕座標 | 螢幕座標 |
| 時間資訊 | 無 | 有 (time 欄位,精確到毫秒) |
| 注入偵測 | 無 | flags & LLMHF_INJECTED 偵測模擬輸入 |
| 效能影響 | 高 (跨進程上下文切換) | 低 (同進程回調) |
| 超時保護 | 無 | 有 (LowLevelHooksTimeout, 預設 300ms) |
| 適用場景 | 特定應用內部滑鼠監控 | 全域熱鍵、宏錄製、螢幕錄製、防作弊 |
unit MouseHookUnit;
interface
uses Windows, Messages, Classes, SysUtils;
type
TMouseEventType = (meMove, meLDown, meLUp, meRDown, meRUp,
meMDown, meMUp, meWheel, meHWheel, meLDouble, meRDouble, meMDouble);
TMouseEvent = record
EventType: TMouseEventType;
X, Y: Integer; // 螢幕座標
WheelDelta: Integer; // 滾輪增量
Injected: Boolean; // 是否為模擬輸入
TimeStamp: DWORD; // 系統時間戳
end;
TMouseEventProc = procedure(const Event: TMouseEvent) of object;
TLowLevelMouseHook = class
private
FHook: HHOOK;
FOnMouseEvent: TMouseEventProc;
class var FInstance: TLowLevelMouseHook;
class function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
function GetEventType(Msg: UINT): TMouseEventType;
public
constructor Create;
destructor Destroy; override;
procedure Install;
procedure Uninstall;
property OnMouseEvent: TMouseEventProc read FOnMouseEvent write FOnMouseEvent;
end;
implementation
{ TLowLevelMouseHook }
constructor TLowLevelMouseHook.Create;
begin
inherited;
FInstance := Self;
end;
destructor TLowLevelMouseHook.Destroy;
begin
Uninstall;
FInstance := nil;
inherited;
end;
class function TLowLevelMouseHook.HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
HookStruct: PMSLLHOOKSTRUCT;
Event: TMouseEvent;
begin
Result := 0;
if (nCode = HC_ACTION) and Assigned(FInstance) and Assigned(FInstance.FOnMouseEvent) then
begin
HookStruct := PMSLLHOOKSTRUCT(lParam);
Event.X := HookStruct.pt.x;
Event.Y := HookStruct.pt.y;
Event.WheelDelta := 0;
Event.Injected := (HookStruct.flags and LLMHF_INJECTED) <> 0;
Event.TimeStamp := HookStruct.time;
Event.EventType := FInstance.GetEventType(wParam);
if wParam = WM_MOUSEWHEEL then
Event.WheelDelta := SmallInt(HookStruct.mouseData shr 16) // HIWORD
else if wParam = WM_MOUSEHWHEEL then
Event.WheelDelta := SmallInt(HookStruct.mouseData shr 16);
// 同步呼叫事件處理 (注意:回調中避免耗時操作!)
FInstance.FOnMouseEvent(Event);
end;
Result := CallNextHookEx(FInstance.FHook, nCode, wParam, lParam);
end;
function TLowLevelMouseHook.GetEventType(Msg: UINT): TMouseEventType;
begin
case Msg of
WM_MOUSEMOVE: Result := meMove;
WM_LBUTTONDOWN: Result := meLDown;
WM_LBUTTONUP: Result := meLUp;
WM_RBUTTONDOWN: Result := meRDown;
WM_RBUTTONUP: Result := meRUp;
WM_MBUTTONDOWN: Result := meMDown;
WM_MBUTTONUP: Result := meMUp;
WM_MOUSEWHEEL: Result := meWheel;
WM_MOUSEHWHEEL: Result := meHWheel;
WM_LBUTTONDBLCLK: Result := meLDouble;
WM_RBUTTONDBLCLK: Result := meRDouble;
WM_MBUTTONDBLCLK: Result := meMDouble;
else Result := meMove;
end;
end;
procedure TLowLevelMouseHook.Install;
begin
if FHook = 0 then
FHook := SetWindowsHookEx(WH_MOUSE_LL, @HookProc, HInstance, 0);
end;
procedure TLowLevelMouseHook.Uninstall;
begin
if FHook <> 0 then
begin
UnhookWindowsHookEx(FHook);
FHook := 0;
end;
end;
end.
unit MainUnit;
interface
uses Windows, Forms, StdCtrls, Controls, Classes, MouseHookUnit;
type
TForm1 = class(TForm)
btnInstall: TButton;
btnUninstall: TButton;
memoLog: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnInstallClick(Sender: TObject);
procedure btnUninstallClick(Sender: TObject);
private
MouseHook: TLowLevelMouseHook;
procedure OnMouseEvent(const Event: TMouseEvent);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
MouseHook := TLowLevelMouseHook.Create;
MouseHook.OnMouseEvent := OnMouseEvent;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MouseHook.Free;
end;
procedure TForm1.btnInstallClick(Sender: TObject);
begin
MouseHook.Install;
memoLog.Lines.Add('[Hook Installed]');
end;
procedure TForm1.btnUninstallClick(Sender: TObject);
begin
MouseHook.Uninstall;
memoLog.Lines.Add('[Hook Uninstalled]');
end;
procedure TForm1.OnMouseEvent(const Event: TMouseEvent);
const
EventNames: array[TMouseEventType] of string = (
'Move', 'LDown', 'LUp', 'RDown', 'RUp',
'MDown', 'MUp', 'Wheel', 'HWheel', 'LDouble', 'RDouble', 'MDouble'
);
var
Line: string;
begin
// 限制日誌行數避免記憶體耗盡
if memoLog.Lines.Count > 1000 then
memoLog.Lines.Delete(0);
Line := Format('[%s] X=%d Y=%d Injected=%s',
[EventNames[Event.EventType], Event.X, Event.Y,
BoolToStr(Event.Injected, True)]);
if Event.EventType in [meWheel, meHWheel] then
Line := Line + Format(' Delta=%d', [Event.WheelDelta]);
memoLog.Lines.Add(Line);
end;
end.
type
TGesturePoint = record X, Y: Integer; end;
TGestureEngine = class
private
FPoints: array of TGesturePoint;
FMinPoints: Integer;
procedure AnalyzeGesture;
public
constructor Create;
procedure AddPoint(X, Y: Integer);
procedure Clear;
property OnGestureRecognized: TNotifyEvent;
end;
procedure TGestureEngine.AddPoint(X, Y: Integer);
begin
SetLength(FPoints, Length(FPoints) + 1);
FPoints[High(FPoints)] := TGesturePoint.Create(X, Y);
// 簡單策略:超過 20 點且移動距離夠大才分析
if Length(FPoints) >= 20 then
AnalyzeGesture;
end;
procedure TGestureEngine.AnalyzeGesture;
var
StartPt, EndPt: TGesturePoint;
DX, DY: Integer;
Angle: Single;
begin
StartPt := FPoints[0];
EndPt := FPoints[High(FPoints)];
DX := EndPt.X - StartPt.X;
DY := EndPt.Y - StartPt.Y;
// 過濾太小移動
if (Abs(DX) < 50) and (Abs(DY) < 50) then Exit;
Angle := ArcTan2(DY, DX) * 180 / Pi; // -180 到 180
// 簡單 8 方向識別
case Round(Angle / 45) * 45 of
0: ; // 右
45: ; // 右下
90: ; // 下
135: ; // 左下
180, -180: ; // 左
-135: ; // 左上
-90: ; // 上
-45: ; // 右上
end;
Clear; // 重置
if Assigned(OnGestureRecognized) then OnGestureRecognized(Self);
end;
uses Windows;
procedure SimulateMouseClick(X, Y: Integer; RightButton: Boolean = False);
var
Input: TInput;
begin
ZeroMemory(@Input, SizeOf(TInput));
Input._type := INPUT_MOUSE;
Input.mi.dx := X * (65535 div GetSystemMetrics(SM_CXSCREEN));
Input.mi.dy := Y * (65535 div GetSystemMetrics(SM_CYSCREEN));
Input.mi.dwFlags := MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_MOVE;
SendInput(1, Input, SizeOf(TInput));
ZeroMemory(@Input, SizeOf(TInput));
Input._type := INPUT_MOUSE;
if RightButton then
Input.mi.dwFlags := MOUSEEVENTF_RIGHTDOWN
else
Input.mi.dwFlags := MOUSEEVENTF_LEFTDOWN;
SendInput(1, Input, SizeOf(TInput));
ZeroMemory(@Input, SizeOf(TInput));
Input._type := INPUT_MOUSE;
if RightButton then
Input.mi.dwFlags := MOUSEEVENTF_RIGHTUP
else
Input.mi.dwFlags := MOUSEEVENTF_LEFTUP;
SendInput(1, Input, SizeOf(TInput));
end;
// 注意:SendInput 產生的輸入在 WH_MOUSE_LL 中 flags 會帶有 LLMHF_INJECTED
// 可用於區分真實用戶操作 vs 程式模擬
HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout),否則系統會自動移除鉤子。嚴禁在回調中執行:檔案 I/O、MessageBox、資料庫操作、同步等待、複雜計算。PostMessage、TThread.Queue 或執行緒安全佇列。ScreenToClient(TargetWnd, Pt)。UnhookWindowsHookEx、SetWindowsHookEx 覆蓋、直接修改鏈結指標。防禦需定期驗證鉤子完整性。SetWindowsHookEx(WH_MOUSE_LL, ...) - 安裝低階滑鼠鉤子SetWindowsHookEx(WH_MOUSE, ...) - 安裝標準滑鼠鉤子CallNextHookEx - 傳遞給下一個鉤子UnhookWindowsHookEx - 移除鉤子GetAsyncKeyState - 即時查詢按鍵狀態 (配合鉤子判斷修飾鍵)GetCursorPos / SetCursorPos - 獲取/設定滑鼠位置SendInput - 模擬輸入 (推薦)mouse_event - 舊版模擬輸入 (已棄用)WindowFromPoint / ChildWindowFromPoint - 座標轉視窗控制代碼MouseProc 配合 WH_MOUSE_LL 是實現全域滑鼠監控、宏錄製、手勢識別、自動化測試的核心技術。關鍵掌握:低階鉤子無需 DLL、MSLLHOOKSTRUCT 提供豐富資訊、300ms 超時鐵律、LLMHF_INJECTED 偵測模擬輸入。正確處理執行緒切換、資料佇列、座標轉換,即可構建健壯的滑鼠事件處理系統。