在Windows应用程序开发中,经常需要查看和修改窗体控件的属性。wfspy是一款功能强大的工具,它可以帮助开发者查看和编辑任何Windows窗体控件的属性。最初,开发者需要一个小工具来获取控件的类型名称和程序集名称,但随着时间的推移,wfspy逐渐发展成为一个功能更加完善的工具,可以显示所有控件的属性,并允许用户进行修改。目前,该工具尚未实现事件监听功能,但开发者计划在未来的版本中加入这一功能。
wfspy通过使用Windows钩子来实现其功能。该项目包含三个程序集:
wfspy工具本身非常简单。主窗口显示所有托管窗口及其层次结构,桌面窗口作为根节点。任何未托管的窗口,如果不是直接或间接托管窗口的父窗口,则不会显示在树中。托管窗口使用略有不同的图标显示。用户可以通过点击主窗体中的“详细信息”按钮来查看托管窗口的属性。这将弹出一个带有属性网格的对话框。用户甚至可以在网格中修改属性。接下来的几个部分将讨论实现的一些重要方面。
为了枚举托管窗口,wfspy使用标准的Win32 API函数EnumChildWindows。如果未托管窗口不是任何托管窗口的父窗口,则它们会被过滤出树。为了确定一个窗口是否托管,需要检查其类名。任何从System.Windows.Forms.Control派生的托管窗口的类名格式为WindowsForms10.<字符序列>.app<应用程序域的哈希码>。中间的字符序列是正在被超类的窗口的类名,例如Button、SysListView32等。以下代码用于确定类名是否为托管。
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);
}
将.NET程序集注入到另一个进程中需要一些技巧,因为与常规Win32 DLL中的函数不同,.NET函数在运行时被编译成原生代码,因此地址不是静态的。这个问题可以通过使用托管C++来解决,它允许从程序集中导出托管全局函数。导出的函数实际上是一个在运行时指向JIT编译器生成的代码的thunk。因此,导出的函数可以用作钩子过程。该函数可以用来加载另一个程序集。将在另一篇文章中详细介绍这种技术。
给定一个HWND,可以通过Control.FromHandle方法找到托管的Control对象。然后可以在属性网格中查看该控件对象的属性。这里有一个需要注意的问题,属性网格应该在控件对象所属的进程中创建。幸运的是,Windows允许一个进程创建子窗口到属于另一个进程的父窗口。这种技术被用来从目标进程创建属性网格,作为wfspy进程中一个窗体的子窗口。为了实现这一点,属性网格控件被放置在一个用户控件中。用户控件的CreateParams属性被覆盖。
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;
}
}