Contents
Introduction
This article presents you with a different perspective of how to inspect window messages, to see how applications are communicating and managing their controls. We are not going to explain what window messages are or what they are used for in this article, so we suggest that you read these excellent articles to understand them: Handling Window Messages (Part 1, Part 2, Part 3). In this article we are going to monitor the Message
API from the inside by hooking the target process.
So, What's the Good News?
As a first step when developing, to inspect Windows, we open the Spy++ application and start the tedious work of following messages as they are printed in their hundreds. This is helpful most of the time, as we usually want to know what our windows are seeing and receiving. Yet, what happens when we want to know exactly how an application is communicating with its controls (what calls it makes to the message
API) or want to see if our messages are getting filtered by someone else? As you may know, Spy++ installs 3 global hooks to receive every Send, Post and Call to a window message handler. The information provided by these methods is not enough to know what messages are coming from our application or if any of them have been filtered by a hook installed earlier in the call chain.
Do not panic, Deviare comes to rescue. What we are going to do is intercept all the Message
APIs from the process that the window belongs to and monitor its calls. From there, we can be sure of what messages are being sent from the application to its controls and if any of them are missing from the ones that Spy++ is reporting, then we will know if someone else is watching us...
What happens with the messages not known by Spy++? How are we going to see them? Look at what happens with many of the messages used by the standard ListView
in Windows. Spy++ does not know anything about them if the window is subclassed (for example ATL:SysListView32
), and cannot trace its content. Try following LVM_GETNEXTITEM in Outlook Express and you will only see unknown 0x100C messages. The same goes for custom user messages that you may know and want to follow. We need an application that can be customized to our needs!
Deviare Message Spy
To probe our theory, we have built this message spying application. We have added to it a way to lookup windows handlers, hook the process owning it, and correctly report the messages and structures.
Finding a Window: The Spy++ Style Window Finder
To pick the target window and the process, we wanted an interface like the one used in Process Explorer and Spy++. Thanks to Mark Belles, this was an easy task. He has a great article on how to implement a nice Window Finder, here on The Code Project.
Hooking
In order to install a hook, first we need to identify our target process. After obtaining a window handle from our Window Finder, we can use GetWindowThreadProcessId
to identify which process owns the window. From there, we use the .NET API to access it and tell Deviare which process we wish to hook.
Win32.GetWindowThreadProcessId(hWnd, out _processId);
_txtProc.Text = Process.GetProcessById(_processId).MainModule.ModuleName
For our monitoring, we have divided the API in 2 sets: the Dispatch group, and the Sent and Post group. Monitoring messages that arrive to the first group will provide us with a very similar view of what Spy++ sees. This is because these messages arrived to the application and have not been filtered by any hook. With our second group, we will identify direct and asynchronous calls to the Message
API.
Let's see how we install the hook for one of these functions:
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();
As you see, we easily pick our target process by Id and select its Module and Function by name. The module name is not important, as it is always going to be “user32.dll”. If you have doubts, you can use Spy Studio to watch the process modules and exported functions.
Once the hook gets installed, we will receive notifications on our handler. From there, we parse the function parameters transparently with the interface provided. (These parameters are actually in the target process, and Deviare copies them to our process on our demand and handles all the communication).
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);
After reading all the data we require from the call, we will use our generated XML to identify the message and properly cast it to its structure and show it properly.
The XML
The XML document in this application was created specifically to link together the message names, values and parameters. As messages like WM_LBUTTONDOWN
are predefined as 0x201, we can place this in an XML file containing information on the parameters WPARAM
and 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>
We could not find any database with this information, so we generated an XML document with the messages that we were interested in knowing about. As you can see, it is easy to simply add any message you want. In the process of building this XML, we used a very nice tool called ApiViewer from ActiveVB.de. Just search for the message names you want and you can evaluate the message values from the names.
The Cast
Now that we can identify the structures used on messages, we need to tell Deviare. Basically we are telling it to interpret our parameter, not as a simple LPARARM
or WPARAM
type, but as the complex structure we know is there. This is the case for messages like WM_DRAWITEM. So, to read its structure contained within the LPARAM
, we need to cast it as follows:
IParam pm = pms.get_Item(2);
pm = pm.CastTo("LPDRAWITEMSTRUCT");
pm = pm.Evaluated;
It is possible to do this with all of the structures you can find defined in the windows headers. So, you should be able to cast and read any of them that are used within these messages.
Using Deviare Message Spy
Above we have our Deviare Message Spy in action. We selected the contacts list window from Outlook Express (at the bottom left) to spy on. You can see all the message values that were sent via Post and Send Message APIs. LVM_HITTEST has been expanded to show the full values received. As LPARAM
is a pointer to the LVHITTESTINFO
structure, we can find all relevant information contained within.
Hope you enjoyed this article, and found it useful. Let us know what you think!
Known Issues
Many messages have the same Hex Address, such as TB_GETITEMRECT
and TTM_UPDATE
. Both of these messages have the value of 0x41d but are very different messages.
The TTM_UPDATE
Message forces the current tool to be redrawn. It does not use the wParam
and lParam
whereas TB_GETITEMRECT
message retrieves the bounding rectangle of a button in a toolbar.
TB is a Toolbar message and TTM is a Tooltip message. As our Spy++ style window finder already finds the window class, such as SysListView32
and ToolbarWindow32
, it would be easy to use the class name to tell the program with XML message is the correct one.
Resources
History
- February 2009: Article posted