Introduction
wfspy is a tool that helps you to view properties of any Windows Form control in the system. I originally needed a small utility that will give me the managed control type name and the assembly name from a window handle, but gradually the tool became sophisticated enough to show all properties of a managed control (and optionally modify them). The only feature currently missing is the spying of events, which I plan to add later on.
wfspy uses Windows hooks to do its job. There are 3 assemblies in the project:
wfspy
- a C# executable. This is the main application which has the UI code. wfspylib
- a C# class library that contains utility functions and controls. This library gets injected into the process where the window belongs. wfspyhook
- a managed C++ class library (.dll) which has code to inject a managed assembly into a process.
Using wfspy
The tool itself is pretty simple. The main window shows all the managed windows and their hierarchy, with the desktop window as the root. Any unmanaged window which is not parent of a managed window directly or indirectly, is not shown in the tree. The managed windows are shown using a slightly different icon. You can view the properties of the managed window by clicking on the details button in the main form. This brings up the dialog box with the property grid as shown in the second screen shot. The properties can even be modified in the grid. The next few sections discuss some important aspects of the implementation.
Enumerating Managed Windows
To enumerate managed windows, the standard Win32 API function EnumChildWindows
is used. Unmanaged windows are filtered out from the tree if they don't parent any manage windows. In order to find out whether a window is managed or not, its class name is inspected. Any managed window that derives from System.Windows.Forms.Control
has class name of the form WindowsForms10.<character sequence>.app<hash code of appdomain>
. The character sequence in the middle is the class name of the window that is being superclassed like Button
, SysListView32
, etc. The following code determines whether a class name is managed or not.
private static Regex classNameRegex = new
Regex(@"WindowsForms10\..*\.app[\da-eA-E]*$",
RegexOptions.Singleline);
public static bool IsDotNetWindow(string className)
{
Match match = classNameRegex.Match(className);
return (match.Success);
}
Injecting a .NET Assembly into a Process
The technique to inject a .NET assembly in another process is slightly tricky as unlike functions in a regular Win32 DLL. .NET functions are compiled into native code during run time so the addresses are not static. This problem can be overcome by using managed C++, which allows managed global functions to be exported from an assembly. The exported function is actually a thunk that at runtime, points to the code generated by the JIT compiler. Thus the exported function can be used as a hook procedure. That function can be used to load another assembly. I will cover this technique in detail in another article.
Viewing Properties of a Control
Given an HWND
, the managed Control
object can be found from the Control.FromHandle
method. The properties of the control object can then be viewed in a property grid. There is a problem here the property grid should be created in the process where control object belongs. Luckily, Windows allows a process to create child windows to a parent window belonging to another process. This technique is used to create the property grid from the target process, as a child of a form in the wfspy
process. In order to do this, the property grid control is placed in a user control. The CreateParams
property of the user control is overridden.
protected override CreateParams CreateParams
{
get
{
System.Windows.Forms.CreateParams cp = base.CreateParams;
cp.Parent = parentWindow;
RECT rc = new RECT();
UnmanagedMethods.GetClientRect(parentWindow, ref rc);
cp.X = rc.left;
cp.Y = rc.top;
cp.Width = rc.right - rc.left;
cp.Height = rc.bottom - rc.top;
return cp;
}
}
The parentWindow
is actually the handle of a form belonging to the wfspy
process. This enables editable property view of a control in the wfspy
application.
Known Bugs/ Remarks
The included wfspy
works only for .NET windows 1.0.3705. If you want the tool to work with .NET 1.1, please build the code using VS.NET 2003. If you use wfspy
to view a window in an application that uses a different .NET version, strange results may occur. Suggestions are welcome on how to fix this bug. I intend to update the article soon with the ability to spy control events.