挂钩是应用程序可以截获事件(如消息、鼠标动作和击键)的机制。 截获特定类型的事件的函数称为 挂钩过程。 挂钩过程可以对其接收的每个事件执行操作,然后修改或放弃该事件。
以下一些用于挂钩的示例:
- 出于调试目的监视消息
- 提供对宏的记录和播放的支持
- 为帮助键(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 中。 有关信息,请参阅 Dynamic-Link 库。
注释
应该仅将全局挂钩用于调试目的;否则,应避免使用。 全局挂钩会损害系统性能,并导致与其他实现相同类型的全局挂钩的应用程序冲突。
挂钩类型
每种挂钩类型使应用程序能够监视系统消息处理机制的不同方面。 以下部分介绍可用的钩子。
- 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 开始,Journaling Hooks API 不受支持,将在将来的版本中删除。 因此,强烈建议改为调用 SendInput TextInput API。
WH_JOURNALPLAYBACK挂钩使应用程序能够将消息插入系统消息队列。 可以使用此挂钩通过 WH_JOURNALRECORD播放前面记录的一系列鼠标和键盘事件。 只要安装了 WH_JOURNALPLAYBACK挂钩, 常规鼠标和键盘输入将被禁用。 WH_JOURNALPLAYBACK 挂钩是一个全局挂钩,它不能用作特定于线程的挂钩。
WH_JOURNALPLAYBACK 挂钩返回超时值。 此值告知系统在处理来自播放挂钩的当前消息之前要等待多少毫秒。 这使挂钩能够控制它播放的事件的时间。
有关详细信息,请参阅 JournalPlaybackProc 回调函数。
WH_JOURNALRECORD
警告
从 Windows 11 开始,Journaling Hooks 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_KEYUP即将由 GetMessage 或 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挂钩使你可以在模式循环期间执行消息筛选,这相当于在主消息循环中完成的筛选。 例如,应用程序通常会在从队列中检索消息的时间和调度消息的时间之间检查主循环中的新消息,并根据需要执行特殊处理。 但是,在模式循环期间,系统检索和调度消息,而不允许应用程序在其主消息循环中筛选消息。 如果应用程序安装 WH_MSGFILTER 或 WH_SYSMSGFILTER 挂钩过程,则系统会在模式循环期间调用该过程。
应用程序可以通过调用 CallMsgFilter 函数来直接调用 WH_MSGFILTER 挂钩。 通过使用此函数,应用程序可以在模式循环期间使用相同的代码来筛选消息,就像在主消息循环中一样。 为此,请在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 回调函数。