从内部监视窗口消息
关于挂钩和监视窗口消息的文章

目录
引言
本文将为您提供一种不同的视角来检查窗口消息,了解应用程序如何通信和管理其控件。本文不解释什么是窗口消息或它们用于什么,因此我们建议您阅读这些精彩的文章来理解它们:处理窗口消息(第一部分、第二部分、第三部分)。在本文中,我们将通过挂钩目标进程,从内部监视 Message
API。
那么,好消息是什么?
作为开发的第一步,要检查 Windows,我们会打开 Spy++ 应用程序,然后开始一项繁琐的工作,即在成百上千条消息的打印中跟踪它们。这在大多数情况下都很有帮助,因为我们通常想知道我们的窗口看到和接收了什么。然而,当想确切地知道应用程序如何与其控件通信(它调用 message
API 的哪些调用)或想查看我们的消息是否被其他人过滤时,会发生什么?如您所知,Spy++ 安装了 3 个全局钩子来接收对窗口消息处理程序的每个 Send、Post 和 Call。这些方法提供的信息不足以了解哪些消息来自我们的应用程序,或者其中是否有任何消息被更早的钩子过滤了。
不要惊慌,Deviare 来救援了。我们将拦截属于该窗口的进程的所有 Message
API,并监视其调用。从那里,我们可以确信应用程序发送给其控件的消息是什么,如果其中任何消息在 Spy++ 报告的消息中丢失,那么我们就知道是否有人在监视我们...
那些 Spy++ 不知道的消息怎么办?我们如何才能看到它们?看看 Windows 标准 ListView
使用的许多消息会发生什么。如果窗口被子类化(例如 ATL:SysListView32
),Spy++ 就一无所知,也无法跟踪其内容。尝试在 Outlook Express 中跟踪 LVM_GETNEXTITEM,您只会看到未知的0x100C 消息。您可能知道并想跟踪的自定义用户消息也一样。我们需要一个可以根据我们的需求进行定制的应用程序!
Deviare Message Spy
为了验证我们的理论,我们构建了这个消息监视应用程序。我们为其添加了查找窗口处理程序、挂钩拥有它的进程并正确报告消息和结构的方法。
查找窗口:Spy++ 风格的窗口查找器
为了选择目标窗口和进程,我们想要一个类似于 Process Explorer 和 Spy++ 中使用的界面。得益于 Mark Belles,这是一项轻松的任务。他有一篇关于如何实现一个不错的 窗口查找器 的精彩文章,就在 Code Project 上。

