译文
Win32
钩子机制
在微软的
Windows
操作系统中,钩子(
hook
)
是一种机制。该机制提供某个函数使各种事件(消息,鼠标动作,击键)到达应用程序之前被截获。在某些情况下,该机制提供的函数能够作用于各种事件,包括修改或丢弃它们。接收各种事件的函数称为过滤函数,它们依据截获的事件分类。例如,某一过滤函数可能只接收所有的键盘和鼠标事件。如果
Windows
要调用一个过滤函数, 该函数必须是安装——
也就是附加——到某个
Windows
钩子里
(比如
,一个键盘钩子
)。在一个钩子里附加一个或多个的过滤函数叫做设置钩子。假如一个钩子附加了一个以上的过滤函数,
Windows
就为其维护一个过滤函数链。该链中元素的次序类似于堆栈结构,也就是,最近附加的函数位于链首,而最先附加的函数位于链尾。
当发生某个事件,该事件触发一个附加了一个或多个过滤函数的钩子时,
Windows
调用其过滤函数链的第一个函数。这种动作称为钩子调用。例如,一个过滤函数附加在
CBT
钩子里,当发生了触发该钩子的事件,(例如,即将创建一个窗口),随即,
Windows
通过调用过滤函数链的第一个函数来调用
CBT
钩子。
应用程序使用函数
SetWindowsHookEx
和
UnhookWindowsHookEx
来维护和访问过滤函数。
钩子机制为基于
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
的应用程序使用函数
SetWindowsHookEx
,
UnhookWindowsHookEx
,以及
CallNextHookEx
来管理钩子过滤函数链。在
3.1
版之前,
Windows
使用函数
SetWindowsHook
,
UnhookWindowsHook
,以及
DefHookProc
来实现钩子管理。
虽然这些函数也是在
Win32
下实现,但是它们的性能比新的(
Ex
)版本低。请把你的现有代码中的这些函数转换为新版本,并且在新代码中使用新函数。
下面是
SetWindowsHookEx
和
UnhookWindowsHookEx
的描述。参考“调用过滤函数链中下一个函数”关于
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
。
钩子码是一个整型的代码,它告知过滤函数应该知道的任何附加数据。例如,钩子码可能指出导致了钩子被调用的行为。