网页
综合
更新时间 2026-11-15 07:46:20
Delphi之钩子函数(二)-菜鸟编程-鸟儿博客
自动蜘蛛
www.birdol.com
正文约 6000 字
鸟儿博客 教程存档相册关于 订阅 花褪残红青杏小。燕子飞时,绿水人家绕。 你的位置: 鸟儿博客 > 菜鸟编程 > Delphi之钩子函数(二) Delphi之钩子函数(二) 菜鸟编程 十五楼的鸟儿 18年前 (2009-07-04) 42184浏览 0评论 目前对钩子的理解:譬如我们用鼠标在某个窗口上双击了一次, 或者给某个窗口输入了一个字母 A;首先发现这些事件的不是窗口, 而是系统!然后系统告诉窗口: 喂! 你让人点了, 并且是连续点了两鼠标, 你准备怎么办?或者是系统告诉窗口: 喂! 有人向你家里扔砖头了, 不信你看看, 那块砖头是 A.这时窗口的对有些事件会忽略、对有些事件会做出反应:譬如, 可能对鼠标单击事件忽略, 窗口想: 你单击我不要紧, 累死你我不负责;但一旦谁要双击我, 我会马上行动, 给你点颜色瞧瞧!这里窗口准备要采取的行动, 就是我们提前写好的事件.用 Windows 的话说, 窗口的事件就是系统发送给窗口的消息; 窗口要采取的行动(事件代码)就是窗口的回调函数.但是! 往往隔墙有耳. 系统要通知给窗口的"话"(消息), 可能会被另一个家伙(譬如是一个贼)提前听到!有可能这个贼就是专门在这等情报的, 贼知道后, 往往在窗口知道以前就采取了行动!并且这个贼对不同的消息会采取不同的行动方案, 它的行动方案一般也是早就准备好的;当然这个贼也不是对什么消息都感兴趣, 对不感兴趣的消息也就无须制定相应的行动方案.总结: 这个"贼"就是我们要设置的钩子; "贼"的"行动方案"就是钩子函数, 或者叫钩子的回调函数.钩子分两种, 一种是系统级的全局钩子; 一种是线程级的钩子.全局钩子函数需要定义在 DLL 中, 从线程级的钩子开始比较简单.其实钩子函数就三个:设置钩子: SetWindowsHookEx释放钩子: UnhookWindowsHookEx继续钩子: CallNextHookEx在线程级的钩子中经常用到 GetCurrentThreadID 函数来获取当前线程的 ID.下面例子中设定了一个线程级的键盘钩子, 专门拦截字母 A.[code=delphi]unit Unit1;interfaceuses 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.[/code]尽管这个例子已经很简单了, 但还不足以让人明白彻底; 下面还得从更简单的开始. 钩子函数虽然不多, 但其参数复杂, 应该从参数入手才能深入进去.UnhookWindowsHookEx 只需要 SetWindowsHookEx 返回的钩子句柄作参数, 这个简单;先看看 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 的参数有变通;并且取消了钩子函数在接口区的声明, 是直接实现的;取消了拦截条件, 现在只要是键盘消息全都拦截.[code=delphi]unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); end;var Form1: TForm1;implementation{$R *.DFM}var hook: HHOOK; {定义一个钩子句柄}{现在这个钩子函数没有在接口区声明, 这里必须指定参数调用方式: stdcall}function KeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;begin Beep; Result := CallNextHookEx(hook, nCode, wParam, lParam);end;{设置键盘钩子}procedure TForm1.FormCreate(Sender: TObject);begin hook := SetWindowsHookEx(WH_KEYBOARD, Addr(KeyHook), HInstance, GetCurrentThreadId);end;{释放键盘钩子}procedure TForm1.FormDestroy(Sender: TObject);begin UnhookWindowsHookEx(hook);end;end.[/code]钩子函数为什么非得使用 stdcall 调用机制? 因为钩子函数不是被应用程序调用, 而是被系统调用的. 建立一个全局的鼠标钩子:先建立一个 DLL 工程, 自动初始的代码如下(这里可以参考这篇文章中建立dll部分 [delphi之调用外部dll中的函数])[code=delphi]library Project1;uses SysUtils, Classes;{$R *.res}beginend.//把工程保存为 MyHook.dpr, 并实现如下:library MyHook;uses SysUtils, Windows, {钩子函数都来自 Windows 单元} Messages, {消息 WM_LBUTTONDOWN 定义在 Messages 单元} Classes;{$R *.res}var hook: HHOOK; {钩子变量}{钩子函数, 鼠标消息太多(譬如鼠标移动), 必须要有选择, 这里选择了鼠标左键按下}function MouseHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;begin if wParam = WM_LBUTTONDOWN then begin MessageBeep(0); end; Result := CallNextHookEx(hook, nCode, wParam, lParam);end;{建立钩子}function SetHook: Boolean; stdcall;begin hook := SetWindowsHookEx(WH_MOUSE, @MouseHook, HInstance, 0); Result := hook <> 0;end;{释放钩子}function DelHook: Boolean; stdcall;begin Result := UnhookWindowsHookEx(hook);end;{按 DLL 的要求输出函数}exports SetHook name 'SetHook', DelHook name 'DelHook', MouseHook name 'MouseHook';//SetHook, DelHook, MouseHook; {如果不需要改名, 可以直接这样 exports}beginend.[/code]注意: SetWindowsHookEx 的第一个参数 WH_MOUSE 说明这是个鼠标钩子; 第四个参数 0 说明是全局的.鼠标钩子回调函数的格式在 这里然后按 Ctrl+F9 编译, 在工程目录下会生成一个和工程同名的文件, 这里是: MyHook.dll.第二步: 调用新建工程后, 保存, 并把刚才制作的 MyHook.dll 复制到这个工程目录下;然后添加两个按钮, 实现如下:[code=delphi]unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); end; {DLL 中的函数声明} function SetHook: Boolean; stdcall; function DelHook: Boolean; stdcall;var Form1: TForm1;implementation{$R *.dfm}{DLL 中的函数实现, 也就是说明来自那里, 原来叫什么名}function SetHook; external 'MyHook.dll' name 'SetHook';function DelHook; external 'MyHook.dll' name 'DelHook';{建立钩子}procedure TForm1.Button1Click(Sender: TObject);begin SetHook;end;{销毁钩子}procedure TForm1.Button2Click(Sender: TObject);begin DelHook;end;end.[/code]测试: 点击第一个按钮后, 钩子就启动了; 这是不管鼠标在哪点一下鼠标左键都会 "呯" 的一下; 点击第二个按钮可以收回钩子.下面是动态调用的方法, 功能和上面完全一直:[code=delphi]unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); end;var Form1: TForm1;implementation{$R *.dfm}{要先要定义和 DLL 中同样参数和返回值的的函数类型}type TDLLFun = function: Boolean; stdcall; {现在需要的 DLL 中的函数的格式都是这样, 定义一个就够了}var h: HWND; {声明一个 DLL 句柄} SetHook, DelHook: TDLLFun; {声明两个 TDLLFun 变量}{载入 DLL 并调用其函数}procedure TForm1.Button1Click(Sender: TObject);begin h := LoadLibrary('MyHook.dll'); {载入 DLL 并获取句柄} if h<>0 then begin SetHook := GetProcAddress(h, 'SetHook'); {让 SetHook 指向 DLL 中相应的函数} DelHook := GetProcAdd
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。