挂钩
为了安装钩子,我们首先需要确定目标进程。从我们的窗口查找器获取窗口句柄后,我们可以使用 GetWindowThreadProcessId
来确定哪个进程拥有该窗口。然后,我们使用 .NET API 访问它,并告知 Deviare 我们要挂钩哪个进程。
Win32.GetWindowThreadProcessId(hWnd, out _processId);
_txtProc.Text = Process.GetProcessById(_processId).MainModule.ModuleName
对于我们的监视,我们将 API 分为 2 组:Dispatch 组和 Sent and Post 组。监视到达第一组的消息将为我们提供与 Spy++ 看到的非常相似的视图。这是因为这些消息已到达应用程序,并且未被任何钩子过滤。使用我们的第二组,我们将识别对 Message
API 的直接和异步调用。
让我们看看如何为其中一个函数安装钩子
procs = _mgr.get_Processes(0);
procs = _mgr.get _Processes(0)
proc = procs.get_Item(_processId)
IPEModuleInfo mod = proc.Modules.get_ModuleByName("user32.dll");
IExportedFunction fnc = mod.Functions.get_ItemByName("PostMessageW");
_hook = _mgr.CreateHook(fnc);
_hook.Attach(proc);
_hook.OnFunctionCalled += new Deviare.DHookEvents_OnFunctionCalledEventHandler
(_hookPst_OnFunctionCalled);
_hook.Properties = (int)DeviareCommonLib.NktHookFlags._call_before;
_hook.Hook();
正如您所见,我们通过 ID 轻松选择目标进程,并通过名称选择其模块和函数。模块名称不重要,因为它始终是“user32.dll”。如果您有疑问,可以使用 Spy Studio 来查看进程模块和导出的函数。
一旦钩子安装成功,我们将在我们的处理程序中收到通知。然后,我们使用提供的接口透明地解析函数参数。(这些参数实际上在目标进程中,并且 Deviare 会根据我们的需求将它们复制到我们的进程中,并处理所有通信)。
int returnVal = callInfo.ReturnValue;
IParams pms = callInfo.Params;
IEnumParams enm = pms.Enumerator;
IParam pm = enm.First;
IParam recvMsgHndl = pms.get_Item(0);
IParam recvMsgParam = pms.get_Item(1);
IParam recvWParam = pms.get_Item(2);
IParam recvLParam = pms.get_Item(3);
在读取了调用所需的所有数据后,我们将使用生成的 XML 来识别消息,并将其正确地转换为其结构并妥善显示。
XML
此应用程序中的 XML 文档是专门创建的,用于链接消息名称、值和参数。由于像 WM_LBUTTONDOWN
这样的消息预定义为 0x201,我们可以将其放入一个 XML 文件中,其中包含 WPARAM
和 LPARAM
参数的信息。
<message value="0x201">
<name>WM_LBUTTONDOWN</name>
<return value="">
<returninfo></returninfo>
<returnmisc></returnmisc>
</return>
<wparam value="">
<wname>wParam</wname>
<wmisc>wParam Indicates whether various virtual keys are down.
This parameter can be one or more of the following values.
MK_CONTROL
The CTRL key is down.
MK_LBUTTON
The left mouse button is down.
MK_MBUTTON
The middle mouse button is down.
MK_RBUTTON
The right mouse button is down.
MK_SHIFT
The SHIFT key is down.
MK_XBUTTON1
Windows 2000/XP: The first X button is down.
MK_XBUTTON2
Windows 2000/XP: The second X button is down.</wmisc>
</wparam>
<lparam value="">
<lname>lParam</lname>
<lmisc>lParam
The low-order word specifies the x-coordinate of the cursor.
The coordinate is relative to the upper-left corner of the client area.
The high-order word specifies the y-coordinate of the cursor.
The coordinate is relative to the upper-left corner of the client area.
</lmisc>
</lparam>
<misc></misc>
我们找不到任何包含此信息的数据库,因此我们创建了一个 XML 文档,其中包含我们感兴趣的消息。如您所见,添加任何您想要的消息都很容易。在构建此 XML 的过程中,我们使用了 ActiveVB.de 的一个非常好的工具,名为 ApiViewer。只需搜索您想要的消息名称,就可以从名称评估消息值。
演员表
现在我们可以识别消息中使用的结构了,我们需要告诉 Deviare。基本上,我们是在告诉它将参数解释为,而不是简单的 LPARARM
或 WPARAM
类型,而是我们知道存在的复杂结构。对于像 WM_DRAWITEM 这样的消息就是这种情况。因此,要读取包含在 LPARAM
中的结构,我们需要将其转换为如下:
IParam pm = pms.get_Item(2); //LPARAM
pm = pm.CastTo(“LPDRAWITEMSTRUCT”); //Now our IParam is read as a pointer
//to DRAWITEMSTRUCT
pm = pm.Evaluated; //Resolve the pointer indirection
//Ready to use IParam as the structure sent by the OS.
使用 Windows 头文件中定义的任何结构都可以做到这一点。因此,您应该能够转换并读取这些消息中使用的任何结构。
使用 Deviare Message Spy

上面是我们正在使用的 Deviare Message Spy。我们选择了 Outlook Express 的联系人列表窗口(左下角)进行监视。您可以看到通过 Post 和 Send Message API 发送的所有消息值。LVM_HITTEST
已被展开以显示接收到的完整值。由于 LPARAM
是指向 LVHITTESTINFO
结构的指针,因此我们可以找到其中包含的所有相关信息。
希望您喜欢这篇文章,并发现它很有用。让我们知道您的想法!
已知问题
许多消息具有相同的十六进制地址,例如 TB_GETITEMRECT
和 TTM_UPDATE
。两者都有值 0x41d,但它们是完全不同的消息。
TTM_UPDATE
消息强制当前工具重新绘制。它不使用 wParam
和 lParam
,而 TB_GETITEMRECT
消息检索工具栏中按钮的边界矩形。
TB 是工具栏消息,TTM 是工具提示消息。由于我们的 Spy++ 风格的窗口查找器已经找到窗口类,例如 SysListView32
和 ToolbarWindow32
,因此可以使用类名来告诉程序哪条 XML 消息是正确的。
资源
- Deviare API 挂钩
- 消息查找:ApiViewer
- Microsoft Windows SDK 中的 Spy++
历史
- 2009 年 2 月:文章发布