目前對鉤子的理解:
譬如我們用鼠標在某個窗口上雙擊了一次, 或者給某個窗口輸入了一個字母 A;
首先發現這些事件的不是窗口, 而是系統!
然後系統告訴窗口: 喂! 你讓人點了, 並且是連續點了兩鼠標, 你准備怎麼辦?
或者是系統告訴窗口: 喂! 有人向你家裡扔磚頭了, 不信你看看, 那塊磚頭是 A.
這時窗口的對有些事件會忽略、對有些事件會做出反應:
譬如, 可能對鼠標單擊事件忽略, 窗口想: 你單擊我不要緊, 累死你我不負責;
但一旦誰要雙擊我, 我會馬上行動, 給你點顏色瞧瞧!
這裡窗口准備要采取的行動, 就是我們提前寫好的事件.
用 Windows 的話說, 窗口的事件就是系統發送給窗口的消息; 窗口要采取的行動(事件代碼)就是窗口的回調函數.
但是! 往往隔牆有耳. 系統要通知給窗口的"話"(消息), 可能會被另一個家伙(譬如是一個賊)提前聽到!
有可能這個賊就是專門在這等情報的, 賊知道後, 往往在窗口知道以前就采取了行動!
並且這個賊對不同的消息會采取不同的行動方案, 它的行動方案一般也是早就准備好的;
當然這個賊也不是對什麼消息都感興趣, 對不感興趣的消息也就無須制定相應的行動方案.
總結: 這個"賊"就是我們要設置的鉤子; "賊"的"行動方案"就是鉤子函數, 或者叫鉤子的回調函數.
鉤子分兩種, 一種是系統級的全局鉤子; 一種是線程級的鉤子.
全局鉤子函數需要定義在 DLL 中, 從線程級的鉤子開始比較簡單.
其實鉤子函數就三個:
設置鉤子: SetWindowsHookEx
釋放鉤子: UnhookWindowsHookEx
繼續鉤子: CallNextHookEx
在線程級的鉤子中經常用到 GetCurrentThreadID 函數來獲取當前線程的 ID.
下面例子中設定了一個線程級的鍵盤鉤子, 專門攔截字母 A.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
end;
{聲明鍵盤鉤子回調函數; 其參數傳遞方式要用 API 的 stdcall}
function KeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
Form1: TForm1;
implementation
{$R *.DFM}
var
hook: HHOOK; {定義一個鉤子句柄}
{實現鍵盤鉤子回調函數}
function KeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
if (wParam = 65) then Beep; {每攔截到字母 A 會發聲}
Result := CallNextHookEx(hook, nCode, wParam, lParam);
end;
{設置鍵盤鉤子}
procedure TForm1.FormCreate(Sender: TObject);
begin
hook := SetWindowsHookEx(WH_KEYBOARD, @KeyHook, 0, GetCurrentThreadID);
end;
{釋放鍵盤鉤子}
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(hook);
end;
end.
盡管這個例子已經很簡單了, 但還不足以讓人明白徹底; 下面還得從更簡單的開始.