More advanced topics

In general (and this is a pretty good generalization) you communicate with windows by sending messages. Whatever you want from a control (which is a window too) - you send it a message. You send a message using SendMessage function. You can follow the link but I will place a function description here, so it's easier to check:

$result = SendMessage($hwnd,  
 $msg,  
 $wParam,  
 $lParam); 
 $hwnd - Handle of a window, to which a message is sent. ;
 $msg - Message id ;
 $wParam - Specifies additional message-specific information ;
 $lParam - Specifies additional message-specific information ;

Message id is integer identifying a command, wParam and lParam are additional command's parameters. Their meaning depends on the command (message id). The important thing to remember is that all parameters are numbers. Some of them can be interpreted as pointers, but they are numbers. The best way to explain it would be an example. Imagine that you want to control and Edit Box control. Again, we will use calculator as an example application. We want to get contents of the display. We have done this already in the first part of the tutorial, but we did that by calling Win32::GuiTest function, which did all the messaging for us. Now we will do it ourselves, in order to better understand how it all works.

You get a contents of an edit box by sending it a message WM_GETTEXT. If you do a search in MSDN you should find a detailed description of WM_GETTEXT (I intentionally refrain from giving you detailed link to message's documentation. Microsoft loves changing things, and documentation in no exception. Whatever link I give you, it will be broken in a year or two). It should look something like this:

If you send such a message then it is expected that 4th parameter is a pointer to the buffer to which the contents of edit box will be copied. If you use C, the code would look something like this:

If you run it you should get the correct value printed on the screen. Note that you had to provide the window handle of an Edit control. You can get it easily using WinSpy. Implementing the mechanism for finding the window handle in C would take too much time and would distract us from the point.

We have the C code working. Let's quickly write an equivalent in perl:

If you are lucky your test script will crash immediately. If not it will continue working, but something bad would happen behind the scenes anyway.

What is wrong with the code? In order to understand the problem we will have to talk a little bit about perl internals. In C code, buffer is a pointer to place in memory where the string is to be stored. In perl, a scalar value $buffer is internally represented as a structure. The structure stores information about the type, and other information and, of course, a pointer to a memory containing the actual value. But perl consider all these internals as something which is not your business. For you, scalar represents a value. So if you pass it to SendMessage it will try convert this value to lParam (which is actually long). Since the scalar is empty, the result of conversion will be 0, and this value will be send as pointer to buffer in which the contents of edit control will be stored. The result of SendMessage will be an attempt to write in address 0, which is rarely the thing you want.

So what shall we do? Shall we use reference to $buffer? No - it will not help, since references are like scalars - perl make them look like references, but behind the scenes there are still some structures involved. What we need is the pointer to the memory where the actual scalar's value is stored. Perl is not very supportive in finding this, but it is possible. You get this pointer using unpack function.

Note however, that perl assumes that you do not deal with it's internals, so it does not do any attempt to assure that such a pointer points to something useful all the time. If for example, you append something to scalar, which will force memory reallocation, the previous pointer will start pointing to nowhere. But if you get this pointer just before the call to SendMessage, the pointer should remain valid. Let's try:

This time it worked.

Armed with this knowledge we can immediately jump to dealing with more sophisticated controls like ListView. Note: If you are familiar a little bit with dealing with advanced MS controls, you may consider following code snippets artificial. I will refer to some structures and I will ignore the fact that Microsoft provides some macros which simplifies sending messages. I am doing this only for educational purposes. I am using ListView as good example to illustrate controls internals.

Example of a ListView control is given below

As you see it is a commonly used control. Let's try to get a first item from such a list. If you look in the MSDN documentation you will find that in order to get an a text of an item from the list you have to send it a message LVM_GETITEMTEXT. This is what MSDN says about this message:

As you can see, wParam should be equal to an index of an item to be retrieved. lParam should point to a memory used for data interchange. However, ListView do not use plain character buffer. It uses LVITEM structure instead. LVITEM looks like this:

Not all members have to be populated when getting an item text. One of the fields, which have to populated is pszText. It should be a pointer to a memory, to which an item text will be copied.

How can you use C structures in perl? pack function comes to the rescue here. In order to get a better understanding of pack/unpack functions, read perlpacktut manual page. In short it works this way: physically, C structure is string of bytes. Structure is a notion provided by C, which helps humans to deal with data having several attributes. So if we want to populate a memory in a way that it looks exactly as if a particular C structure is placed there, we have to put contents of all structure's fields in this memory. You can refer to pack documentation, or simply trust me that the code below really sends a structure to a control.

If you run it and if you are lucky enough, the application which hosts the list view control will crash immediately. If not, you still have good chances that something really bad has happened. But the nature of this problem is different from the problem with Edit control described earlier. If you look at SendMessage documentation you will read that for messages with id below WM_USER (which is 1024) Win32 will do a marshalling. For other messages you have to take care of it yourself. What is marshalling? Marshalling means sending data between processes. Not only bytes, but also objects, structures. Marshalling is not a technique. It is a notion of sending data. As we already know, SendMessage often expects lParam to be a pointer. But physically, lParam is only a long number. If you allocate a memory in process A, allocating function will return you the pointer to this memory in the address space of the calling process (i.e. process A). That's really important to understand. Remember that in Win32 there is no such thing like a global, universal memory addressing method, which would allow process to refer to any place of physical memory (or at least this mechanism is not promptly provided - I don't know. Waiting for your feedback). Each pointer is valid only in address space of one process. So if process A calls SendMessage on window owned by process B and pass a pointer to a memory, this pointer will not be valid in the address space of a process B, which hosts ListView or any other control. OK, so why our first example worked? Because WM_GETTEXT is below 1024. So Win32 did the marshalling itself.

