16. Windows Messages

Page 281
  16. Windows Messages  
   
  The Windows operating system uses messages to communicate to and from the windows in the system. In general, messages can be classified as:  
   
  Messages generated by Windows in response to user input from the keyboard or mouse  
   
  Messages generated by an application, such as those produced by the SendMessage function  
   
  A message is composed of four parts:  
   
  A window handle that identifies the target window of the message  
   
  A message identifier, which is a 32-bit long  
   
  Two 32-bit values called message parameters  
   
  These four message parts are clearly seen in the declaration of the SendMessage function:  
   LRESULT SendMessage(        HWND hWnd,      // handle of destination window        UINT Msg,       // message to send        WPARAM wParam,  // first message parameter        LPARAM lParam,  // second message parameter      );   
   
  I have counted almost 1,000 different messages, and I am sure that I have missed some. These messages can be broadly categorized into a few main categories:  
   
  Messages related to a type of control: Static, Listbox, Combo box, Button, Edit, Scrollbar, TreeView, ListView, Toolbar, Trackbar, Statusbar, Progressbar, ToolTip, UpDown, Tab Control  
   
  Mouse messages  
Page 282
   
  Keyboard messages  
   
  Clipboard messages  
   
  Dialog messages  
   
  MDI messages  
   
  Others  
   
  Microsoft was kind enough to supply symbolic constants for the message identifiers. For instance, Table 16-1 shows the 45 Listbox-related message identifiers. We can tell what many of these messages do from their names:  
   
  Table 16-1. The Listbox Messages  
   
  LB_ADDFILE  
   
  LB_GETITEMHEIGHT  
   
  LB_SELECTSTRING  
   
  LB_ADDSTRING  
   
  LB_GETITEMRECT  
   
  LB_SELITEMRANGE  
   
  LB_CTLCODE  
   
  LB_GETLOCALE  
   
  LB_SELITEMRANGEEX  
   
  LB_DELETESTRING  
   
  LB_GETSEL  
   
  LB_SETANCHORINDEX  
   
  LB_DIR  
   
  LB_GETSELCOUNT  
   
  LB_SETCARETINDEX  
   
  LB_ERR  
   
  LB_GETSELITEMS  
   
  LB_SETCOLUMNWIDTH  
   
  LB_ERRSPACE  
   
  LB_GETTEXT  
   
  LB_SETCOUNT  
   
  LB_FINDSTRING  
   
  LB_GETTEXTLEN  
   
  LB_SETCURSEL  
   
  LB_FINDSTRINGEXACT  
   
  LB_GETTOPINDEX  
   
  LB_SETHORIZONTALEXTENT  
   
  LB_GETANCHORINDEX  
   
  LB_INITSTORAGE  
   
  LB_SETITEMDATA  
   
  LB_GETCARETINDEX  
   
  LB_INSERTSTRING  
   
  LB_SETITEMHEIGHT  
   
  LB_GETCOUNT  
   
  LB_ITEMFROMPOINT  
   
  LB_SETLOCALE  
   
  LB_GETCURSEL  
   
  LB_MSGMAX  
   
  LB_SETSEL  
   
  LB_GETHORIZONTALEXTENT  
   
  LB_OKAY  
   
  LB_SETTABSTOPS  
   
  LB_GETITEMDATA  
   
  LB_RESETCONTENT  
   
  LB_SETTOPINDEX  

   
  As we will see in some detail later in this chapter, every window is based on a window class. Each window class has an associated window procedure (also called a window function). This procedure must be supplied by the programmer; that is, by the VC++ programmer VB takes care of this for the VB programmer.  
   
  The window procedure is called by the system in order to notify a window that it has a message. Thus, a window procedure is a callback procedure. The procedure has the general form shown here. The parameters are filled in by the system with the four parts of a message described earlier.  
 
  // Window procedure for window class
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
   switch(iMsg)    // act based on window identifier
   {
 
 

Page 283
 
        // process any resize messages
      // case WM_SIZE:
         // code here to process message
         return 0;
      // process the destroy message
      case WM_DESTROY:
         // code here to process message
         return 0;
   }
   // Call Windows default window procedure
   return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
 
   
  Messages are handled on a per thread basis. That is, the system determines which thread owns the target window of the message and passes the message to that thread. The thread determines which window procedure should be called and, in effect, asks the system to call that window procedure by calling the DispatchMessage function. This will become clearer as we look at the process in more detail.  
 
  The Message Queues of a Thread  
   
  Each thread has a collection of message queues that are used to queue incoming messages, that is, messages sent by the system (perhaps in response to user action or another thread calling SendMessage). As Figure 16-1 illustrates, there are four queues:  
 
  The posted-message queue
Queues posted messages, that is, messages that are received via the PostMessage API function.
 
 
  The sent-message queue
Queues sent messages, that is, messages that are received via the SendMessage API function (and its relatives).
 
 
  The reply-message queue
Queues messages that are received as a reply to a sent message (posted messages do not reply).
 
 
  The virtualized input queue
Queues messages that result from hardware input, such as from the keyboard or mouse.
 
   
  A special structure called THREADINFO holds pointers to these queues along with some additional information that we will discuss later in this chapter. Note, however, that this structure is undocumented. The only source of information that I could obtain for it is Jeffrey Richter's book Advanced Windows, Third Edition  
Page 284
   
  from Microsoft Press. Thus, I suppose I should say that my information is only as current as his. Normally, I would not discuss undocumented features of the operating system, but this one does help tie the discussion together very nicely, so I will make an exception.  
   
  0284-01.gif  
   
  Figure 16-1.
A thread's input queues
 
 
  The Windows Messaging System  
   
  Figure 16-2 shows the overall layout of the Windows messaging system. Let us look at the components of this system.  
   
  We should note that the documentation simply refers to the message queue for a thread. I presume that it does so because it does not acknowledge the existence of separate posted, sent, reply, and virtual input queues.  
   
  The documentation also suggests that sent messages are not queued at all, but that the system just calls the window procedure for the target window immediately. This is true when the SendMessage function is being called from the thread that owns the target window. In this case, the thread simply calls the window procedure for the target window just like any other procedure. On the other hand, if the target window belongs to a foreign thread (in the same process or in another process), the message must be placed in the target thread's sent-message queue. (In 16-bit Windows, sent messages were never queued.)  
Page 285
   
  0285-01.gif  
   
  Figure 16-2.
The Windows messaging system
 
   
  Getting to a Thread's Message Queues  
   
  As we have said, messages are generated in response to user action (keyboard and mouse) and also by application code. Hardware input messages, such as those generated by the keyboard or mouse, are placed in the system message queue by  
Page 286
   
  the hardware device driver. From there, a special system thread called the Raw Input Thread, or RIT, removes the messages, determines the thread that owns the target window, and places the message in the appropriate message queue for the thread. Note that posted and sent messages are placed in the appropriate thread message queue directly.  
   
  Message Loops  
   
  When a VC++ programmer creates a program, he or she must create a message loop, which has the following appearance:  
 
  // Message loop for window
while( GetMessage(&msg, hwnd, 0 , 0) )
{
   TranslateMessage(&msg);
   DispatchMessage(&msg);
}
 
   
  The GetMessage function is:  
  BOOL GetMessage(       LPMSG lpMsg,         // address of MSG structure to receive message       HWND hWnd,           // handle of window to check for messages for       UINT wMsgFilterMin,  // first message in acceptable message range       UINT wMsgFilterMax   // last message in acceptable message range     );
   
  This function retrieves a message from the thread's message queues, provided that it is intended for the window with handle hwnd and provided that the message number lies in the acceptable range between wMsgFilterMin and wMsgFilterMax. These parameters provide a way to filter messages. For instance, if wMsgFilterMin is set to WM_KEYFIRST, and wMsgFilterMax is set to WM_KEYLAST, then only keyboard messages are retrieved.  
   
  Setting both filtering parameters to 0 removes all filtering and allows all messages to be retrieved. Also, setting the hwnd parameter to NULL specifies that all messages for this thread should be retrieved.  
   
  The GetMessage function then fills the lpMsg structure with information about the message. This structure is defined by:  
  typedef struct tagMSG {       HWND hWnd;     // handle to the target window of message       UINT message;  // message number       WPARAM wParam; // message-specific parameter       LPARAM lParam; // message-specific parameter       DWORD time;    // time the message was posted       POINT pt;      // cursor position when message was posted     } MSG;
Page 287
   
  When GetMessage returns, it returns a nonzero value unless the message is WM_QUIT, in which case the return value is 0. Hence, the while loop will continue until a quit message is received.  
   
  If GetMessage returns a nonzero value, the TranslateMessage function will do some translation related to keyboard input if necessary (we won't discuss this further), and the DispatchMessage function:  
 
  LONG DispatchMessage(
   CONST MSG *lpmsg // pointer to MSG structure with message
);
 
   
  asks Windows to call the window procedure for the class, feeding it the parameters from the filled-in MSG structure for the message. Note that DispatchMessage does not return until the message has been processed and the window procedure returns.  
   
  Note, however, that it is possible for a function within a window procedure to send a message that results in the window procedure being called again. For instance, if a window procedure calls UpdateWindow, this function sends a WM_PAINT message to the window, thus invoking the window procedure while it is still processing the message that caused the call to UpdateWindow. In this case, processing of the first message is halted until the second message (WM_PAINT) is processed.  
   
  This requires that a window procedure be reentrant (capable of being called while its code is being executed). Among other things, this may require that the procedure save its current state so that it can restore that state to continue where it left off.  
   
  A Closer Look at GetMessage  
   
  When a message is placed in one of a thread's message queues, the system also sets one or more wake flags in the thread's THREADINFO structure. This will cause the thread to wake so that it can process the message. A thread can also call the API function GetQueueStatus:  
 
  DWORD GetQueueStatus(
   UINT flags // queue-status flags
);
 
   
  or, in VB:  
 
  Declare Function GetQueueStatus Lib "user32" ( _
   ByVal fuFlags As Long _
) As Long
 
   
  to check on the types of messages that are pending. Table 16-2 shows the various wake flags and their meanings.  
Page 288
Table 16-2. Wake Flags
Value Meaning
QS_ALLEVENTS An input WM_TIMER, WM_PAINT, WM_HOTKEY, or posted message is in a queue.
QS_ALLINPUT Any message is in a queue.
QS_ALLPOSTMESSASGE A posted message (other than those listed here) is in the posted-message queue.
QS_HOTKEY A WM_HOTKEY message is in a queue.
QS_INPUT An input message is in the input queue.
QS_KEY A WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP, or WM_SYSKEYDOWN message is in the input queue.
QS_MOUSE A WM_MOUSEMOVE message or mouse-button message is in the input queue.
QS_MOUSEBUTTON A mouse-button message is in the input queue.
QS_MOUSEMOVE A WM_MOUSEMOVE message is in the input queue.
QS_PAINT A WM_PAINT message is in a queue.
QS_QUIT A Quit message is pending.
QS_POSTMESSAGE A posted message (other than those listed here) is in the posted-message queue.
QS_SENDMESSAGE A message sent by another thread or application is in the sent-message or reply-message queue.
QS_TIMER A WM_TIMER message is in a queue.


   
  You are probably wondering by now how the GetMessage function determines from which of possibly several nonempty queues to retrieve a message. The answer, according to Richter's book (with a few embellishments), is that GetMessage follows the steps outlined below, in this order. These steps also show the priority given to the various types of messages and explain why sent messages are taken before posted messages, for instance.  
   
  1. If the QS_SENDMESSAGE flag is on, the next sent message is retrieved from the sent-message queue, and the appropriate window procedure is called. This is repeated until no more sent messages remain. The QS_SENDMESSAGE flag is then turned off. GetMessage does not return during this process, and so no further code following the call to GetMessage is executed.  
   
  2. If the QS_POSTMESSAGE flag is on, the next posted message is retrieved from the post-message queue, and the MSG structure (the parameter to GetMessage) is filled. If this was the only posted message, the QS_POSTMESSAGE flag is cleared. In any case, the GetMessage function returns a nonzero value other than -1 (which is reserved to indicate an error).  
   
  3. If the QS_QUIT flag is set, the MSG structure is filled and the flag is cleared. GetMessage returns FALSE(0).  
Page 289
   
  4. If the QS_INPUT flag is set, the MSG structure is filled. The status of each of the input-related flags (QS_KEY, QS_MOUSEMOVE, etc.) is cleared if there are no more messages of the corresponding type in the input queue. GetMessage returns a nonzero value other than -1 (which is reserved to indicate an error).  
   
  5. If the QS_PAINT flag is set, the MSG structure is filled. GetMessage returns a nonzero value other than -1 (which is reserved to indicate an error).  
   
  6. If the QS_TIMER flag is set, the MSG structure is filled. The timer is reset. GetMessage returns a nonzero value other than -1 (which is reserved to indicate an error).  
   
  Note, in particular, that a WM_PAINT message is not processed until all sent, posted, and input messages are processed. Also, if several WM_PAINT messages pile up for the same window, they are consolidated by the system into a single message.  
 
  Posting and Sending Messages  
   
  The above description shows that all sent messages are processed before any posted messages (or other type of message) are processed. There is also an important distinction between the SendMessage and PostMessage functions.  
   
  The PostMessage function returns immediately with a Boolean value indicating success or failure of the posting, but the calling thread gets no information about the outcome of the message, or even whether or not the thread actually processed the message. (It could terminate before processing the message.)  
   
  Unlike the PostMessage function, SendMessage does not return (and so the calling thread waits) until the message has been processed. In this way, SendMessage can return information to the calling thread about the outcome of the message processing. For instance, the LB_GETTEXT message retrieves the text of an item in the listbox to which the message is sent. Obviously, posting an LB_GETTEXT message to a listbox is pointless.  
   
  Actually, the calling thread must do one thing while waiting for a return value from SendMessage it must (and does) process messages sent to it. After all, if it did not do this, then the target thread could send a message to the calling thread, in which case both threads would be sitting idle, waiting for the other thread to respond. Result: deadlock.  
   
  Setting a Timeout  
   
  If the calling thread does not want to wait indefinitely for the target thread to process its message, it can use the SendMessageTimeout function instead of SendMessage. This function has declaration as follows.  
  BOOL SendMessageTimeout(       HWND hWnd,           // handle of destination window       UINT Msg,            // message to send       WPARAM wParam,       // first message parameter       LPARAM lParam,       // second message parameter       UINT fuFlags,        // how to send the message       UINT uTimeout,       // timeout duration       LPDWORD lpdwResult   // return value for synchronous call     );
Page 290
   
  The first four parameters are the same as for SendMessage. The fuFlags parameter can be a combination of the following values:  
 
  SMTO_ABORTIFHUNG
Returns immediately if the receiving process appears to be hung.
 
 
  SMTO_BLOCK
Prevents the calling thread from processing any other messages until the function returns.
 
 
  SMTO_NORMAL
Does not prevent the calling thread from processing other messages while waiting for the function to return. This is the usual setting.
 
   
  The uTimeout parameter is the time to wait, in milliseconds. The final parameter contains return information and is message-specific.  
   
  Notification Messages  
   
  The SendNotifyMessage function:  
   BOOL SendNotifyMessage(        HWND hWnd,      // handle of destination window        UINT Msg,       // message to send        WPARAM wParam,  // first message parameter        LPARAM lParam   // second message parameter      );
   
  has the same signature as SendMessage, but has different behavior depending upon whether the target window is in a different thread or not. If the target window is in the calling thread, the function behaves just like SendMessage. However, if the target window is in a foreign thread, the function returns immediately, thus gathering no information about the results of the message. However, it differs from PostMessage in that the message sent has higher priority than posted messages.  
   
  The main role of SendNotifyMessage is to send notification messages. These are messages that notify the recipient of some action, but that do not require the sender to wait for a reply. These messages are very common, and Windows uses them often. For instance, WM_ACTIVATE, WM_DESTROY, WM_SIZE, and WM_MOVE messages are all notification messages. Also, as we mentioned earlier, a child  
Page 291
   
  control will often send a notification message to its parent, informing the parent of some action related to the child.  
 
  Example: Sending Messages to a Listbox  
   
  As you begin to write programs that manipulate the operating system more closely than VB generally permits, you will find various reasons to send messages. One of the main reasons is to have more control over controls. For instance, Visual Basic itself does not permit us to set tab stops in a listbox control, nor to add a horizontal scrollbar to a listbox. However, we can accomplish both of these tasks easily using SendMessage.  
   
  Setting Tab Stops  
   
  To set the tab stops in a listbox, we just send the listbox a LB_SETTABSTOPS message. According to the documentation, the message parameters are as follows:  
 
  wParam
The number of tab stops to set.
 
 
  lParam
A pointer to the first member of an array of integers (i.e., VB longs) containing the tab stops, in dialog template units. The tab stops must be sorted in ascending order.
 
   
  For this message, the SendMessage function returns True if the tabs are set correctly.  
   
  The documentation also mentions that in order to respond to the LB_SETTABSTOPS message, the listbox must have been created with the LBS_USETABSTOPS style. Fortunately, VB's ThunderListbox listboxes seem to have this style.  
   
  The documentation goes on to say that if cTabs is set to 0 and lpnTabs is NULL (also 0), the listbox will have a default tab stop set at intervals of two dialog box template units. Moreover, if cTabs is set to 1, the listbox will have default tab stops separated by the distance specified by lpnTabs. On the other hand, if the array pointed to by lpnTabs has more than a single value, a tab stop will be set for each value in the array.  
   
  The dialog box template units that this message uses are the device-independent units used in dialog box templates. To convert measurements from dialog box template units to pixels, we can use the MapDialogRect function. However, these units tend to be about one-fourth the average character width, and it generally suffices to just guess and adjust.  
Page 292
   
  To illustrate, Figure 16-3 shows the results of an LB_SETTABSTOPS message. For this, we need the declarations for SendMessage and LB_SETTABSTOPS:  
 
  Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
   ByVal hWnd As Long, _
   ByVal wMsg As Long, _
   ByVal wParam As Long, _
   ByRef lParam As Long _
) As Long
Public Const LB_SETTABSTOPS = &H192
 
   
  Example 16-1 shows the code to send the message.  
   
  Example 16-1. Using SendMessage to Set Tab Stops  
   
  Public Function SetTabstopsExample()

Dim i As Integer

 Set tab stops in List2 only
Dim Tabstops(1 To 4) As Long

Tabstops(1) = 4 * 10               approx 10 characters wide
Tabstops(2) = Tabstops(1) + 4 * 8
Tabstops(3) = Tabstops(2) + 4 * 6
Tabstops(4) = Tabstops(3) + 4 * 4
SendMessage List2.hWnd, LB_SETTABSTOPS, 4, Tabstops(1)

 Fill listboxes
For i = 1 To 4
   List1.AddItem  Item1  & vbTab &  Item2  & _
      vbTab &  Item3  & vbTab &  Item4  & vbTab &  Item5
   List2.AddItem  Item1  & vbTab &  Item2  & _
      vbTab &  Item3  & vbTab &  Item4  & vbTab &  Item5
Next

End Sub
 
   
  Setting the Horizontal Extent  
   
  To quote the documentation:  
  An application sends an LB_SETHORIZONTALEXTENT message to set the width, in pixels, by which a listbox can be scrolled horizontally (the scrollable width). If the width of the listbox is smaller than this value, the horizontal scroll bar horizontally scrolls items in the listbox. If the width of the listbox is equal to or greater than this value, the horizontal scroll bar is hidden.  
   
  For this message, the wParam parameter is set to the scrollable width, in pixels. For Windows 9x, this is limited to a 16-bit value (are they kidding?) and lParam is 0. There is no return value.  
   
  The declaration for LB_SETHORIZONTALEXTENT is:  
 
  Public Const LB_SETHORIZONTALEXTENT = &H194  
Page 293
   
  0293-01.gif  
   
  Figure 16-3.
Setting tab stops in a listbox
 
   
  The following code results in the listbox shown in Figure 16-4:  
 
  List1.AddItem "I would I could quit all offenses with as clear" & _
              "excuse as well as I am doubtless I can purge " & _
              "myself of many I am charg'd withal."

SendMessage List1.hWnd, LB_SETHORIZONTALEXTENT, 500&, 0&
 
   
  0293-02.gif  
   
  Figure 16-4
Setting the horizontal extent of a listbox
 
   
  Extracting Listbox Data  
   
  From time to time, I have found the need to extract the entries from a listbox or combo box of another application. This has been very useful to me in writing books. For instance, in my books on programming Microsoft Access, Word, and Excel, and on creating Visual Basic Add-ins, I have wanted to grab a list of the properties or methods of various objects in the corresponding object models.  
Page 294
   
  Figure 16-5 shows the Microsoft Word Visual Basic help system, with an open Topics Found dialog box for the properties of the Range object. Since this object has 65 properties, I find it helpful to extract the list of these properties so that I can peruse it more easily than scrolling through the ridiculously small Topics Found listbox. Figure 16-5 also shows the rpiControlExtractor application, which has extracted the list of properties. Of course, once this list is in my own listbox, I can do what I want with it. (This application should probably be called the Control Data Extractor, since it doesn't extract controls, it extracts a control's data, but I opted for the shorter term.)  
   
  0294-01.gif  
   
  Figure 16-5.
The Control Extractor at work
 
   
  The Control Extractor application can extract data from a listbox, a combo box, or a ListView control. The former controls (the listbox and combo box) are old-style controls that existed in 16-bit Windows, whereas the ListView control is a newer 32-bit control. As we will see, this has a profound effect on the problem of getting the data from a foreign control (control in another process).  
   
  In fact, we will need to postpone a discussion of the code required to extract data from a foreign ListView control until Chapter 20, DLL Injection and Foreign Process Access. Also, the process of extracting from combo boxes is very similar to that of listboxes, so we will consider only the latter.  (Of course, the source code for rpiControlExtractor contains code for all three types of controls.)  
   
  For a listbox, control data extraction is done by sending a LB_GETTEXT message to the control. To send this message, we set the wParam message parameter to  
Page 295
   
  the zero-based index of the item to retrieve and 1Param to the address of a memory buffer in which to place the item.  
   
  Example 16-2 contains the code that executes when the user presses the (Extract From) Listbox command button. It assumes that the handle of the listbox is in the global variable hControl.  
   
  Example 16-2. Extracting the Contents of a Listbox  
   
  Sub ExtractFromListBox()

Dim cItems As Integer
Dim i As Integer
Dim sBuf As String
Dim cBuf As Long
Dim 1Resp As Long

 Get item count from control
cItems = SendMessageByNum(hControl, LB_GETCOUNT, 0&,0&)

 Display count in label
1b1Items =  Items:  & cItems

If cItems <= 0 Then Exit Sub

 Clear listbox first?
If chkClearList.Value = 1 Then 1stMain.Clear

 Put items into listbox
For i = 0 To cItems - 1

    Get length of item
   cBuf = SendMessageByString (hControl, LB_GETTEXTLEN, CLng(i), 0)

     Allocate buffer to hold item
   sBuf = String$(cBuf + 1,    )

     Send message to get item
   1Resp = SendMessageByString(hControl, LB_GETTEXT, CLng(i), sBuf)

    Add item to local listbox
   If 1Resp > 0 Then
      1stMain.AddItem Left $ (sBuf, 1Resp)
    End If

Next i

1stMain.Refresh

End Sub
 
Page 296
 
  Interprocess Marshalling  
   
  The issue of interprocess communication is a very important one to those of us who like to hack. After all, much of what we want to do involves invading a foreign process sending commands and sending or receiving data.  
   
  Referring to the ExtractFromListbox procedure, the LB-GETTEXTLEN message works across process boundaries because the only data returned is the return value of the function. This value is returned on the calling process's stack. In particular, it does not require allocating a memory buffer.  
   
  On the other hand, the LB_GETTEXT  message requires the address of a memory buffer. Now, the calling process can allocate a memory buffer only its own memory space, not in the address space of the process that contains the listbox. But the window procedure of the foreign process thinks that the address in the  1Param parameter is in its own address space (that is, the only address space it knows about), so it will try to place the item at that location in its address space. It would certainly seem that this will cause an access violation. In any case, it certainly won't return the item text to the calling process. However, the code works, and the listbox item is in fact placed in the buffer of the calling process!  
   
  The reason is that Windows actually watches out for certain messages, such as LB_GETTEXT, and is kind enough to marshall the data (in this case the listbox item) from the target address space to the calling address space. (Marshalling means packaging data and sending it across process boundaries. This happens a lot in OLE Automation.)  
   
  It is not clear exactly which messages are automatically marshalled by Windows and which are not, so you need to experiment. For instance, buffers for messages sent to older-style 16-bit controls, such as listboxes and combo boxes, appear to be marshalled in order to preserve compatibility with 16-bit Windows, where marshalling was not even an issue (it was not required).  
   
  However, Windows does not marshall data extracted from a new 32-bit ListView control, since in this case there is no compatibility issue. The Control Extractor application will need to find another approach. Indeed, we will need to find some way to allocate memory in the foreign process and then copy data to and from this foreign memory, in particular, when we come to write the rpiControlExtractor code for extracting from a foreign ListView control.  
   
  Actually, the hard part can be the allocation of foreign memory. Note that under Windows NT, this is not difficult, because Windows NT implements the function VirtualAllocEx:  
  LPVOID VirtualAllocEx(       HANDLE hProcess,         // process within which to allocate memory       LPVOID lpAddress,        // desired starting address of allocation       DWORD dwSize,            // size, in bytes, of region to allocate       DWORD flAllocationType,  // type of allocation       DWORD flProtect          //  type of access protection     );
Page 297
   
  which is designed specifically for this purpose! Unfortunately, this function is not implemented in Windows 9x. We will need to go to considerably more trouble to allocate foreign memory in a way that will work in all versions of Windows. As mentioned, this will be done in Chapter 20.  
 
  Copying Data Between Process  
   
  Once foreign memory has been allocated, there are a variety  of ways to copy data between the processes. In fact, I find it a bit curious that Microsoft has made it easy to copy data between processes, but not to set up a foreign memory buffer to hold that data. Let us discuss some of these methods now. We will have occasion to use them later in the book.  
   
  One possibility for interprocess data transfer is to use WriteProcessMemory and ReadProcessMemory. The WriteProcessMemory function is:  
 
  BOOL WriteProcessMemory(
   HANDLE hProcess,      // handle to process whose memory is written to
   LPVOID 1pBaseAddress, // address to start writing to
   LPVOID 1pBuffer,      // Pointer to buffer to write data to
   DWORD nSize,          // number of bytes to write
   LPDWORD 1pNumberOfByteWritten // actual number of bytes written
);
 
   
  Note that the foreign process handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access to the foreign process. Such a handle can be obtained by using the OpenProcess function, discussed in Chapter 11, Processes The ReadProcessMemory function is:  
 
  BOOL ReadProcessMemory(
   HANDLE hProcess,              // handle to the process whose memory is read
   LPCVOID 1pBaseAddress,        // address to start reading
   LPVOID 1pBuffer,              // address of buffer to place read data
   DWORD nSize,                  // number of bytes to read
   LPDWORD 1pNumberOfBytesRead      // address of number of bytes read
);
 
   
  In the case of ReadProcessMemory, the handle must have PROCESS_VM_READ access to the foreign process.  
   
  Another way to send data to a foreign process is to use the SendMessage function to send a WM_COPYDATA message. In this case, the wParam parameter is a handle to a foreign window (that is, a window in the foreign process), and the 1Param parameter is a pointer to a COPYDATASTRUCT structure:  
 
  typedef struct tagCOPYDATASTRUCT {
    DWORD dwData;
 
 

Page 298
 
      DWORD cbData;
    PVOID lpData;
} COPYDATASTRUCT;
 
   
  The dwData parameter can be any 32-bit data value; cbData is the number of bytes to send; and lpData is a pointer to a buffer, in the calling process that contains the cbData bytes to send.  
   
  The correct form of SendMessage is:  
 
  SendMessage(hTargetWindow, WM_COPYDATA, hSendingWindow, _
            Address of COPYDATASTRUCT)
 
   
  Sending this message causes Windows to create a buffer in the foreign process, place the data into that buffer, and adjust the value of lpData to point to that foreign buffer. (Of course, that buffer is not foreign to the target window's window procedure.)  
   
  We will see an example of using this message when we discuss hooks in Chapter 19, Windows Hooks. Here are some points to keep in mind:  
   
  We must use SendMessage to send this message and not PostMessage. The reason is that Windows will deallocate the foreign buffer as soon as the message is processed, and the procedure is not designed to wait around for a posted message to be processed.  
   
  The data in the buffer (as well as dwData) should not contain pointers, for the simple reason that the target of any such pointers is not marshalled and is thus not accessible to the foreign process.  
   
  The target process should consider the data as being read-only. The final parameter to SendMessage, that is, the pointer to the foreign copy of COPYDATASTRUCT, is valid only during the processing of the message. The target process should not free this memory. Also, if the target process wants to save the data, it should copy that data to another location.  
 
  The Local Input State  
   
  Under Win32, each thread in the system is a kind of virtual computer in itself. Accordingly, each thread needs to at least think it has sole possession of the keyboard, mouse, monitor, and other hardware.  
   
  To put this into effect, each thread is given its own local input state as part of its THREADINFO structure, as shown in Figure 16-1. This consists of the following information related to the focus, the keyboard, and the mouse:  
   
  The currently active window for the thread.  
   
  The window that has the keyboard input focus (or just  
   
  input focus).  
Page 299
   
  The current state of the keyboard (for example, is the Alt key pressed or is CAPLOCKS on?).  
   
  The current state of the caret (the bitmap that marks the location of the insertion point).  
   
  The window that currently has the mouse capture, that is, the window that currently receives mouse messages. (It need not be the window underneath the mouse pointer.)  
   
  The current cursor (the bitmap that marks the location of the mouse pointer) and its visibility.  
   
  It is important to understand that the notions of active window and input focus exist for each thread separately. In other words, each thread owns a window that it considers the active window and a window that it considers as having the input focus. On the other hand, the user sees the computer as a whole, not as a collection of separate virtual computers. To the user, only one window is ''active" at a time and only one window has the "input focus." Here are the facts.  
   
  The Foreground Thread  
   
  At any given time, the Raw Input Thread (RIT) directs keyboard input and mouse input to the virtual input queue of one thread. The thread that is currently receiving this attention is called the foreground thread. When the user switches applications, the RIT will redirect output to a different thread, thus causing a change in which thread is the foreground thread. The API function SetForegroundWindow can be used to change which thread is the foreground thread.  
   
  Recall, however, our discussion in Chapter 11, concerning the change in behavior of SetForegroundWindow under Windows 98 and Windows 2000. To reiterate, under Windows 95 and Windows NT 4, the SetForegroundWindow function does bring an application to the foreground. However, under Windows 98 and Windows 2000, all that happens is that the target application's main window will become the active window for that thread and the titlebar will flash! Recall also that we discussed the rpiSetForegroundWindow function that is designed to duplicate the original functionality of SetForegroundWindow.  
   
  Keyboard Input  
   
  For each thread, at any given time, there is at most one window that would be processing keyboard messages if the thread happened to be the foreground thread. The window that processes keyboard messages (if any) is said to have the input focus for the thread. The input focus for a given thread can be changed by calling SetFocus. Also, the handle of the window that currently has the input focus can be retrieved by calling GetFocus. Again, we emphasize that this is on a per-thread  
Page 300
   
  basis. (We will consider an example of keyboard input in the "Experiments" section later in the chapter.)  
   
  Mouse Capture  
   
  Mouse input is a bit more involved than keyboard input. The reason is that a mouse-drag operation starts at one location and ends at another location, and these two locations need not be over windows in the same thread!  
   
  For each thread, there are essentially three possible states with regard to mouse focus:  
   
  No mouse capture  
   
  Thread-wide mouse capture  
   
  System-wide mouse capture  
   
  Under normal conditions (no button pressed and no strange code executing), no window has the mouse capture. In this case, any mouse message are placed in the input queue of the thread that owns the window under the mouse cursor at the time the message was generated, and the message is then processed by that window.  
   
  Whether or not the window does anything with this message is another story. In VB, we must place code in the MouseDown, MouseUp, or MouseMove events if we wish to process the mouse messages. In VC++, the programmer must place code in the appropriate window procedure.  
   
  On the other hand, mouse capture can take place on two levels: system-wide and thread-wide.  
   
  When we click on a control in a VB project, that control is given a system-wide mouse capture until we release the button. The reason is that clicking on a control may be preparatory to dragging the mouse pointer (as in drawing), in which case the initial window should receive all mouse messages until and including the release of the mouse button that signals the end of the drawing procedure. (Visual C++ programmers must arrange for mouse capture by calling SetCapture when a mouse button is depressed VB does that for us.)  
   
  The SetCapture function can also be used to give a window the mouse capture, but this is only a thread-wide capture. If the mouse ventures over a window in a foreign thread, that window will begin receiving mouse messages. However, as soon as the mouse returns to any window owned by the thread that called SetCapture, the window with the mouse capture will receive all mouse messages. To turn off mouse capture for that thread, we must call ReleaseCapture. (We will consider an example of mouse capture in the "Experiments" section later in the chapter.)  
Page 301
   
  Active Windows and the Foreground Window  
   
  As we have seen, Windows uses a parent-child relationship for its windows. Thus, one window can be the child of another window. Windows that do not have parents are called top-level windows. In each thread, the top-level window that is the parent of the window with the keyboard input focus is said to be the active window for the thread.  
   
  The foreground window is the active window for the foreground thread. It is the window that typically has the blue titlebar, to distinguish it from other windows. Unfortunately, much of the documentation uses the term active window for the foreground window. (This is true, for instance, in the Appearance tab of the Display applet on the Control Panel.) However, it is vital to maintain a distinction between an active window and the foreground window.  
   
  It should be clear from this discussion that using SetFocus works only within a given thread. Thus, it cannot be used to switch applications, that is, to set the foreground application. Under Windows 95 and Windows NT 4, SetForeground-Window can be used in this way, since it changes the foreground window and therefore redirects hardware input to the thread that owns the target window. However, see the discussion about the change in behavior of SetForeground-Window for Windows 98 and Windows 2000 in the section "The Foreground Thread" earlier in the chapter.  
   
  Experiments  
   
  The best way of examining the Windows messaging system and learning how it handles user input is to experiment. Accordingly, this section contains three experiments that allow you to see that different windows can have the input focus and the mouse capture at the same time; to determine that each thread has its own input focus; and to examine the difference between system-wide and thread-wide mouse capture.  
   
  Experiment one  
   
  To see that the window with the input focus and the window with the mouse capture can be different, you can create a simple VB project as follows. Create a form with one Command button and two text boxes. Add the code shown in Example 16-3.  
   
  Example 16-3. The Window with the Input Focus and the Mouse Capture  
   
  Option Explicit
Private Declare Function SetCapture Lib  user32  (ByVal hwnd As Long) _
                         As Long
Private Declare Function SetFocusAPI Lib  user32  Alias  SetFocus  _
                         (ByVal hwnd As Long) As Long
 
Page 302
   
  Example 16-3. The Window with the Input Focus and the Mouse Capture (continued)  
   
  Private Declare Function GetActiveWindow Lib  user32  () As Long

Private Sub Command1_Click()
 Set input focus to Text1
SetFocusAPI Text1.hwnd
 Set mouse capture to Text2
SetCapture Text2.hwnd
Debug.Print GetActiveWindow = Me.hwnd
End Sub

Private Sub Text2_MouseMove(Button As Integer, Shift As Integer, _
                            X As Single, Y As Single)
Text2 = X
End Sub
 
   
  Now start the project and click the command button. Then move the mouse and type at the keyboard at the same time. You should see numbers changing in Text2 and keystrokes being added to Text1. This shows that Text1 has the input focus, but Text2 has the mouse capture (both relative to this thread).  
   
  Experiment two  
   
  You can also perform an interesting experiment that demonstrates the fact that each thread has its own input focus as follows. Create a VB project with a form that contains a single text box and a single timer control (see Figure 16-6).  
   
  0302-01.gif  
   
  Figure 16-6.
Demonstrating input focus
 
   
  Set the timer's Interval property to 1000. Add the code from Example 16-4 to the form.  
   
  Example 16-4. Establishing that Each Thread Has Its Own Input Focus  
   
  Option Explicit
Private Declare Function SetFocusAPI Lib  user32  Alias  SetFocus  _
                          (ByVal hwnd As Long) As Long
Private Declare Function GetActiveWindow Lib  user32  () As Long

Private Sub Timer1_Timer()
 Set input focus to Text1
 
Page 303
   
  Example 16-4. Establishing that Each Thread Has Its Own Input Focus (continued)  
   
  SetFocusAPI Text1.hwnd
 Display active window handle
Text1 = GetActiveWindow
End Sub
 
   
  Now repeat this with a second VB project, renaming the form's caption to Form2 to distinguish it from the form in the first project. Run both projects. After 1 second, both text boxes should have a blinking cursor, indicating that they both have the input focus, but for different threads. (Figure 16-6 does not do this justice.) Moreover, each thread has its own active window, as indicated by the different handles in each text box. Only one of the two forms can be the foreground window, however, as indicated by the blue titlebar.  
   
  Experiment three  
   
  To demonstrate mouse capture, create a project with a single form, as in Figure 16-7. Place a timer on the form as well, and set the interval property to 250. The complete code for the project appears in Example 16-5.  
   
  Example 16-5. System-wide and Thread-wide Mouse Capture  
   
  Option Explicit
Private Declare Function SetCapture Lib  user32  (ByVal hwnd As Long) _
                                                As Long
Private Declare Function GetCapture Lib  user32  () As Long
Private Declare Function ReleaseCapture Lib  user32  () As Long

Private Sub Command1_Click()

 Set mouse capture to Text1
SetCapture Text1.hwnd

End Sub

Private Sub Command2_Click()
ReleaseCapture
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, _
                         X As Single, Y As Single)
Text1 = X + Y
End Sub

Private Sub Text1_MouseMove(Button As Integer, Shift As Integer, _
                            X As Single, Y As Single)
Text1 = X + Y
End Sub

Private Sub Timer1_Timer()
txtCapture = GetCapture
End Sub
 
Page 304
   
  0304-01.gif  
   
  Figure 16-7.
Demonstrating mouse capture
 
   
  Now, as you move the mouse over the form, the value in Text1 (the upper text box) will change, indicating that the form is processing WM_MOUSEMOVE messages. However, the GetCapture function is reporting 0, meaning that no window has the mouse capture. This is why the value in Text1 does not change when the mouse pointer goes over the Command button or one of the text boxes, or goes outside of the form. This is normal (noncapture) mouse operation.  
   
  Next, hold down the left mouse button while the mouse pointer is over the form and drag the pointer around the screen, especially to a window belonging to another application. Notice that the handle of the form is placed in Text2, and Text1 gets the value of X+Y no matter where the pointer goes. Thus, VB has given the form system-wide mouse capture.  
   
  Finally, click the Command button. This gives a thread-wide mouse capture to Text1 as a result of the call to GetCapture. Move the mouse around the screen again, including over foreign windows. As long as the mouse is over any window in the current VB project (thread), the values in Text1 change, but as soon as the mouse ventures over a foreign window, the changes stop since the mouse is no longer captured by the Command button.


WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net