钩子函数并不是什么高深的技术,在Microsoft的Win32 SDK手册上就有记述。不过很可惜秉承M$的一贯风格,要看懂可是不容易的事!而且它的例子是使用SDK写的,又不完整!这就让我们这些用只会Delphi的程序员更看不懂了。不过用钩子函数是很有用的,例如鼠标钩子可以拦截下所有的鼠标消息的。
前段时间我写我的第一个软件《聊天快贴》的时候,学习了一下鼠标钩子函数,现在我把我几周的学习成果写出来希望对大家有帮助。
钩子函数一共有12种(这里就不列举出来了,不过说实话我多数钩子也没有用过^_^),分为全局子和线程钩子两种。线程钩子就只监视某个线程,全局钩子可以监视Windows的所有线程。具体的你可以看看Delphi 带的Win32 SDK,就是是全英文的,可惜了。全局钩子是必须用DLL加载,也就是说钩子函数这部分必须包装为一个DLL文件,然后再在主程序中调用钩子DLL中函数才可以!而且有些钩子是必须以全局钩子的方式存在,也就是一定要用DLL包装它才可以。
再解释一下设置钩子的Api函数:
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HINST; dwThreadId: DWORD): HHOOK; stdcall;这是在Delphi下的说明,其中第一个参数是钩子的类型;第二个参数是钩子函数的地址;第个参数是包含钩子函数的模块句柄;第四个参数指定监视的线程;返回钩子句柄。如果指定了某个确定的线程就只监视那个线程,即是线程钩子;如果为空,即是监视所有线程的全局钩子。其它几个相关函数就没有什么讲的了,只要照着用就可以了。具体的看我的源程序吧! 
另外如果你只想使用进程钩子的话,有一个现成的控件可以用,就是Rx的RxWindowHook控件。拖到你窗体上,设置Active为True就可以了。 然后他只有BeforeMessage(消息从消息队列取走前)和AfterMessage(消息从消息队列取走后)两个事件,响应他就可以了,怎么用就看你的了。
下面我说说比较实用的全局钩子的使用!我写了一个最简单的鼠标全局钩子的例子,我还假定你懂如何写DLL。好了,来看源程序:(BTW:我学习编程技巧的时候总是想看一些最简单的例子,可是有些作者习惯用复杂的应用作为例子。学起来真是痛苦!!!)一、DLL的工程文件。
library hookprj;uses
SysUtils,
Classes,
hkprocunit in 'hkprocunit.pas';{$R *.RES}
exports
EnableMouseHook, //只要把这两个函数输出就可以了,
DisableMouseHook;//不会不懂函数的意思吧^_^。
beginend.二、DLL输出函数的实现单元。unit hkprocunit;interfaceuses
Windows,Messages;var
hHk: HHOOK;//钩子的句柄值。
function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
//鼠标钩子的回调函数,即是用它来处理得到消息后要干什么。这里我只是发送一个//WM_PASTE消息。
//nCode参数是Hook的标志,一般只关心小于0时。看下面的详细说明
//WParam参数表示鼠标消息的类型
//LParam参数是一个指向 TMOUSEHOOKSTRUCT 结构的指针。结构包含了鼠标消息的状态,我只用了hwnd一个
//即鼠标消息要传递给的窗口句柄。
//返回值如果不是0的话windows就把这个消息丢掉,其它的程序就不会再收到这个消息了。function EnableMouseHook:Boolean; stdcall; export;
function DisableMouseHook:Boolean; stdcall; export;//两个函数都是Boolean类型,成功都是返回Trueimplementationfunction MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
var
MouseHookStruct: ^TMOUSEHOOKSTRUCT;//这个结构Delphi在Windows单元有定义,直接用就可以了。
nState: SHORT;//得到键盘状态的GetKeyState函数的返回值。这是一个16位的数。 
begin
Result := 0; //最好首先给他一个返回值,不然会有警告的!记住这可不是C语言。
//当nCode小于0时表示还有其它的Hook,必须把参数传给他。
//此时就要用Api函数CallNextHookEx让他调用下一个Hook!!!当然不用好像也可以。 
if nCode < 0 then 
Result := CallNextHookEx(hHk,nCode,WParam,LParam)//参数是现成的,直接用就可以了,
//详细的说明可以参考Win32 SDK
else if wParam = WM_LBUTTONDBLCLK then //判断是不是鼠标左键双击事件
begin
nState := GetKeyState(VK_CONTROL);//这个函数只有一个参数,就是要得到的键的
//键值,这里用windows的虚拟键值表示ctrl键。
if (nState and $8000) = $8000 then//如果按下了,那么返回值的最高位为1
begin //即是16进制的8000,如果没有按下就返回0
MouseHookStruct := Pointer(LParam);//转换指针并付值给MouseHookStruct变量。
SendMessage(MouseHookStruct.hwnd,WM_PASTE,0,0);//如果条件都满足了就发送WM_PASTE(粘贴)消息
end;
end;end;function EnableMouseHook:Boolean; stdcall; export;
begin
if hHk = 0 then //为了安全,必须判断一下再设置钩子。
Begin 
// 第三个参数的Hinstance 在Delphi中有定义,用就可以了。第四个参数必须为0
hHk := SetWindowsHookEx(WH_MOUSE,@MouseHookProc,Hinstance,0);
Result := True;
end
else
Result := False;
end;function DisableMouseHook:Boolean; stdcall; export;
begin
if hHk <> 0 then //如果有钩子就卸掉他。
begin
UnHookWindowsHookEx(hHk);
hHk := 0;
Result := True;
end
else
Result := False;
end;end.三、使用钩子的应用程序的工程文件。program Project1;uses
Forms,
Unit1 in 'Unit1.pas' {Form1};{$R *.RES}begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.四、使用钩子的应用程序代码。unit Unit1;interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, RxHook;type
TForm1 = class(TForm)
Button1: TButton;//放上两个Button和一个Edit控键用来试用我们的钩子函数。
Button2: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;var
Form1: TForm1;
//下面是引用hookprj.dll中的函数。
function EnableMouseHook:Boolean; stdcall; external 'Hookprj.dll' name 'EnableMouseHook';
function DisableMouseHook:Boolean; stdcall; external 'Hookprj.dll' name 'DisableMouseHook';
implementation{$R *.DFM}procedure TForm1.Button1Click(Sender: TObject);
begin
if EnableMouseHook then
ShowMessage('启动钩子成功');
end;procedure TForm1.Button2Click(Sender: TObject);
begin
if DisableMouseHook then
ShowMessage('停止钩子成功');
end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//这里调用是必须的,否则有可能没有卸载钩子就退出了,那就不好了。 
DisableMouseHook;
end;
end.Windows2000 + Delphi5.0sp1 测试通过好了讲完了,如果你还有什么问题的话可以email to me:[email protected]不过我也是菜鸟一个,太深了我也回答不了:( 不过大家可以讨论一下,共同进步! 还有如果你不想自己敲代码的话,可以到我的主页上下源程序http://suncw.boy.net.cn最后要感谢我学校的图书馆,如果不是它收藏了《Vc++ 4开发人员指南》和《Win32 SDK》这两套书的中文版的话我也不能如此快的学到如何使用钩子^_^
  重庆 晨晨 

解决方案 »

  1.   

    amiao(伤心2002)
    人家是讲学习经验,你说什么查一下吧...有没有搞错...又不是在问问题
      

  2.   

    查了,但真正能实现的很少,晨晨所提供的在退出钩子的时候会出现一个"错误关闭"的窗口.能不能把钩子函数所钩到的句柄和鼠标坐标显示到TForm面板上?
      

  3.   

    是我应用程序的问题,我多加了一个QForms单元导致错误.不好意思.
      

  4.   

    delphi开发人员指南 上有详细的讲解,其他看看api方面的书吧
      

  5.   

    delphi开发人员指南 上有详细的讲解,其他看看api方面的书吧
      

  6.   

    我也贴一份儿!Hook(钩子)的实现:
    Hook是应用程序在Microsoft Windows 消息处理过程中设置的用来监控消息流并且处理系统中尚未到达目的窗口的某一类型消息过程的机制。如果Hook过程在应用程序中实现,若应用程序不是当前窗口时,该Hook就不起作用;如果Hook在DLL中实现,程序在运行中动态调用它,它能实时对系统进行监控。根据需要,我们采用的是在DLL中实现Hook的方式。1.新建一个导出两个函数的DLL文件,在hookproc.pas中定义了钩子具体实现过
    程。代码如下:
    library keyspy;
    uses
    windows, messages, hookproc in 'hookproc.pas';
    exports
    setkeyhook,
    endkeyhook;
    begin
    nexthookproc:=0;
    procsaveexit:=exitproc;
    exitproc:=@keyhookexit;
    end.2.在Hookproc.pas中实现了钩子具体过程:
    unit hookproc;
    interface
    uses
    Windows, Messages, SysUtils, Controls, StdCtrls;
    var
    nexthookproc:hhook;
    procsaveexit:pointer;
    function keyboardhook(icode:integer;wparam:wparam;
    lparam:lparam):lresult;stdcall;export;
    function setkeyhook:bool;export;//加载钩子
    function endkeyhook:bool;export;//卸载钩子
    procedure keyhookexit;far;
    const
    afilename='c:\debug.txt';//将键盘输入动作写入文件中
    var
    debugfile:textfile;
    implementation
    function keyboardhookhandler(icode:integer;wparam:wparam;
    lparam:lparam):lresult;stdcall;export;
    begin
    if icode<0 then
    begin
    result:=callnexthookex(hnexthookproc,icode,wparam,lparam);
    exit;
    end;
    assignfile(debugfile,afilename);
    append(debugfile);
    if getkeystate(vk_return)<0 then
    begin
    writeln(debugfile,'');
    write(debugfile,char(wparam));
    end
    else
    write(debugfile,char(wparam));
    closefile(debugfile);
    result:=0;
    end;
    function endkeyhook:bool;export;
    begin
    if nexthookproc<>0 then begin
    unhookwindowshookex(nexthookproc);
    nexthookproc:=0;
    messagebeep(0); end;
    result:=hnexthookproc=0;
    end;
    procedure keyhookexit;far;
    begin
    if nexthookproc<>0 then endkeyhook;
    exitproc:=procsaveexit; end;
    end.
      

  7.   

    看了各位的发言,我想补充一下:
      上面 netlib(河外孤星) 写的好象只能在当前窗口其作用,如果用16位的DELPHI
      

  8.   

    看了各位的发言,我想补充一下:
      上面 netlib(河外孤星) 写的好象只能在当前窗口其作用,如果用16位的DELPHI可能没有问题。大家好象都没有提到全局钩子必须使用数据共享,Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定
    的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分
    别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进
    程或线程载入和卸载DLL时,都要调用该函数,它的原型是BOOL WINAPI DllMain
    (HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一个
    参数表示DLL的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,
    它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线
    程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载)
    ,在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数
    值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个D
    LL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这
    个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地
    址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间
    都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度
    。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同
    的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DL
    L地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进
    程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同
    的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。
    亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的
    属性设置为共享。不过DELPHI中好象并没有这样的方法设置段属性,所以我们可以使用文件映射的方法实现!
      

  9.   

    看了各位的发言,我想补充一下:
      上面 netlib(河外孤星) 写的好象只能在当前窗口其作用,如果用16位的DELPHI应该没问题,Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,都要调用该函数,它的原型是BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一个参数表示DLL的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载),在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。但是DELPHI中没有相应的方法来设置段属性(至少我不知道),所以可以使用文件映射的方法实现!
      

  10.   

    To tigerd88(蔬菜) ,
    你作了充分测试了吗?
    上面的起动钩子后你打开一个记事本,按住Ctrl+鼠标左键你试试就知道是全局钩子还是局部钩子,
    请记住了,不要用打断点的方式来测试!!!ok?
    不过如果要向自己线程发消息,需要使用文件映射,
    delphi的dll不能实例全局共享变量。
      

  11.   

    to tigerd88(蔬菜) 
    你没有高搞懂文章的意思,DLL的全局数据是进程私有的,但netlib(河外孤星) 在dll里实现的hook作用是全局的,如果Hook过程在应用程序中实现的才是局部的。