首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi 鉤子開發實戰 (4):鉤子鏈機制、CallNextHookEx 返回值與攔截控制

     編輯:Delphi

深入理解 Windows 鉤子鏈運作機制,是掌握鉤子開發的關鍵。本文詳細解析鉤子鏈的「後進先出」執行順序、CallNextHookEx 的傳遞責任、以及鉤子函數返回值對訊息攔截 (吞噬/放行) 的決定性影響。

鉤子鏈核心概念

  • 系統為每種鉤子類型 (共 14 種) 維護一條獨立鏈。
  • 安裝鉤子 (SetWindowsHookEx) 將新鉤子**插入鏈頭**。
  • 觸發事件時,**鏈頭最新安裝的鉤子最先執行** (LIFO: Last In, First Out)。
  • 當前鉤子**必須呼叫 CallNextHookEx** 將控制權傳給鏈中下一個鉤子,否則鏈中斷,後續鉤子永不執行。
  • UnhookWindowsHookEx 從鏈移除指定鉤子,不影響其他鉤子。

CallNextHookEx 參數傳遞責任

CallNextHookEx(hook, nCode, wParam, lParam);
  • 原封不動將系統傳入的參數傳給下一個鉤子。
  • 修改參數屬於進階/危險操作 (如修改滑鼠座標、按鍵碼)。
  • 返回值:下一個鉤子的返回值 (層層回傳),最終返回給系統。

鉤子函數返回值的攔截語意 (以鍵盤鉤子 WH_KEYBOARD 為例)

關鍵口訣:鍵盤/滑鼠鉤子返回 0 = 放行,非 0 = 攔截銷毀。

驗證範例:鍵盤鉤子攔截控制

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hook: HHOOK;

{ 鍵盤鉤子回調:記錄按鍵碼並根據返回值決定攔截 }
function MyKeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Form1.Memo1.Lines.Add(IntToStr(wParam));  // 記錄虛擬鍵碼
  Result := 0;  // 修改此處測試:0=放行, 1=攔截
end;

{ 安裝線程級鍵盤鉤子 }
procedure TForm1.Button1Click(Sender: TObject);
begin
  hook := SetWindowsHookEx(WH_KEYBOARD, @MyKeyHook, HInstance, GetCurrentThreadId);
  Memo1.Clear;
  Caption := '鉤子啟動 (返回 0: 放行)';
end;

{ 移除鉤子 }
procedure TForm1.Button2Click(Sender: TObject);
begin
  UnhookWindowsHookEx(hook);
  Caption := '鉤子關閉';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if hook <> 0 then UnhookWindowsHookEx(hook);
end;

end.

實測現象對照

三種狀態測試:無鉤子/返回0/返回1 輸入字母 a 的結果

  1. 無鉤子:Memo 正常輸入 'a'。
  2. 鉤子返回 0:Memo 記錄鍵碼 (65),**且**輸入框正常出現 'a' (放行)。
  3. 鉤子返回 1:Memo 記錄鍵碼 (65),**但**輸入框**無任何字元** (攔截銷毀)。

隱藏細節:按鍵雙重訊息

按一次鍵產生兩個訊息:WM_KEYDOWN (按下) + WM_KEYUP (釋放),鉤子都會攔截,故 Memo 記錄兩次鍵碼。區分方法在 lParam 高位旗標 (第 31 位: 0=Down, 1=Up),下篇再詳述。

關於函數指標傳遞的小秘密

SetWindowsHookEx(WH_KEYBOARD, @MyKeyHook, ...);  // 用 @ 取址
SetWindowsHookEx(WH_KEYBOARD, MyKeyHook, ...);   // 直接用函數名也可!

原因:Delphi 中函數名稱本身即代表其進入點位址,@ 與 Addr 僅為語法糖,編譯器自動處理。

最佳實務建議

  1. 永遠呼叫 CallNextHookEx:除非明確要攔截訊息 (如熱鍵處理),否則務必傳遞鏈,維護系統穩定。
  2. 返回值傳遞:Result := CallNextHookEx(...) 將下游返回值原樣回傳,符合 Windows 規範。
  3. 攔截慎重:全域鍵盤鉤子返回非 0 會導致該鍵全系統失效,極度影響使用者體驗,僅限極特定場景 (如密碼輸入保護、遊戲防外掛)。
  4. 執行緒安全:全域鉤子回調在系統進程執行,避免 VCL/UI 操作,僅做極簡邏輯 (記錄、發訊息)。

鉤子類型對照表 (節錄)

返回值語意結果
0「我處理完了,請繼續傳遞」訊息**放行**,目標視窗會收到
非 0 (如 1)「我處理完了,**攔截結束**」訊息**吞噬**,目標視窗**不會**收到

掌握鉤子鏈機制與返回值語意,是從「能用鉤子」進階到「精準控制鉤子」的分水嶺。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
常數值層級用途
WH_KEYBOARD2系統/線程鍵盤訊息
WH_MOUSE7系統/線程滑鼠訊息
WH_KEYBOARD_LL13系統低層級鍵盤 (不需 DLL)
WH_MOUSE_LL14系統低層級滑鼠 (不需 DLL)
WH_CBT5系統/線程視窗建立/啟動/銷毀
WH_SHELL10系統/線程Shell 事件