程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 使用鉤子函數[4] - 鉤子鏈和CallNextHookEx的返回值

使用鉤子函數[4] - 鉤子鏈和CallNextHookEx的返回值

編輯:Delphi

SetWindowsHookEx 函數的第一個參數表示鉤子類型, 共有 14 種選擇, 前面我們已經用過兩種:

WH_KEYBOARD、WH_MOUSE.

系統會為每一種類型的鉤子建立一個表(那就是 14 個表), 譬如某個應用程序啟動了鍵盤鉤子, 我們自己的程序也啟動了鍵盤鉤子, 同樣是鍵盤鉤子就會進入同一個表. 這個表(可能不止一個, 可能還會有鼠標鉤子等等)就是傳說中的"鉤子鏈".

假如某個鉤子鏈中共進來了三個鉤子(譬如是: 鉤子A、鉤子B、鉤子C 依次進來), 最後進來的 "鉤子C" 會先執行.

是不是先進後出? 我覺得應該說成: 後進先出! 這有區別嗎? 有! 因為先進來的不一定出得來.

最後進了的鉤子會最先得到執行, 先前進來的鉤子(鉤子A、鉤子B)能不能得到執行那還得兩說, 這得有正在執行的 "鉤子C" 說了算.

如果 "鉤子C" 的函數中包含了 CallNextHookEx 語句, 那麼 "鉤子A、鉤子B" 就有可能得以天日; 不然就只有等著相應的

UnhookWindowsHookEx 來把它們帶走(我想起趙本山的小品...).

這時你也許會想到: 這樣太好了, 我以後就不加 CallNextHookEx , 只讓自己的鉤子"橫行"; 但如果是你的鉤子先進去的呢?

所以 Windows 建議: 鉤子函數要調用 CallNextHookEx, 並把它的返回值當作鉤子函數自己的返回值.

CallNextHookEx 同時要給鉤子鏈中的下一個(或許應該叫上一個)鉤子傳遞參數(譬如在鍵盤消息中按了哪個鍵). 一個鍵盤鉤子和鼠標鉤子的參數一樣嗎? 當然不一樣, 所以它們也不在一個 "鏈" 中啊; 同一個鏈中的鉤子的類型肯定是一樣的.

再聊聊鉤子函數的返回值:

在這之前, 鉤子函數的返回值, 我們都是遵循 Windows 的慣例, 返回了 CallNextHookEx 的返回值.

如果 CallNextHookEx 成功, 它會返回下一個鉤子的返回值, 是個連環套;

如果 CallNextHookEx 失敗, 會返回 0, 這樣鉤子鏈也就斷了, 只有當前鉤子還在執行任務.

不同類型的鉤子函數的返回值是不同的, 對鍵盤鉤子來講如果返回一個非 0 的值, 表示它處理完以後就把消息給消滅了.

換句話說:

如果給鍵盤的鉤子函數 Result := 0; 說明消息被鉤子攔截並處理後就給 "放" 了;

如果給鍵盤的鉤子函數 Result := 1; 說明消息被鉤子攔截並處理後又給 "殺" 了.

在下面的例子中, 我們干脆不使用 CallNextHookEx (反正暫時就我一個鉤子), 直接給返回值!

這是接下來例子的演示動畫:

動畫中, 我在三種狀態下分別給 Memo 輸入了字母 a

當沒啟動鉤子時, Memo 是可以正常輸入的;

當鉤子函數返回 0, 鉤上以後, 先執行了鉤子函數的功能(返回鍵值), 字母 a 也能輸入成功;

當鉤子函數返回非 0 值(譬如1), 鉤上以後, 就只執行了鉤子函數的功能(返回鍵值), 可憐的 Memo 不知道發生了什麼.

但這裡又有了新問題: 鉤子函數返回鍵值時怎麼...不是一個?

先提醒: 在前面用 Beep 測試時, 你有沒有發現那個聲音也不只一次, 這是一個道理.

因為按一次鍵就會發出兩個消息: WM_KEYDOWN、WM_KEYUP, 我們沒有指定攔截哪個, 就都攔截了.

那怎麼區分這兩個消息呢? 秘密在鍵盤鉤子函數的第三個參數 lParam 裡面, 等下一個話題再研究吧.

//示例代碼:
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;

{鉤子函數聲明}
function MyKeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

var
Form1: TForm1;

implementation

{$R *.dfm}

var
hook: HHOOK;

function MyKeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
Form1.Memo1.Lines.Add(IntToStr(wParam)); {參數二是鍵值}
Result := 0; {分別測試返回 0 或非 0 這兩種情況}
end;

{派出鉤子}
procedure TForm1.Button1Click(Sender: TObject);
begin
hook := SetWindowsHookEx(WH_KEYBOARD, MyKeyHook, HInstance, GetCurrentThreadId);
Memo1.Clear;
Text := '鉤子啟動';
end;

{收回鉤子}
procedure TForm1.Button2Click(Sender: TObject);
begin
UnhookWindowsHookEx(hook);
Text := '鉤子關閉';
end;

{如果忘了收回鉤子...}
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hook<>0 then UnhookWindowsHookEx(hook);
end;

end.

小秘密: 發現沒有, 這次在 SetWindowsHookEx 時, 第二參數(函數地址), 沒有使用 @、也沒有用 Addr, 怎麼也行呢?

因為函數名本身就是個地址.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved