Delphi - 关于钩子函数HOOK (1) 基本概念 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息, 而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允 许应用程序截获处理window消息或特定事件。 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的 窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息, 也可以不作处理而继续传递该消息,还可以强制结束消息的传递。 运行机制 1、钩子链表和钩子子程: 每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的, 应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的 消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止 消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安 装的钩子放在最后,也就是后加入的先获得控制权。 Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其 占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会 自动为它做卸载钩子的操作。 钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为 普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系 统中所有线程的事件。 钩子子程必须按照以下的语法: LRESULT CALLBACK HookProc ( int nCode, WPARAM wParam, LPARAM lParam ); HookProc是应用程序定义的名字。 nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自 己的Hook代码特征字符集。 wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。 2、钩子的安装与释放: 使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总 是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的 开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件 到下一个Hook子程需要调用CallNextHookEx函数。 HHOOK SetWindowsHookEx( int idHook, // 钩子的类型,即它处理的消息类型 HOOKPROC lpfn, // 钩子子程的地址指针。如果dwThreadId参数为0 // 或是一个由别的进程创建的线程的标识, // lpfn必须指向DLL中的钩子子程。 // 除此以外,lpfn可以指向当前进程的一段钩子子程代码。 // 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。 HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的 DLL。 // 如果dwThreadId 标识当前进程创建的一个线程, // 而且子程代码位于当前进程,hMod必须为NULL。 // 可以很简单的设定其为本应用程序的实例句柄。 DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。 // 如果为0,钩子子程与所有的线程关联,即为全局钩子。 ); 函数成功则返回钩子子程的句柄,失败返回NULL。 以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。 在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外 一个SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子 链中下一个钩子过程的返回值,返回值的类型依赖于钩子的类型。这个函数的原型如下: LRESULT CallNextHookEx ( HHOOK hhk; int nCode; WPARAM wParam; LPARAM lParam; ); hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。 NCode为传给钩子过程的事件代码。 wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。 钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将 不会接收到钩子的通知而且还有可能产生不正确的结果。 钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx() 只有一个参数。函数原型如下: UnHookWindowsHookEx ( HHOOK hhk; ); 函数成功返回TRUE,否则返回FALSE。 3、一些运行机制: 在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化, DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自 动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该 进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的, 而且是互不干涉的。 因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存 储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里, 并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初 始化的数据段中。 #pragma data_seg预处理指令用于设置共享数据段。例如: #pragma data_seg("SharedDataName") HHOOK hHook=NULL; #pragma data_seg() 在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到 和共享。再加上一条指令#pragma comment(linker,"/section:.SharedDataName,rws"),那么这个数据节中的数 据可以在所有DLL的实例之间共享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。 当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下 简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。 4、系统钩子与线程钩子: SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。 线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。 系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须 放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地 址空间中,即将这个DLL注入了那些进程。 几点说明: (1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子, 然后调用系统勾子。 (2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾 子信息传递给下一个勾子函数。 (3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。 钩子类型 每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。 1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过 程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。 WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。 CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。 2、WH_CBT Hook 在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括: 1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 2. 完成系统指令; 3. 来自系统消息队列中的移动鼠标,键盘事件; 4. 设置输入焦点事件; 5. 同步系统消息队列事件。 Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。 3、WH_DEBUG Hook 在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决 定是否允许系统调用与其他Hook关联的Hook子程。 4、WH_FOREGROUNDIDLE Hook 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的 前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。 5、WH_GETMESSAGE Hook 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用 WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。 6、WH_JOURNALPLAYBACK Hook WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用 WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装, 正常的鼠标和键盘事件就是无效的。 WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。 WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长 时间(毫秒)。这就使Hook可以控制实时事件的回放。 WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。 7、WH_JOURNALRECORD Hook WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件, 然后通过使用WH_JOURNALPLAYBACK Hook来回放。 WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。 WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。 8、WH_KEYBOARD Hook 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过 GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。 9、WH_KEYBOARD_LL Hook WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。 10、WH_MOUSE Hook WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入 到消息队列中的鼠标消息。 11、WH_MOUSE_LL Hook WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。 12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现 用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条, 消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。 WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数, 应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。 13、WH_SHELL Hook 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层 窗口建立或者销毁时,系统调用WH_SHELL Hook子程。 WH_SHELL 共有5钟情況: 1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 2. 当Taskbar需要重画某个按钮; 3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 4. 当目前的键盘布局状态改变; 5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前, 应用程序必须调用SystemParametersInfo function注册它自己。 Delphi - 关于钩子函数HOOK (2) 消息钩子函数入门篇 Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。 而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息, 来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往 目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定 事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。可见, 利用钩子可以实现许多特殊而有用的功能。因此,对于高级编程人员来说,掌握钩子的编程方法是很有必要的。 钩子的类型 一. 按事件分类,有如下的几种常用类型 (1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。 (2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。 (3) 外壳钩子可以监视各种Shell事件消息。比如启动和关闭应用程序。 (4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。 (5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。 此外,还有一些特定事件的钩子提供给我们使用,不一一列举。 下面描述常用的Hook类型: 1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息 发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用 WH_CALLWNDPRO CRET Hook子程。WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传 递到Hook子程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与 这个消息关联的消息参数。 2、WH_CBT Hook 在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括: 1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 2. 完成系统指令; 3. 来自系统消息队列中的移动鼠标,键盘事件; 4. 设置输入焦点事件; 5. 同步系统消息队列事件。 Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。 3、WH_DEBUG Hook 在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。 你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。 4、WH_FOREGROUNDIDLE Hook 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。 当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。 5、WH_GETMESSAGE Hook 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。 你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。 6、WH_JOURNALPLAYBACK Hook WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过 使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装, 正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。 WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长 时间(毫秒)。这就使Hook可以控制实时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks, 它們不會被注射到任何行程位址空間。(估计按键精灵是用这个hook做的) 7、WH_JOURNALRECORD Hook WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件, 然后通过使用WH_JOURNALPLAYBACK Hook来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定 Hook一样使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。 8、WH_KEYBOARD Hook 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过 GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。 9、WH_KEYBOARD_LL Hook WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。 10、WH_MOUSE Hook WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 使用这个Hook监视输入到消息队列中的鼠标消息。 11、WH_MOUSE_LL Hook WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。 12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用 户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息, 以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式 循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。 13、WH_SHELL Hook 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时, 系统调用WH_SHELL Hook子程。 WH_SHELL 共有5种情況: 1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 2. 当Taskbar需要重画某个按钮; 3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 4. 当目前的键盘布局状态改变; 5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前, 应用程序必须调用SystemParametersInfo function注册它自己。 以上是13种常用的hook类型! 二. 按使用范围分类,主要有线程钩子和系统钩子 (1) 线程钩子监视指定线程的事件消息。 (2) 系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序, 所以钩子函数必须放在独立的动态链接库(DLL) 中。这是系统钩子和线程钩子很大的不同之处。 几点需要说明的地方: (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先 调用线程钩子,然后调用系统钩子。 (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子 信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。 编写钩子程序 编写钩子程序的步骤分为三步:定义钩子函数、安装钩子和卸载钩子。 1.定义钩子函数 钩子函数是一种特殊的回调函数。钩子监视的特定事件发生后,系统会调用钩子函数进行处理。不 同事件的钩子函数的形式是各不相同的。下面以鼠标钩子函数举例说明钩子函数的原型: LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam) 参数wParam和 lParam包含所钩消息的信息,比如鼠标位置、状态,键盘按键等。nCode包含有关消息本身的信息, 比如是否从消息队列中移出。 我们先在钩子函数中实现自定义的功能,然后调用函数 CallNextHookEx. 把钩子信息传递给钩子链的下一个钩子函数。CallNextHookEx.的原型如下: LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam ) 参数 hhk是钩子句柄。nCode、wParam和lParam 是钩子函数。 当然也可以通过直接返回TRUE来丢弃该消息,就阻止了该消息的传递。 2.安装钩子 在程序初始化的时候,调用函数SetWindowsHookEx安装钩子。其函数原型为: HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId ) 参数idHook表示钩子类型,它是和钩子函数类型一一对应的。比如,WH_KEYBOARD表示安装的是键盘钩子, WH_MOUSE表示是鼠标钩子等等。 Lpfn是钩子函数的地址。 HMod是钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL;对于系统钩子,该参数为钩子函数所在的DLL句柄。 dwThreadId 指定钩子所监视的线程的线程号。对于全局钩子,该参数为NULL。 SetWindowsHookEx返回所安装的钩子句柄。 3.卸载钩子 当不再使用钩子时,必须及时卸载。简单地调用函数 BOOL UnhookWindowsHookEx( HHOOK hhk)即可。 值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。线程钩子一般在当前线程或者当前线程派生的线程内, 而系统钩子必须放在独立的动态链接库中,实现起来要麻烦一些。 Delphi - 关于钩子函数HOOK (3) 系统挂钩捕捉键盘操作 在WINDOWS系统下,应用程序常常要截获其他程序的消息,并加以处理(例如跟踪键盘或鼠标的按键状况等)。 现在,我们假设在前台进行正常操作,在后台利用HOOK程序为系统安装一个键盘挂钩,当有按键操作时, 系统发给键盘挂钩对应的消息,而这些消息被HOOK程序截获,并加以相应的处理,这样就可以监视键盘的使用状况了。 一.实现方法 DELPHI提供了强大的可视化集成开发环境,它使得在Windows下的应用程序开发变得更加广泛, 因此我们将用DELPHI编写一个动态链接库,然后在主程序中加以调用以实现系统挂钩的设置。具体步骤如下: * 用DELPHI创建一个使用键盘挂钩的动态链接库HK.DLL * 用DELPHI编写一个使用上述DLL的可执行文件HOOK.EXE 二.实现步骤 1.创建动态链接库 * 选择FILE菜单中的NEW选项,选择DLL产生一个新的模板,保存为HK.DPR library HK . uses SysUtils, Classes, hkproc in 'hkproc.pas'; //挂钩函数在文件中的定义 exports //DLL的输出函数 EnableHotKeyHook, DisableHotKeyHook; begin hNextHookProc :=0; Assign(f,'c:.txt');//将捕获的键值存入C盘的“code.txt”文件中 Reset(f); //初始化“code.txt”文件 procSaveExit := ExitProc; //DLL释放时解除挂钩 ExitProc := @HotKeyHookExit; end. * 选择FILE菜单中的NEW选项,选择UNIT生成HKPROC.PAS unit hkproc; interface uses Windows,Messages; var f :file of char; c:char; i :integer; j :integer; hNextHookProc : HHook; procSaveExit : Pointer; function KeyboardHookHandler(iCode : Integer; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall export; function EnableHotKeyHook : BOOL export function DisableHotKeyHook : BOOL; export procedure HotKeyHookExit far implementation function KeyboardHookHandler(iCode : Integer; WParam : WPARAM; lParam : LPARAM) : LRESULT stdcall export; const _KeyPressMask = $80000000 begin Result :=0; if iCode <0 then begin Result :=CallNextHookEx(hNextHookProc,iCode, wParam,lParam); Exit; end; if((lParam and _KeyPressMask)=0) then begin i:=getkeystate($10); //返回Shift键的状态 j:=getkeystate($14); //返回Caps Lock键的状态 if((j and 1)=1 )then //判断CapsLock是否按下 begin //判断Shift 是否按下 if ((i and _KeyPressMask)=_KeyPressMask) then begin if (wparam<65) then //判断是字母键还是数字键 begin c:=chr(wparam-16); end else begin c:= chr(wparam+32); end; end else begin if (wparam<65) then begin c:=chr(wparam); end else begin c:=chr(wparam); end; end; end else begin if ((i and _KeyPressMask)=_KeyPressMask) then begin if (wparam<65) then begin c:=chr(wparam-16); end else begin c:= chr(wparam); end; end else begin if (wparam<65) then begin c:=chr(wparam); end else begin c:=chr(wparam+32); end; end; end; seek(f,FileSize(f)); write(f,c); //将捕获的键码存入文件 end; end; function EnableHotKeyHook:BOOL;export; begin Result:=False; if hNextHookProc 0 then exit; hNextHookProc:=SetWindowsHookEx(WH_KEYBOARD, KeyboardHookHandler,Hinstance,0); Result:=hNextHookProc 0 end; function DisableHotKeyHook:BOOL; export; begin if hNextHookPRoc 0 then begin UnhookWindowshookEx(hNextHookProc); hNextHookProc:=0; Messagebeep(0); Messagebeep(0); end; Result:=hNextHookPRoc=0; end; procedure HotKeyHookExit; begin if hNextHookProc 0 then DisableHotKeyHook; close(f); //关闭文件并自动解除挂钩 ExitProc:=procSaveExit; end; end. * 将程序编译后生成一个名为HK.DLL的动态链接库文件并存入“c:”目录下。 2.创建调用DLL的EXE程序HOOK.EXE * 选择FILE菜单中的NEW选项,在New Items窗口中,选择Application选项。在窗体Form中,加入两个按键, 一个定义为挂钩,另一个定义为解脱,同时加入一个文本框以提示挂钩的设置状况。将Unit1存为“c:.pas”,其相应的代码如下: unit hk; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Edit1: TEdit; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function EnableHotKeyHook : BOOL;external 'HK.dll'; //声明HOOK . DLL中的两函数 function DisableHotKeyHook :BOOL;external 'HK.dll'; procedure TForm1.Button1Click(Sender: TObject); begin if EnableHotKeyHook() then begin edit1.text :='设置挂钩' end end; procedure TForm1.Button2Click(Sender: TObject); begin if DisableHotKeyHook() then begin edit1.Text :='挂钩解脱' end end; end. * 选取Views菜单中的Project Source,将Project1存为“c:.dpr”,其代码如下: program hook; uses Forms, hk in 'hk.pas' {Form1}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end. * 编译生成HOOK.EXE 程序并存入“c:”目录下。预先用“记事本”在“c:”目录下建立CODE.TXT文件, 运行HOOK程序并单击“挂钩”键,文本框提示“设置系统挂钩”,这时启动写字板等应用程序,所键入的字 母和数字将被记录在CODE.TXT文件中。 单击“解脱”键,文本框显示“挂钩解脱”,程序将停止对键盘的捕获。 点击示意图 三. 结束语 将上述例子稍加改动,就可为系统安装其他类型的挂钩,同时为了增强程序的隐蔽性,可利用DELPHI中丰富的控件, 将上述程序运行后,只需在屏幕右下部时钟处显示一个图标,就可以跟踪键盘等系统部件的工作状况了。 在许多系统中,出于安全或其它原因,常常要求随时对键盘进行监控,一个专业的监控程序必须具备两点, 一是实时;二是作为指示图标运行。实际应用中把利用Hook(即钩子)技术编写的应用程序添加到Windows的 任务栏的指示区中就能够很好的达到这个目的。我在参考了API帮助文档基础上,根据在Delphi开发环境中的 具体实现分别对这两部分进行详细论述。 一、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:.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 nexthookproc0 then begin unhookwindowshookex(nexthookproc); nexthookproc:=0; messagebeep(0); end; result:=hnexthookproc=0; end; procedure keyhookexit;far; begin if nexthookproc0 then endkeyhook; exitproc:=procsaveexit; end; end. 二、Win95/98使用任务栏右方指示区来显示应用程序或工具图标对指示区图标的操作涉及了一个API函数 Shell_NotifyIcon,它有两个参数,一个是指向TnotifyIconData结构的指针,另一个是要添加、删除、 改动图标的标志。通过该函函数将应用程序的图标添加到指示区中,使其作为图标运行,增加专业特色。 当程序起动后,用鼠标右键点击图标,则弹出一个菜单,可选择sethook或endhook。 unit kb; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Menus,shellapi; const icon_id=1; MI_iconevent=wm_user+1;//定义一个用户消息 type TForm1 = class(TForm) PopupMenu1: TPopupMenu; sethook1: TMenuItem; endhook1: TMenuItem; N1: TMenuItem; About1: TMenuItem; Close1: TMenuItem; Gettext1: TMenuItem; procedure FormCreate(Sender: TObject); procedure sethook1Click(Sender: TObject); procedure endhook1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Close1Click(Sender: TObject); private { Private declarations } nid:tnotifyicondata; normalicon:ticon; public { Public declarations } procedure icontray(var msg:tmessage); message mi_iconevent; end; var Form1: TForm1; implementation {$R *.DFM} function setkeyhook:bool;external 'keyspy.dll'; function endkeyhook:bool;external 'keyspy.dll'; procedure tform1.icontray(var msg:tmessage); var pt:tpoint; begin if msg.lparam=wm_lbuttondown then sethook1click(self); if msg.LParam=wm_rbuttondown then begin getcursorpos(pt); setforegroundwindow(handle); popupmenu1.popup(pt.x,pt.y); end; end; procedure TForm1.FormCreate(Sender: TObject); begin normalicon:=ticon.create; application.title:=caption; nid.cbsize:=sizeof(nid); nid.wnd:=handle; nid.uid:=icon_id; nid.uflags:=nif_icon or nif_message or nif_tip; nid.ucallbackmessage:=mi_iconevent; nid.hIcon :=normalicon.handle; strcopy(nid.sztip,pchar(caption)); nid.uFlags:=nif_message or nif_icon or nif_tip; shell_notifyicon(nim_add,@nid); SetWindowLong(Application.Handle, GWL_EXSTYLE,WS_EX_TOOLWINDOW); end; procedure TForm1.sethook1Click(Sender: TObject); begin setkeyhook; end; procedure TForm1.endhook1Click(Sender: TObject); begin endkeyhook; end; procedure TForm1.FormDestroy(Sender: TObject); begin nid.uFlags :=0; shell_notifyicon(nim_delete,@nid); end; procedure TForm1.Close1Click(Sender: TObject); begin application.terminate; end; 该程序虽然只用了几个shellai函数,但是它涉及到了在Delphi中对DLL的引用、钩子实现、对指示区的操作、 用户定义消息的处理、文件的读写等比较重要的内容,我相信这篇文章能对许多Delphi的初学者有所帮助。 该程序在Win98、Delphi4.0中正常运行。 Delphi - 关于钩子函数HOOK (4) 用DELPHI编制钩子函数 Windows消息管理机构提供了能使应用程序访问控制消息流 所谓的钩子(HOOK)机制。钩子有多种,分别用于捕获某一特定类型或某一范围的消息。如:键盘消息,鼠标消息等。 我们这里仅以键盘钩子的使用为例,讨论在DELPHI下怎样编写DLL程序和怎样在自己的程序中安装使用键盘钩子函数, 并讨论了不同程序使用同一DLL文件时怎样共享数据。 一、 钩子过滤函数的编写说明 由于钩子过滤函数必须在独立的模块中,也就是说我们必须首先生成一个DLL框架,然后再在其中加入钩子函数代码 以及其他相关函数代码。我们这里以键盘钩子过滤函数的编写为例来说明。具体步骤如下: 1、先生成一个DLL框架 2、编写自己的键盘钩子过滤函数 钩子过滤函数必须是回调函数,其函数的声明为: function KeyHookProc( iCode:Integer; wParam:WPARAM; lParam:LPARAM ) : LRESULT; stdcall ;export ; 在生成的DLL框架中加入自己的键盘钩子处理函数处理键盘消息。 代码如下:… if(iCode>=0) then begin Result:=0; //初始化返回值 // 在这里加入自己的代码 end else begin Result:=CallNextHook(hOldKeyHook iCode wParam lParam); // hOldKeyHook是保存的原键盘过滤函数 end; 3、 安装键盘钩子过滤函数 为安装一个钩子键盘过滤函数应调用SetWindowsHookEx函数(适用于Windows3.0的SetWindowsHook钩子安装函数现在 已经废弃不用)。该函数的原形如下: HHOOK SetWindowsHookEx( int idHook // 安装的钩子类型 HOOKPROC lpfn // 钩子过滤函数地址 HINSTANCE hMod // 任务句柄 DWORD dwThreadId // 钩子用于的目的 ); 需要说明的是:通常应该调用MakeProcInstance函数以获取一个输出函数的前导码的入口地址,再将此地址作为 SetWindowsHookEx的第二个参数lpfn。但由于Delphi提供了"灵巧调用(smart callback)",使得MakeProcInstance 可以省去,而直接将钩子过滤函数名用作入口地址。 这样当应用程序用GetMessage或PeekMessage函数从消息队列中读消息或有按键消息(WM_KEYDOWN或WM_KEYUP)要处理时, 系统就要调用钩子过滤函数KeyHookProc处理键盘消息。 4、 卸载钩子过滤函数。 当钩子函数不再需要时,应调用UnHookWindowsHookProc卸载安装的钩子以释放系统资源。 完整的程序清单如下 Library KEYHOOK; uses Windows; const BUFFER_SIZE=16*1024; const HOOK_MEM_FILENAME='SAMPLE KEY_HOOK_MEM_FILE'; const HOOK_MUTEX_NAME ='SAMPLE KEY_HOOK_MUTEX_NAME'; type TShared=record Keys : array[0..BUFFER_SIZE] of Char; KeyCount : Integer; end; PShared=^TShared; var MemFile HookMutex : THandle; hOldKeyHook : HHook; ProcSaveExit : Pointer; Shared : PShared; //键盘钩子过滤函数 function KeyHookProc(iCode: Integer; wParam: WPARAM ; lParam: LPARAM):LRESULT ; stdcall; export; const KeyPressMask = $80000000; begin if iCode < 0 then Result := CallNextHookEx(hOldKeyHook iCode wParam lParam) else begin if ((lParam and KeyPressMask)= 0) then // 键按下 begin Shared^.Keys[Shared^.KeyCount]:=Char(wParam and $00ff); Inc(Shared^.KeyCount); if Shared^.KeyCount>=BUFFER_SIZE-1 then Shared^.KeyCount:=0; end; iCode:=-1; Result := CallNextHookEx(hOldKeyHook iCode wParam lParam); end; end; // 设置钩子过滤函数 function EnableKeyHook : BOOL ; export; begin Shared^.KeyCount:=0; //初始化键盘指针 if hOldKeyHook=0 then begin hOldKeyHook := SetWindowsHookEx(WH_KEYBOARD KeyHookProc HInstance 0); end; Result := (hOldKeyHook 0); end; //撤消钩子过滤函数 function DisableKeyHook: BOOL ; export; begin if hOldKeyHook 0 then begin UnHookWindowsHookEx(hOldKeyHook); // 解除 Keyboard Hook hOldKeyHook:= 0; Shared^.KeyCount:=0; end; Result := (hOldKeyHook = 0); end; //取得键盘缓冲区中击键的个数 function GetKeyCount :Integer ; export; begin Result:=Shared^.KeyCount; end; //取得键盘缓冲区的键 function GetKey(index:Integer) : Char ; export; begin Result:=Shared^.Keys[index]; end; //清空键盘缓冲区 procedure ClearKeyString ; export; begin Shared^.KeyCount:=0; end; //DLL的退出处理过程 procedure KeyHookExit; far; begin if hOldKeyHook 0 then DisableKeyHook; UnMapViewOfFile(Shared); // 释放内存映象文件 CloseHandle(MemFile); // 关闭映象文件 ExitProc := ProcSaveExit; end; exports // 定义输出函数 EnableKeyHook DisableKeyHook GetKeyCount ClearKeyString GetKey; begin // DLL 初始化部分 HookMutex:=CreateMutex(nil True HOOK_MUTEX_NAME); // 通过建立内存映象文件以共享内存 MemFile:=OpenFileMapping(FILE_MAP_WRITE False HOOK_MEM_FILENAME); if MemFile=0 then MemFile:=CreateFileMapping($FFFFFFFF nil PAGE_READWRITE 0 SizeOf(TShared) HOOK_MEM_FILENAME); Shared:=MapViewOfFile(MemFile File_MAP_WRITE 0 0 0); ReleaseMutex(HookMutex); CloseHandle(HookMutex); ProcSaveExit := ExitProc; // 保存DLL的ExitProc ExitProc := @KeyHookExit; // 设置DLL新的ExitProc end. // 源代码结束 二、 在自己的程序中使用编制好的键盘钩子过滤函数。 钩子函数编制好后,使用起来其实很简单:首先调用SetWindowsHookEx安装自己的钩子过滤函数, 同时保存原先的钩子过滤函数地址。这时钩子函数就开始起作用了,它将按照你的要求处理键盘消息。 程序运行完毕或不再需要监视键盘消息时,调用UnHookWindowsHookProc函数卸载所安装的钩子函数, 同时恢复原来的钩子过滤函数地址。 下面就是使用在以上编制的钩子函数的例子: unit Unit1; interface uses Windows Messages SysUtils Classes Graphics Controls Forms Dialogs StdCtrls ExtCtrls; type TForm1 = class(TForm) Memo1: TMemo; Panel1: TPanel; bSetHook: TButton; bCancelHook: TButton; bReadKeys: TButton; bClearKeys: TButton; Panel2: TPanel; procedure bSetHookClick(Sender: TObject); procedure bCancelHookClick(Sender: TObject); (36 procedure bReadKeysClick(Sender: TObject); procedure bClearKeysClick(Sender: TObject); end; var Form1: TForm1; implementation {$R *.DFM} function EnableKeyHook : BOOL ; external 'KEYHOOK.DLL'; function DisableKeyHook : BOOL ; external 'KEYHOOK.DLL'; function GetKeyCount : Integer ; external 'KEYHOOK.DLL'; function GetKey(idx:Integer) : Char ; external 'KEYHOOK.DLL'; procedure ClearKeyString ; external 'KEYHOOK.DLL'; procedure TForm1.bSetHookClick(Sender: TObject); // 设置键盘钩子 begin EnableKeyHook; bSetHook.Enabled :=False; bCancelHook.Enabled:=True; bReadKeys.Enabled :=True; bClearKeys.Enabled :=True; Panel2.Caption:=' 键盘钩子已经设置'; end; procedure TForm1.bCancelHookClick(Sender: TObject); // 卸载键盘钩子 begin DisableKeyHook; bSetHook.Enabled :=True; bCancelHook.Enabled:=False; bReadKeys.Enabled :=False; bClearKeys.Enabled :=False; Panel2.Caption:=' 键盘钩子没有设置'; end; procedure TForm1.bReadKeysClick(Sender: TObject); // 取得击键的历史记录 var i:Integer; begin Memo1.Lines.Clear; // 在Memo1中显示击键历史记录 for i:=0 to GetKeyCount-1 do Memo1.Text:=Memo1.Text+GetKey(i); end; procedure TForm1.bClearKeysClick(Sender: TObject); // 清除击键历史记录 begin Memo1.Clear; ClearKeyString; end; end. // 源代码结束 三、 Windows95下DLL中实现共享内存 在上面的钩子函数所在的DLL文件中,需要使用共享内存,即,所有击键的记录存储在同一个数据段中。 为什么要这样做呢?这是因为Windows95的DLL调用方法与Windows3.X的方法不同。每个进(线)程在登 录某动态连接库时都会为该动态连接库传入一个新的实例句柄(即DLL数据段的句柄)。这使得DLL各个 实例之间互不干扰,但是这对那些所有DLL实例共享一组变量带来一些困难。为了解决这个问题,我们 在这儿通过建立内存映射文件的方法来解决。即使用Windows的OpenFileMapping、CreateFileMapping和 MapViewOfFile三个函数来实现。使用方法如下: … MemFile是THandle类型,Shared是指针类型,HOOK_MEM_FILENAME是一常量串 … MemFile:=OpenFileMapping(FILE_MAP_WRITE False HOOK_MEM_FILENAME); //打开内存映射文件 if MemFile=0 then //打开失败则创建内存映射文件 MemFile:=CreateFileMapping($FFFFFFFF nil PAGE_READWRITE 0 SizeOf(TShared) HOOK_MEM_FILENAME); //映射文件到变量 Shared:=MapViewOfFile(MemFile File_MAP_WRITE 0 0 0); 到此为止,你已经知道用Delphi编制钩子函数有多么容易。最后不得不提醒大家:钩子函数虽然功能比较强, 但如果使用不当将会严重影响系统的效率,所以要尽量避免使用系统钩子。非要使用不可时也应该格外小心, 应使之尽可能小地影响系统的运行。 Delphi - 关于钩子函数HOOK (5) 建立键盘鼠标动作记录与回放 内容:很多的教学软件或系统监视软件可以自动记录回放用户的输入文字或点击按钮等操作操作, 这个功能的实现是使用了Windows的Hook函数。 Windows提供API函数SetwindowsHookEx来建立一个Hook,通过这个函数可以将一个程序添加到Hook链中监视Windows消息,函数语法为: SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HINST; dwThreadId: DWORD) 其中参数idHook指定建立的监视函数类型。通过Windows MSDN帮助可以看到,SetwindowsHookEx函数提供15 种不同的消息监视类型,在这里我们将使用WH_JOURNALRECORD和WH_JOURNALPLAYBACK来监视键盘和鼠标操作。 参数lpfn指定消息函数,在相应的消息产生后,系统会调用该函数并将消息值传递给该函数供处理。函数的一般形式为: Hookproc (code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall; 其中code为系统指示标记,wParam和lParam为附加参数,根据不同的消息监视类型而不同。只要在程序中建立这样 一个函数再通过SetwindowsHookEx函数将它加入到消息监视链中就可以处理消息了。 在不需要监视系统消息时需要调用提供UnHookWindowsHookEx来解除对消息的监视。 WH_JOURNALRECORD和WH_JOURNALPLAYBACK类型是两种相反的Hook类型,前者获得鼠标、键盘动作消息,后者回放鼠标键盘消息。 所以在程序中我们需要建立两个消息函数,一个用于纪录鼠标键盘操作并保存到一个数组中,另一个用于将保存的操作返给系统回放。 下面来建立程序,在Delphi中建立一个工程,在Form1上添加3个按钮用于程序操作。另外再添加一个按钮控件和一个Edit控件用于验证操作。 下面是Form1的全部代码 unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Button3: TButton; Edit1: TEdit; Button4: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; EventArr:array[0..1000]of EVENTMSG; EventLog:Integer; PlayLog:Integer; hHook,hPlay:Integer; recOK:Integer; canPlay:Integer; bDelay:Bool; implementation {$R *.DFM} Function PlayProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall; begin canPlay:=1; Result:=0; if iCode < 0 then //必须将消息传递到消息链的下一个接受单元 Result := CallNextHookEx(hPlay,iCode,wParam,lParam) else if iCode = HC_SYSMODALON then canPlay:=0 else if iCode = HC_SYSMODALOFF then canPlay:=1 else if ((canPlay =1 )and(iCode=HC_GETNEXT)) then begin if bDelay then begin bDelay:=False; Result:=50; end; pEventMSG(lParam)^:=EventArr[PlayLog]; end else if ((canPlay = 1)and(iCode = HC_SKIP))then begin bDelay := True; PlayLog:=PlayLog+1; end; if PlayLog>=EventLog then begin UNHookWindowsHookEx(hPlay); end; end; function HookProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall; begin recOK:=1; Result:=0; if iCode < 0 then Result := CallNextHookEx(hHook,iCode,wParam,lParam) else if iCode = HC_SYSMODALON then recOK:=0 else if iCode = HC_SYSMODALOFF then recOK:=1 else if ((recOK>0) and (iCode = HC_ACTION)) then begin EventArr[EventLog]:=pEventMSG(lParam)^; EventLog:=EventLog+1; if EventLog>=1000 then begin UnHookWindowsHookEx(hHook); end; end; end; procedure TForm1.FormCreate(Sender: TObject); begin Button1.Caption:='纪录'; Button2.Caption:='停止'; Button3.Caption:='回放'; Button4.Caption:='范例'; Button2.Enabled:=False; Button3.Enabled:=False; end; procedure TForm1.Button1Click(Sender: TObject); begin EventLog:=0; //建立键盘鼠标操作消息纪录链 hHook:=SetwindowsHookEx(WH_JOURNALRECORD,HookProc,HInstance,0); Button2.Enabled:=True; Button1.Enabled:=False; end; procedure TForm1.Button2Click(Sender: TObject); begin UnHookWindowsHookEx(hHook); hHook:=0; Button1.Enabled:=True; Button2.Enabled:=False; Button3.Enabled:=True; end; procedure TForm1.Button3Click(Sender: TObject); begin PlayLog:=0; //建立键盘鼠标操作消息纪录回放链 hPlay:=SetwindowsHookEx(WH_JOURNALPLAYBACK,PlayProc, HInstance,0); Button3.Enabled:=False; end; end. 代码添加完毕后,运行程序,点击“纪录”按钮开始纪录操作,这时你可以在文本控件中输入一些文字或者点击 “范例”按钮,然后点击“停止”按钮停止纪录,再点击“回放”按钮就可以讲先前所做的操作回放。 在上面的程序中,HookProc是纪录操作的消息函数,每当有鼠标键盘消息发生时,系统都会调用该函数, 消息信息就保存在地址lParam中,我们可以讲消息保存在一个数组中。PlayProc是消息回放函数,当系统可以 执行消息回放时调用该函数,程序就将先前纪录的消息值返回到lParam指向的区域中,系统就会执行该消息, 从而实现了消息回放。 Delphi - 关于钩子函数HOOK (6) 以下例程可以实现禁止用户用ALT+TAB或ALT+ESCAPE键切换程序,并且可以屏蔽左右windows键: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) btn1: TButton; btn2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } public { Public declarations }end; {按键消息的结构,Delphi中也没有,自己定义吧。这也就我为什么说用C写 这样的程序更好的原因之一。还必须注意的是这个结构在Windows NT 4 sp3以上系统 中才能使用} tagKBDLLHOOKSTRUCT = packed record vkCode: DWORD;//虚拟键值 scanCode: DWORD;//扫描码值(没有用过,我也不懂^_^) {一些扩展标志,这个值比较麻烦,MSDN上说得也不太明白,但是 根据这个程序,这个标志值的第六位数(二进制)为1时ALT键按下为0相反。} flags: DWORD; time: DWORD;//消息时间戳 dwExtraInfo: DWORD;//和消息相关的扩展信息 end; KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT; PKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT; //这个是低级键盘钩子的索引值,Delphi中没有,必须自己定义 const WH_KEYBOARD_LL = 13; //定义一个常量好和上面哪个结构中的flags比较而得出ALT键是否按下 const LLKHF_ALTDOWN = $20; var Form1: TForm1; hhkLowLevelKybd: HHOOK; implementation {$R *.dfm} { 功能:低级键盘钩子的回调函数,在里面过滤消息 参数:nCode 是Hook的标志 WParam 表示消息的类型 LParam 是一个指向我们在上面定义的哪个结构KBDLLHOOKSTRUCT的指针 返回值:如果不是0的话windows就把这个消息丢掉,程序就不会再收到这个消息了。 } function LowLevelKeyboardProc(nCode: Integer; WParam: WPARAM;LParam: LPARAM):LRESULT; stdcall; var fEatKeystroke: BOOL; p: PKBDLLHOOKSTRUCT; begin Result := 0; fEatKeystroke := FALSE; p := PKBDLLHOOKSTRUCT (lParam); //nCode值为HC_ACTION时表示WParam和LParam参数包涵了按键消息 if (nCode = HC_ACTION) then begin //拦截按键消息并测试是否是左windows、右windows、Ctrl+Esc、Alt+Tab、和Alt+Esc功能键。 case wParam of WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP, WM_SYSKEYUP: fEatKeystroke := (p.vkCode = VK_rwin) or (p.vkCode = VK_lwin) or ((p.vkCode = VK_TAB) and ((p.flags and LLKHF_ALTDOWN) 0)) or ((p.vkCode = VK_ESCAPE) and ((p.flags and LLKHF_ALTDOWN) 0)) or ((p.vkCode = VK_ESCAPE) and ((GetKeyState(VK_CONTROL) and $8000) 0)); end; end; if fEatKeystroke = True then Result := 1; if nCode 0 then Result := CallNextHookEx(0, nCode, wParam, lParam); end; procedure TForm1.Button1Click(Sender: TObject); begin //设置低级键盘钩子 if hhkLowLevelKybd = 0 then begin hhkLowLevelKybd := SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, Hinstance, 0); if hhkLowLevelKybd 0 then MessageBox(Handle, '低级键盘钩子设置成功!', '提示', MB_OK) else MessageBox(Handle, '低级键盘钩子设置失败!', '提示', MB_OK); end else MessageBox(Handle, '低级键盘钩子已设置!', '提示', MB_OK); end; procedure TForm1.Button2Click(Sender: TObject); begin //卸载低级键盘钩子 if hhkLowLevelKybd 0 then if UnhookWindowsHookEx(hhkLowLevelKybd) False then begin MessageBox(Handle, '低级键盘钩子卸载成功!', '提示', MB_OK); hhkLowLevelKybd := 0; end else MessageBox(Handle, '低级键盘钩子卸载失败!', '提示', MB_OK) else MessageBox(Handle, '没有发现低级键盘钩子!', '提示', MB_OK); end; procedure TForm1.FormDestroy(Sender: TObject); begin //在Form关闭的时候检测,如果没有卸载钩子就卸载之 if hhkLowLevelKybd 0 then UnhookWindowsHookEx(hhkLowLevelKybd); end; 上面例程在WINXP和DELPHI 7.0中使用通过。 Delphi - 关于钩子函数HOOK (7) 关于钩子函数的讲解 这是win32汇编语言中关于钩子函数的讲解,是丛windows的底层讲的,对于c,vb,delphi语言,原理都相同,只是语法的问题。 WINDOWS钩子函数 本文中我们将要学习WINDOWS钩子函数的使用方法。WINDOWS钩子函数的功能非常强大, 有了它您可以探测其它进程并且改变其它进程的行为。 理论: WINDOWS的钩子函数可以认为是WINDOWS的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。 通过“钩挂”,您可以给WINDOWS一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴趣的事件时, WINDOWS都将调用该函数。一共有两种类型的钩子:局部的和远程的。 局部钩子仅钩挂您自己进程的事件。 远程的钩子还可以将钩挂其它进程发生的事件。远程的钩子又有两种: 基于线程的 它将捕获其它进程中某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。 系统范围的 将捕捉系统中所有进程将发生的事件消息。 安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件 时都将调用您的钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先 截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能强大也意味着使用时要负责任。 在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据 结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。 当一个事件发生时,如果您安装的是一个局部钩子,您进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子 函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用远程钩子, 就必须把该钩子函数放到动态链接库中去。当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必 须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都 是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。 故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。 钩子一共有14种,以下是它们被调用的时机: WH_CALLWNDPROC 当调用SendMessage时 WH_CALLWNDPROCRET 当SendMessage的调用返回时 WH_GETMESSAGE 当调用GetMessage 或 PeekMessage时 WH_KEYBOARD 当调用GetMessage 或 PeekMessage 来从消息队列中查询WM_KEYUP 或 WM_KEYDOWN 消息时 WH_MOUSE 当调用GetMessage 或 PeekMessage 来从消息队列中查询鼠标事件消息时 WH_HARDWARE 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时 WH_MSGFILTER 当对话框、菜单或滚动条要处理一个消息时。该钩子是局部的。它时为那些有自己的消息处理过程的控件对象设计的。 WH_SYSMSGFILTER 和WH_MSGFILTER一样,只不过是系统范围的 WH_JOURNALRECORD 当WINDOWS从硬件队列中获得消息时 WH_JOURNALPLAYBACK 当一个事件从系统的硬件输入队列中被请求时 WH_SHELL 当关于WINDOWS外壳事件发生时,譬如任务条需要重画它的按钮. WH_CBT 当基于计算机的训练(CBT)事件发生时 WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的应用程序很少使用 WH_DEBUG 用来给钩子函数除错 现在我们知道了一些基本的理论,现在开始讲解如何安装/卸载一个钩子。 要安装一个钩子,您可以调用SetWindowHookEx函数。该函数的原型如下: SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD HookType 是我们上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD pHookProc 是钩子函数的地址。如果使用的是远程的钩子,就必须放在一个DLL中,否则放在本身代码中 hInstance 钩子函数所在DLL的实例句柄。如果是一个局部的钩子,该值为NULL ThreadID 是您安装该钩子函数后想监控的线程的ID号。该参数可以决定该钩子是局部的还是系统范围的。如果该值为NULL, 那么该钩子将被解释成系统范围内的,那它就可以监控所有的进程及它们的线程。如果您指定了您自己进程中的某个线程ID 号, 那该钩子是一个局部的钩子。如果该线程ID是另一个进程中某个线程的ID,那该钩子是一个全局的远程钩子。 这里有两个特殊情况:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK总是代表局部的系统范围的钩子,之所以说是局部, 是因为它们没有必要放到一个DLL中。WH_SYSMSGFILTER 总是一个系统范围内的远程钩子。其实它和WH_MSGFILTER钩子类似, 如果把参数ThreadID设成0的话,它们就完全一样了。 如果该函数调用成功的话,将在eax中返回钩子的句柄,否则返回NULL。您必须保存该句柄,因为后面我们还要它来卸载钩子。 要卸载一个钩子时调用UnhookWidowHookEx函数,该函数仅有一个参数,就是欲卸载的钩子的句柄。如果调用成功的话, 在eax中返回非0值,否则返回NULL。 现在您知道了如何安装和卸载一个钩子了,接下来我们将看看钩子函数。. 只要您安装的钩子的消息事件类型发生,WINDOWS就将调用钩子函数。譬如您安装的钩子是WH_MOUSE类型, 那么只要有一个鼠标事件发生时,该钩子函数就会被调用。不管您安装的时那一类型钩子,钩子函数的原型都时是一样的: HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD nCode 指定是否需要处理该消息 wParam 和 lParam 包含该消息的附加消息 HookProc 可以看作是一个函数名的占位符。只要函数的原型一致,您可以给该函数取任何名字。 至于以上的几个参数及返回值的具体含义各种类型的钩子都不相同。譬如: WH_CALLWNDPROC nCode 只能是HC_ACTION,它代表有一个消息发送给了一个窗口 wParam 如果非0,代表正被发送的消息 lParam 指向CWPSTRUCT型结构体变量的指针 return value: 未使用,返回0 WH_MOUSE nCode 为HC_ACTION 或 HC_NOREMOVE wParam 包含鼠标的事件消息 lParam 指向MOUSEHOOKSTRUCT型结构体变量的指针 return value: 如果不处理返回0,否则返回非0值 所以您必须查询您的WIN32 API 指南来得到不同类型的钩子的参数的详细定义以及它们返回值的意义。 这里还有一个问题需要注意:所有的钩子都串在一个链表上,最近加入的钩子放在链表的头部。当一个事件发生时, WINDOWS将按照从链表头到链表尾调用的顺序。所以您的钩子函数有责任把消息传到下一个链中的钩子函数。 当然您可以不这样做,但是您最好明白这时这么做的原因。在大多数的情况下,最好把消息事件传递下去以便 其它的钩子都有机会获得处理这一消息的 |
请发表评论