65.9K
CodeProject 正在变化。 阅读更多。
Home

从内部监视窗口消息

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (23投票s)

2009年2月19日

GPL3

6分钟阅读

viewsIcon

111115

downloadIcon

14002

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

目录

引言

本文将为您提供一种不同的视角来检查窗口消息,了解应用程序如何通信和管理其控件。本文不解释什么是窗口消息或它们用于什么,因此我们建议您阅读这些精彩的文章来理解它们:处理窗口消息(第一部分第二部分第三部分)。在本文中,我们将通过挂钩目标进程,从内部监视 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

DevMsgSpy.png

上面是我们正在使用的 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 消息是正确的。

资源

历史

  • 2009 年 2 月:文章发布
© . All rights reserved.