Unfortunately, LVM_GETITEMTEXT, which returns an item from ListView is above WM_USER (1024). So we have to do marshalling ourselves. There are probably many ways of doing this but the most common are the following:

  • DLL injection using Win32 hooks

  • Allocation of memory in other processes address spaces

DLL injection using Win32 hooks allows you to intercept calls to some functions in other process and call functions in our process's address space instead. The good coverage of this topic can be found in "Programming Applications for Microsoft Windows, 4th edition" by Jeffrey Richter, chapter 22 "DLL Injection and API Hooking". It is already used in Win32::GuiTest. And it works well. The only disadvantage it has is that if you want to be able to send new messages, you have to make changes in Win32::GuiTest guts and recompile it. So it is not particularly good for experimenting with new controls.

The method I am going to present is based on some interesting mechanism provided by Win32: you can allocate memory in other process address space. Look at the code below:

This time it worked.

Let me now explain the program step by step. Firstly we call GetWindowThreadProcessId. It returns a thread id and a process id which owns the window pointed by hWnd. We don't care about the thread id, but we will need process id. In order to manipulate other process's memory, we need a handle to this process. We get a handle to a process by calling OpenProcess and passing process id as a parameter. Then we can start allocating memory. We do it calling VirtualAllocEx. We do it twice - once for LVITEM structure, and second time for the buffer to which a control will copy the item's text. Then we populate a local copy of LVITEM structure with the correct values and we copy it to other process's address space, calling WriteProcessMemory. We are now ready to call SendMessage. We do it, passing it 0 as wParam (first element from the list) and pLVI as a lParam. pLVI is a pointer returned by VirtualAllocEx. This pointer is invalid in address space of process which calls VirtualAllocEx. But it is valid in the address space of the process which hosts the control we interact with. And this is what we want. Fine, but if we can't use the pointer in the calling process, how do we exchange data. How would we access from our process the data returned by the control? Well, we have done this already. ReadProcessMemory and WriteProcessMemory are functions, which allow us to copy data from other process address space to ours and vice versa. Once everything is done, we are good citizens and we free the allocated memory.

And now the equivalent perl code:

This code definitely needs some further explanations. There are few new functions used:

Note: As for now this functions are not a part of a standard distribution. If you are interested in using them, you can get the ppm package from here.

AllocateVirtualBuffer allocates the memory in the address space of a process, which owns the window pointed by handle $hwnd. It returns a reference to a hash, which contains 2 elements: 'process' being a process handle, returned by OpenProcess, and 'ptr', which is an actual pointer to a memory in other process's address space. Then we populate $str_buf string with bytes so it looks in memory exactly like LVITEM structure. Then we copy those bytes to other process's address space (by calling WriteToVirtualBuffer). Then we call SendMessage. And we get the returned data using ReadFromVirtualBuffer function. After we are done, we have to free allocated memory. And this is what is FreeVirtualBuffer function for.