1. 钩子函数 :
如果要设置系统级钩子, 钩子函数必须在 DLL 中.
1. SetWindowsHookEx - 设置钩子
2. UnhookWindowsHookEx - 卸掉钩子
UnhookWindowsHookEx(
hhk: HHOOK {钩子句柄}
): BOOL;
{True/False}
3. CallNextHookEx - 调用下一个钩子
CallNextHookEx(
hhk: HHOOK;
{当前钩子的句柄}
nCode: Integer;
{钩子代码; 就是给下一个钩子要交待的}
wParam: WPARAM;
{要传递的参数; 由钩子类型决定是什么参数}
lParam: LPARAM
{要传递的参数; 由钩子类型决定是什么参数}
): LRESULT;
{会返回下一个钩子执行后的返回值; 0 表示失败}
钩子回调函数之 MsgFilterProc 钩子回调函数之 JournalRecordProc
钩子回调函数之 JournalPlaybackProc 钩子回调函数之 GetMsgProc
钩子回调函数之 CBTProc
目前对钩子的理解:
譬如我们用鼠标在某个窗口上双击了一次, 或者给某个窗口输入了一个字母 A;
首先发现这些事件的不是窗口, 而是系统!
然后系统告诉窗口: 喂! 你让人点了, 并且是连续点了两鼠标, 你准备怎么办?
或者是系统告诉窗口: 喂! 有人向你家里扔砖头了, 不信你看看, 那块砖头是 A.
这时窗口的对有些事件会忽略、对有些事件会做出反应:
譬如, 可能对鼠标单击事件忽略, 窗口想: 你单击我不要紧, 累死你我不负责;
但一旦谁要双击我, 我会马上行动, 给你点颜色瞧瞧!
这里窗口准备要采取的行动, 就是我们提前写好的事件.
用 Windows 的话说, 窗口的事件就是系统发送给窗口的消息; 窗口要采取的行动(事件代码)就是窗口的回调函数.
但是! 往往隔墙有耳. 系统要通知给窗口的"话"(消息), 可能会被另一个家伙(譬如是一个贼)提前听到!
有可能这个贼就是专门在这等情报的, 贼知道后, 往往在窗口知道以前就采取了行动!
并且这个贼对不同的消息会采取不同的行动方案, 它的行动方案一般也是早就准备好的;
当然这个贼也不是对什么消息都感兴趣, 对不感兴趣的消息也就无须制定相应的行动方案.
总结: 这个"贼"就是我们要设置的钩子; "贼"的"行动方案"就是钩子函数, 或者叫钩子的回调函数.
钩子分两种, 一种是系统级的全局钩子; 一种是线程级的钩子.
全局钩子函数需要定义在 DLL 中, 从线程级的钩子开始比较简单.
其实钩子函数就三个:
设置钩子: SetWindowsHookEx
释放钩子: UnhookWindowsHookEx
继续钩子: CallNextHookEx
在线程级的钩子中经常用到 GetCurrentThreadID 函数来获取当前线程的 ID.
下面例子中设定了一个线程级的键盘钩子, 专门拦截字母 A.
- <span style="font-size:18px;">{声明键盘钩子回调函数; 其参数传递方式要用 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;
-
- </span>
先看看 SetWindowsHookEx 的声明:
SetWindowsHookEx(
idHook: Integer; {钩子类型}
lpfn: TFNHookProc; {函数指针}
hmod: HINST; {包含钩子函数的模块(EXE、DLL)的句柄}
dwThreadId: DWORD {关联的线程}
): HHOOK;
第一个参数非常麻烦, 从后面说:
参数四 dwThreadId : 在设置全局钩子时这个参数一般是 0, 表示关联所有线程; 本例是线程级的钩子, 所以是
GetCurrentThreadId.
参数三 hmod: 是模块实例的句柄, 在 EXE 和 DLL 中都可以用 HInstance 得到当前实例的句柄; 直接用 API 也可以:
GetModuleHandle(nil).
参数二 lpfn: 是钩子函数的指针, 用 @ 和 Addr 函数都可以得到函数指针; 这里的关键是那个钩子函数:
首先不同的钩子类型对应着不同的钩子函数结构, Win32 共有 14 种钩子类型, 这是 详细注释;
本例用的是键盘钩子, 键盘钩子的回调函数的参数结构在 这里, 我们定义的函数名无所谓, 参数必须按照Windows的规定来.
还有, 这个回调函数的调用惯例必须是: stdcall; 我们在上例中是先在接口区声明, 如果不要声明直接实现, 也不能忘了这个 stdcall.
根据以上说明, 做如下修改:
SetWindowsHookEx 的参数有变通;
并且取消了钩子函数在接口区的声明, 是直接实现的;
取消了拦截条件, 现在只要是键盘消息全都拦截.
|