Win32 Hooks

来源:互联网 发布:linux无法删除用户 编辑:程序博客网 时间:2024/04/28 01:14
 
译文
Win32 钩子机制
在微软的Windows操作系统中,钩子(hook是一种机制。该机制提供某个函数使各种事件(消息,鼠标动作,击键)到达应用程序之前被截获。在某些情况下,该机制提供的函数能够作用于各种事件,包括修改或丢弃它们。接收各种事件的函数称为过滤函数,它们依据截获的事件分类。例如,某一过滤函数可能只接收所有的键盘和鼠标事件。如果Windows要调用一个过滤函数, 该函数必须是安装——也就是附加——到某个Windows钩子里 (比如,一个键盘钩子)。在一个钩子里附加一个或多个的过滤函数叫做设置钩子。假如一个钩子附加了一个以上的过滤函数,Windows 就为其维护一个过滤函数链。该链中元素的次序类似于堆栈结构,也就是,最近附加的函数位于链首,而最先附加的函数位于链尾。
当发生某个事件,该事件触发一个附加了一个或多个过滤函数的钩子时,Windows 调用其过滤函数链的第一个函数。这种动作称为钩子调用。例如,一个过滤函数附加在CBT钩子里,当发生了触发该钩子的事件,(例如,即将创建一个窗口),随即,Windows通过调用过滤函数链的第一个函数来调用CBT钩子。
应用程序使用函数SetWindowsHookExUnhookWindowsHookEx来维护和访问过滤函数。
钩子机制为基于Windows的应用程序提供强大的性能。它被用于:
处理或修改所有应用程序的对话框,消息框,滚动条,或者菜单的全部消息(WH_MSGFILTER)。
处理或修改所有系统对话框,消息框,滚动条,或者菜单的全部消息(WH_SYSMSGFILTER)。
当调用函数GetMessage或者PeekMessage时,处理或修改所有任何类型的系统消息WH_GETMESSAGE)。
当调用函数SendMessage时,处理或修改所有任何类型的消息WH_CALLWNDPROC
记录或反弹键盘或鼠标事件(WH_JOURNALRECORD, WH_JOURNALPLAYBACK)。
处理,修改,或者删除键盘事件(WH_KEYBOARD)。
处理,修改,或者丢弃鼠标事件(WH_MOUSE
响应特定的系统动作,使开发基于计算机的训练程序(CBT)成为可能WH_CBT
防止调用其它的过滤器(WH_DEBUG
在应用程序中,钩子机制被用于:
支持菜单,对话框,以及消息框F1帮助键WH_MSGFILTER
提供鼠标和击键记录以及反弹功能,这些功能往往涉及到各种宏。例如, Windows宏记录器使用钩子机制来提供记录以及反弹泛函性WH_JOURNALRECORD, WH_JOURNALPLAYBACK)。
监控各种消息,以确定发送到特定的窗口是哪类消息或者某个消息将产生何种动作(WH_GETMESSAGE, WH_CALLWNDPROC)。例如,Spy,一个Win32™环境下的Windows NT™软件开发包(SDK)中的实用程序,使用钩子机制实现上面谈到的任务。在SDK中能够找到Spy的源码。
模拟鼠标或键盘输入(WH_JOURNALPLAYBACK)。钩子机制提供唯一可靠的方法来模拟这些行为。如果你试图以发送或寄送消息来模拟这些事件,Windows内部不会更新鼠标或键盘的状态,这将导致非预期的行为。如果钩子用于反弹键盘或者鼠标事件,这些事件处理起来非常像真的键盘或鼠标事件。例如,微软Excel使用钩子机制实现其SEND.KEYS宏功能。
为运行于Windows环境下的应用程序提供CBT模式(WH_CBT)。WH_CBT钩子使得开发CBT应用程序变得更简单。
如何使用钩子:
使用钩子时,你必须了解:
如何使用Windows钩子函数从钩子的过滤函数链中添加或删除函数 。
附加的过滤函数将要执行什么功能。
哪种类型的钩子可用,它们能干什么,以及向过滤函数传递什么样的信息 (参数)。
Windows 钩子函数
基于Windows的应用程序使用函数SetWindowsHookExUnhookWindowsHookEx,以及CallNextHookEx来管理钩子过滤函数链。在3.1版之前,Windows使用函数SetWindowsHook UnhookWindowsHook,以及DefHookProc来实现钩子管理。虽然这些函数也是在Win32下实现,但是它们的性能比新的(Ex)版本低。请把你的现有代码中的这些函数转换为新版本,并且在新代码中使用新函数。
下面是SetWindowsHookExUnhookWindowsHookEx的描述。参考“调用过滤函数链中下一个函数”关于CallNextHookEx的讨论。
SetWindowsHookEx
SetWindowsHookEx函数添加一个过滤函数到钩子中。它有四个参数:
一个整型代码,描述钩子的附加过滤函数,以及该函数的地址。这些代码在WINUSER.H中定义,在下文描述。
过滤函数的地址。过滤函数的输出必须包含它(1)在应用程序模块定义文件中的输出声明或(2)实时链接库(DLL)或者(3)使用适当的编译器标志。
包容过滤函数模块的实例句柄。在 Win32 (不像Win16)系统中,当安装特定线程钩子(看下文)时,该值为NULL,但并不都像文档说明的一定为NULL。当你安装某个系统范围或其它进程的子线程钩子时,必须在过滤函数出现之处使用DLL的实例句柄。
待设置钩子的线程标志(ID)。如果线程ID不为零,设置的过滤函数只能在特定线程上下文中调用。如果线程ID为零,设置的过滤函数就在系统范围内有效,能够被系统内任何线程的上下文中调用。应用程序或库能够使用函数GetCurrentThreadId来获取线程的句柄,以次钩住当前线程。
某些钩子只能在系统域内设置,一些只能在特定线程内设置,而其它的两者均可。如下表:
钩子
作用范围(Scope
WH_CALLWNDPROC
线程或系统
WH_CBT
线程或系统
WH_DEBUG
线程或系统
WH_GETMESSAGE
线程或系统
WH_JOURNALRECORD
仅限系统
WH_JOURNALPLAYBACK
仅限系统
WH_FOREGROUNDIDLE
线程或系统
WH_SHELL
线程或系统
WH_KEYBOARD
线程或系统
WH_MOUSE
线程或系统
WH_MSGFILTER
线程或系统
WH_SYSMSGFILTER
仅限系统
对于给出的钩子类型,首先调用线程钩子,然后是系统钩子。在某些前提下,使用线程钩子代替系统钩子是个好主意。对于线程钩子:不要使用高于系统范围的钩子,它不利于调用。
不要序列化钩子的所有事件。比方说,如果某个应用程序设置了一个系统范围内的键盘钩子,发往所有应用程序的键盘消息都会被该程序的过滤函数截获,极大的浪费系统的多输入队列功能性。如果它的过滤函数停止处理键盘事件,那么系统将会显式地停止用户,但用户不会真的被停止。用户往往能够使用CTRL+ALT+DEL组合键来注销或解决该问题,但他可能不喜欢文中的讨论。同样,用户可能没意识到能够依次利用注销/登陆操作来重置系统。
不要将过滤函数的实现打包到单独的DLL里(译者注:可能是指过滤函数的声明及定义打包到同一个DLL)。在不同的应用程序中,所有系统范围内的钩子以及线程钩子必须驻于DLL中。
附加到不同进程的钩子不需要共享DLL中的数据。系统范围的过滤函数必须驻于DLL中,必须与其它进程共享所需的数据。由于数据共享不是DLL的缺省操作,所以必须小心实现系统范围的过滤函数。假如过滤函数错误地共享进程中的数据或者使用进程中的无效数据,那么将导致该进程崩溃。
函数SetWindowsHookEx返回一个指向已设置的钩子的句柄(一个 HHOOK)。通过这个句柄,应用程序或者库在调用函数UnhookWindowsHookEx才能识别该钩子。如果钩子添加过滤函数失败,函数SetWindowsHookEx返回NULL,同时按照下面列出的值设置错误代码,指出函数失败的原因。
ERROR_INVALID_HOOK_FILTER:钩子码无效。
ERROR_INVALID_FILTER_PROC: 过滤函数无效。
ERROR_HOOK_NEEDS_HMOD: 全局钩子的hInstance参数被设置为NULL,或设置钩子的线程不在应用程序中。
ERROR_GLOBAL_ONLY_HOOK: 系统专有的钩子被设置到线程中。
ERROR_INVALID_PARAMETER: 线程ID无效
ERROR_JOURNAL_HOOK_SET: 重复设置簿记类型的钩子。同一时间内,只能设置一个簿记记录或者簿记回放钩子。当屏幕保护程序运行时,假如某个应用程序试图设置簿记钩子,函数将设置该错误代码。
ERROR_MOD_NOT_FOUND: 全局钩子的hInstance参数不是库。(事实上,该值仅仅意味着用户不能在它的模块列表中定位模块句柄。)
其它任意值:出于安全性不允许设置该钩子,或者系统内存溢出。
Windows内部维护着过滤函数链(见下图),在该链中不依赖过滤函数而正确地存储下一个过滤函数的地址Windows 3.1之前版本就是这样做的)。因此,Windows 3.1版本的钩子比以前版本更为强壮。另外,由于系统内部维护过滤函数链,使得性能极大地增强。
Windows 3.1的过滤函数链
UnhookWindowsHookEx
调用函数UnhookWindowsHookEx从钩子的过滤函数链移除过滤函数。该函数接收SetWindowsHookEx返回的句柄作为参数,并返回一个值,指示钩子是否被删除。函数UnhookWindowsHookEx在这种情况下往往返回真。
过滤函数
过滤(钩子)函数是一种附加到钩子的函数。因为过滤函数是被Windows调用而不是应用程序,所以它们有时候类似回调函数一样被引用。为保持连贯性,本文使用过滤函数这一术语。
所有的过滤函数都必须包含以下形式:
LRESULT CALLBACK FilterFunc(nCode, wParam, lParam);
int nCode;
WORD wParam;
DWORD lParam;
所有的过滤函数都应该返回一个长整型数据(LONG)。FilterFunc是实际过滤函数名的占位符。
参数
过滤函数接收三个参数:ncode(钩子码),wParam,以及 lParam钩子码是一个整型的代码,它告知过滤函数应该知道的任何附加数据。例如,钩子码可能指出导致了钩子被调用的行为。
 
原创粉丝点击