Thursday, February 22, 2007

Mobile Spy

Many Windows and Windows Mobile developers have at one time used the Microsoft Spy++ or the Microsoft CE Remote Spy tools. The Microsoft Spy++ tool allows a developer to view properties of selected windows, threads, processes, or messages using a graphical tree. Remote Spy displays a list of the windows that are open on a particular device that is connected. In addition to the list of windows, Remote Spy also provides the capability to view properties of a window such as the class, style, extended style, window procedure address, window positioning, and client area window positioning as well as the messages within the message queue for a selected window.

Image 1 - Microsoft CE Remote Spy



Remote Spy is an excellent application which is extremely useful for any mobile developer. What happens though if an individual wants to see the listing of top-level windows currently running on a device and does not have the ability to connect the device to another machine where Remote Spy can be run? Here's a simple application that performs that functionality.

Image 2 - Mobile Spy



This application (which has been named Mobile Spy just to differentiate) shows a listing of all the top level windows running on a device at a particular moment in time. Along side the window handle of the top-level window, Mobile Spy also displays the Window Text, and the Class Name of the class to which a window belongs.

This application uses three P/Invoked methods to retrieve the window handles, the window text, and the window class names:


  • GetWindow - This function retrieves the handle to a window that has the specified relationship to the specified window.

  • GetWindowText - This function copies the text of the specified windows title bar—if it has one—into a buffer. If the specified window is a control, the text of the control is copied.

  • GetClassName - This function retrieves the name of the class to which the specified window belongs.



(Source for all above descriptions comes directly from MSDN)

The application contains a simple button and a simple list view with three columns. The "Refresh" button loads the windows in at a specific point in time. Here is code of the method associated with the click event of the "Refresh" button as well as an associated explanation:

lvWindows.BeginUpdate();
lvWindows.Items.Clear();
IntPtr window = GetWindow(this.Handle, GW_HWNDFIRST);
StringBuilder className = new StringBuilder(MAX_LENGTH);
StringBuilder windowText = new StringBuilder(MAX_LENGTH);
while (window != IntPtr.Zero)
{
int classNameCount = GetClassName(window, className, MAX_LENGTH);
int windowTextCount = GetWindowText(window, windowText, MAX_LENGTH);
string windowTextDisplay = NO_NAME;
if (windowTextCount > 0)
{
windowTextDisplay = windowText.ToString();
}
string hexWindow = String.Format("{0:X2}", window.ToInt32());
ListViewItem item = new ListViewItem(new string[] { hexWindow,
windowTextDisplay, className.ToString() });
lvWindows.Items.Add(item);
window = GetWindow(window, GW_HWNDNEXT);
}
lvWindows.EndUpdate();


The method begins with a call to lvWindows.BeginUpdate() and lvWindows.Items.Clear(). lvWindows is the name of the ListView where the information is displayed. Calling BeginUpdate prevents the control from redrawing until EndUpdate is called. The next call is made to clear the items from the list view. Be cognizant of the difference between calling lvWindows.Clear() and lvWindows.Items.Clear().

The next line is where it starts to get interesting with a call to GetWindow using the window handle of the Mobile Spy application and GW_HWNDFIRST (defined as 0). Using the GW_HWNDFIRST constant retrieves the handle of the window highest in the Z-order. Because GetWindow is passed a top-level window, it retrieves the topmost window in the Z-order.

The application then sets up a while loop and iterates as long as the returned window handle is not zero. If the window handle is not zero, the application retrieves the Class Name and the WindowText using a StringBuilder for each call respectively. The return value of both the GetClassName and GetWindowText functions are the count of characters copied into the StringBuilder passed in. If the window text returned is a length of zero, "<No name>" is used for display purposes (just as in Remote Spy).

A list view item is setup using the window handle, the window text, and the class name. The window handle is slightly modified to be displayed in its hexadecimal notation (just as in Remote Spy). The window text is displayed as explained above and the class name is displayed as returned by the GetClassName function. The list view item is added to the list view.

When the application is done processing for a particular window handle, it attempts to retrieve the next window handle. The next window handle within the Z-order is retrieved via a call to GetWindow using the current window handle and GW_HWNDNEXT (defined as 2). GW_HWNDNEXT retrieves the window below the specified window in the Z-order. If this window handle retrieved is zero, the loop will exit on the next iteration. If this window handle is non-zero, the inner section of the loop will retrieve the window text and the class name and add the information to the list view.
Finally the list view is allowed to resume drawing because of the EndUpdate() method call.

No comments: