挂钩概述
挂钩是应用程序截获消息、鼠标操作和击键等事件的机制。 截获特定类型的事件的函数称为 挂钩过程。 挂钩过程可以对其接收的每个事件执行操作,然后修改或放弃该事件。
以下一些示例使用 挂钩:
- 监视用于调试的消息
- 支持录制和播放宏
- 为帮助密钥 (F1) 提供支持
- 模拟鼠标和键盘输入
- 实现基于计算机的训练 (CBT) 应用程序
注意
挂钩往往会降低系统速度,因为它们会增加系统必须对每条消息执行的处理量。 应仅在必要时安装挂钩,并尽快将其删除。
本部分讨论以下内容:
挂钩链
系统支持许多不同类型的挂钩;每种类型都提供对其消息处理机制的不同方面的访问。 例如,应用程序可以使用 WH_MOUSE 挂钩来监视鼠标消息的消息流量。
系统为每种类型的挂钩维护单独的挂钩链。 挂钩链是指向应用程序定义的特殊回调函数(称为挂钩过程)的指针列表。 当发生与特定类型的挂钩关联的消息时,系统会将消息依次传递给挂钩链中引用的每个挂钩过程。 挂钩过程可以执行的操作取决于所涉及的挂钩类型。 某些类型挂钩的挂钩过程只能监视消息;其他人可以修改消息或通过链停止其进度,从而阻止它们到达下一个挂钩过程或目标窗口。
挂钩过程
为了利用特定类型的挂钩,开发人员提供了一个挂钩过程,并使用 SetWindowsHookEx 函数将其安装到与挂钩关联的链中。 挂钩过程必须具有以下语法:
LRESULT CALLBACK HookProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
// process event
...
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
HookProc 是应用程序定义名称的占位符。
nCode 参数是挂钩过程用于确定要执行的操作的挂钩代码。 挂钩代码的值取决于挂钩的类型;每种类型都有自己的特征挂钩代码集。 wParam 和 lParam 参数的值取决于挂钩代码,但它们通常包含有关已发送或发布的消息的信息。
SetWindowsHookEx 函数始终在挂钩链的开头安装挂钩过程。 当发生由特定类型的挂钩监视的事件时,系统会在与挂钩关联的挂钩链的开头调用过程。 链中的每个挂钩过程确定是否将事件传递给下一个过程。 挂钩过程通过调用 CallNextHookEx 函数将事件传递给下一过程。
请注意,某些类型挂钩的挂钩过程只能监视消息。 无论特定过程是否调用 CallNextHookEx,系统都会向每个挂钩过程传递消息。
全局挂钩监视与调用线程位于同一桌面中的所有线程的消息。 特定于线程的挂钩仅监视单个线程的消息。 全局挂钩过程可以在调用线程所在的桌面中的任何应用程序的上下文中调用,因此该过程必须位于单独的 DLL 模块中。 仅在关联线程的上下文中调用特定于线程的挂钩过程。 如果应用程序为其自己的线程之一安装挂钩过程,则挂钩过程可以位于与应用程序代码的其余部分相同的模块中,也可以位于 DLL 中。 如果应用程序为不同应用程序的线程安装挂钩过程,则该过程必须位于 DLL 中。 有关信息,请参阅 动态链接库。
注意
应仅将全局挂钩用于调试目的;否则,应避免使用它们。 全局挂钩会损害系统性能,并导致与实现相同类型全局挂钩的其他应用程序冲突。
挂钩类型
每种类型的挂钩使应用程序能够监视系统消息处理机制的不同方面。 以下部分介绍可用的挂钩。
- WH_CALLWNDPROC和WH_CALLWNDPROCRET
- WH_CBT
- WH_DEBUG
- WH_FOREGROUNDIDLE
- WH_GETMESSAGE
- WH_JOURNALPLAYBACK
- WH_JOURNALRECORD
- WH_KEYBOARD_LL
- WH_KEYBOARD
- WH_MOUSE_LL
- WH_MOUSE
- WH_MSGFILTER和WH_SYSMSGFILTER
- WH_SHELL
WH_CALLWNDPROC和WH_CALLWNDPROCRET
使用WH_CALLWNDPROC和WH_CALLWNDPROCRET挂钩,可以监视发送到窗口过程的消息。 系统在将消息传递到接收窗口过程之前调用 WH_CALLWNDPROC 挂钩过程,并在窗口过程处理消息后调用 WH_CALLWNDPROCRET 挂钩过程。
WH_CALLWNDPROCRET挂钩将指向 CWPRETSTRUCT 结构的指针传递给挂钩过程。 结构包含处理消息的窗口过程的返回值以及与消息关联的消息参数。 对窗口进行子类化不适用于进程之间设置的消息。
有关详细信息,请参阅 CallWndProc 和 CallWndRetProc 回调函数。
WH_CBT
在激活、创建、销毁、最小化、最大化、移动窗口或调整窗口大小之前,系统会调用 WH_CBT 挂钩过程;在完成系统命令之前;从系统消息队列中删除鼠标或键盘事件之前;设置输入焦点之前;或在与系统消息队列同步之前。 挂钩过程返回的值确定系统是允许还是阻止这些操作之一。 WH_CBT挂钩主要用于基于计算机的训练 (CBT) 应用程序。
有关详细信息,请参阅 CBTProc 回调函数。
有关信息,请参阅 WinEvents。
WH_DEBUG
在调用与系统中任何其他挂钩关联的挂钩过程之前,系统将调用 WH_DEBUG 挂钩过程。 可以使用此挂钩来确定是否允许系统调用与其他类型的挂钩关联的挂钩过程。
有关详细信息,请参阅 DebugProc 回调函数。
WH_FOREGROUNDIDLE
借助WH_FOREGROUNDIDLE挂钩,可以在前台线程空闲时执行低优先级任务。 当应用程序的前台线程即将变为空闲状态时,系统会调用 WH_FOREGROUNDIDLE 挂钩过程。
有关详细信息,请参阅 ForegroundIdleProc 回调函数。
WH_GETMESSAGE
WH_GETMESSAGE挂钩使应用程序能够监视 GetMessage 或 PeekMessage 函数将返回的消息。 可以使用 WH_GETMESSAGE 挂钩来监视鼠标和键盘输入以及发布到消息队列的其他消息。
有关详细信息,请参阅 GetMsgProc 回调函数。
WH_JOURNALPLAYBACK
警告
从 Windows 11 开始,日记挂钩 API 不受支持,将在将来的版本中删除。 因此,强烈建议改为调用 SendInput TextInput API。
WH_JOURNALPLAYBACK挂钩使应用程序能够将消息插入系统消息队列。 可以使用此挂钩播放之前使用 WH_JOURNALRECORD录制的一系列鼠标和键盘事件。 只要安装了 WH_JOURNALPLAYBACK 挂钩,常规鼠标和键盘输入就处于禁用状态。 WH_JOURNALPLAYBACK挂钩是全局挂钩,不能用作特定于线程的挂钩。
WH_JOURNALPLAYBACK挂钩返回超时值。 此值告知系统在处理来自播放挂钩的当前消息之前要等待多少毫秒。 这使挂钩能够控制它播放的事件的计时。
有关详细信息,请参阅 JournalPlaybackProc 回调函数。
WH_JOURNALRECORD
警告
从 Windows 11 开始,日记挂钩 API 不受支持,将在将来的版本中删除。 因此,强烈建议改为调用 SendInput TextInput API。
使用WH_JOURNALRECORD挂钩可以监视和记录输入事件。 通常,可以使用此挂钩记录一系列鼠标和键盘事件,以便稍后使用 WH_JOURNALPLAYBACK 播放。 WH_JOURNALRECORD挂钩是全局挂钩,不能用作特定于线程的挂钩。
有关详细信息,请参阅 JournalRecordProc 回调函数。
WH_KEYBOARD_LL
使用WH_KEYBOARD_LL挂钩可以监视即将在线程输入队列中发布的键盘输入事件。
有关详细信息,请参阅 LowLevelKeyboardProc 回调函数。
WH_KEYBOARD
WH_KEYBOARD挂钩使应用程序能够监视WM_KEYDOWN的消息流量,并WM_KEYUPGetMessage 或 PeekMessage 函数将返回的消息。 可以使用 WH_KEYBOARD 挂钩来监视发布到消息队列的键盘输入。
有关详细信息,请参阅 KeyboardProc 回调函数。
WH_MOUSE_LL
使用WH_MOUSE_LL挂钩可以监视即将在线程输入队列中发布的鼠标输入事件。
有关详细信息,请参阅 LowLevelMouseProc 回调函数。
WH_MOUSE
使用WH_MOUSE挂钩可以监视 GetMessage 或 PeekMessage 函数即将返回的鼠标消息。 可以使用 WH_MOUSE 挂钩来监视发布到消息队列的鼠标输入。
有关详细信息,请参阅 MouseProc 回调函数。
WH_MSGFILTER和WH_SYSMSGFILTER
通过WH_MSGFILTER和WH_SYSMSGFILTER挂钩,可以监视菜单、滚动条、消息框或对话框即将处理的消息,并检测用户按 Alt+TAB 或 Alt+ESC 组合键后即将激活其他窗口的时间。 WH_MSGFILTER挂钩只能监视传递给菜单、滚动条、消息框或由安装挂钩过程的应用程序创建的对话框的消息。 WH_SYSMSGFILTER挂钩监视所有应用程序的此类消息。
WH_MSGFILTER和WH_SYSMSGFILTER挂钩使你可以在模式循环期间执行消息筛选,这等效于main消息循环中完成的筛选。 例如,应用程序通常会检查main循环中的新消息,从队列中检索消息的时间到调度消息的时间,并根据需要执行特殊处理。 但是,在模式循环期间,系统会检索和调度消息,而不允许应用程序在其main消息循环中筛选消息。 如果应用程序安装 WH_MSGFILTER 或 WH_SYSMSGFILTER 挂钩过程,系统会在模式循环期间调用该过程。
应用程序可以通过调用 CallMsgFilter 函数直接调用WH_MSGFILTER挂钩。 通过使用此函数,应用程序可以使用与在main消息循环中使用的相同代码在模式循环期间筛选消息。 为此,请将筛选操作封装在WH_MSGFILTER挂钩过程中,并在对 GetMessage 和 DispatchMessage 函数的调用之间调用 CallMsgFilter。
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
if (!CallMsgFilter(&qmsg, 0))
DispatchMessage(&qmsg);
}
CallMsgFilter 的最后一个参数只是传递给挂钩过程;可以输入任何值。 挂钩过程通过定义常量(如 MSGF_MAINLOOP)可以使用此值来确定从何处调用过程。
有关详细信息,请参阅 MessageProc 和 SysMsgProc 回调函数。
WH_SHELL
shell 应用程序可以使用 WH_SHELL 挂钩来接收重要通知。 当 shell 应用程序即将激活以及创建或销毁顶级窗口时,系统会调用 WH_SHELL 挂钩过程。
请注意,自定义 shell 应用程序不会接收 WH_SHELL 消息。 因此,任何将自身注册为默认 shell 的应用程序都必须在 (之前调用 SystemParametersInfo 函数,否则任何其他应用程序) 可以接收 WH_SHELL 消息。 必须使用 SPI_SETMINIMIZEDMETRICS 和 MINIMIZEDMETRICS 结构调用此函数。 将此结构的 iArrange 成员设置为 ARW_HIDE。
有关详细信息,请参阅 ShellProc 回调函数。