使用UIAutomation开发软件外挂

原文使用UIAutomation开发软件外挂

         UIAutomation是.Net 3.5之后提供的“界面自动化测试”技术,本来是给测试人员用的,不过UIAutomation由于也是界面自动操作的技术,比直接使用keybd_event、GetWindowText等Win32的API进行界面模拟操作简单很多,因此也可以用UIAutomation做软件的“外挂”。

         我手头正好有这样一个需求,如鹏网有一个内部使用的一个工具(购买的第三方软件),用于根据学生的机器码计算“播放密码”,这个工具只提供了图形化的界面:

 

         输入机器码之后点击【创建播放密码】按钮就能生成播放密码。

         如鹏网第二期学习辅助系统的开发中需要开发“自动生成播放密码”的功能,也就是学生在浏览器中输入他的机器码,网站自动计算他的播放密码。

         由于这个工具只提供了图形化的界面,没有提供API,所以我就想到使用模拟点击的方法来进行“自动化”,直接使用Win32太麻烦,AutoIt使用还要注册组件,因此就想到了UIAutomation。

         完成的效果如下:

  

         下面分享一下主要技术。

         学习UIAutomation之前一定要知道,Windows中的程序界面元素都是由“窗口组成的”(DirectUI等除外),按钮、文本框等都是窗口,窗口之间也有父子关系。Windows桌面是所有窗口的根窗口。

         UIAutomation支持普通Win32程序(不是VC++、.Net开发的也支持,因为本质上都是Win32程序)和WPF程序,但是不支持普通的DirectUI窗口(比如QQ、浏览器)。

         使用UIAutomation之前先要添加对UIAutomationClient、 UIAutomationProvider、 UIAutomationTypes三个程序集的引用。所有的界面元素都是由AutomationElement组成,每个窗口就是一个AutomationElement,因此AutomationElement之前也有父子结构。

         可以使用AutomationElement.RootElement获得桌面的根元素;使用AutomationElement.FromHandle(IntPtr hwnd)从Win32窗口句柄拿到AutomationElement对象。

         拿到一个AutomationElement通常要遍历他的子元素。遍历子元素之前需要先了解“遍历条件”的概念,遍历条件就是按照什么样的条件去搜索子元素。所有的条件都继承自Condition类,Condition类的主要子类有PropertyCondition、AndCondition 、NotCondition 、OrCondition,这些之类之间可以进行复合的组合,形成各种复杂的遍历条件。

         PropertyCondition是根据属性的名字和值进行过滤的。它构造函数的第一个参数为属性的名字,所有支持的属性都在AutomationElement的***Property这些静态成员中;构造函数的第二个参数为被比较的值。又可以使用AndCondition、NotCondition、OrCondition把各个条件进行复杂的逻辑组合。比如下面的conditionBtn9就是“类名为Button并且名字为9”的条件:

1
2
3
4
Condition conditionBtn9 = new AndCondition(
                new PropertyCondition(AutomationElement.ClassNameProperty, "Button"),
                new PropertyCondition(AutomationElement.NameProperty, "9")
                );

  

         Condition类有两个固定的值,Condition. TrueCondition代表永远为True的条件,Condition. FalseCondition代表永远为False的条件(应该很少用)

         我们可以使用AutomationElement的FindAll或者FindFirst方法进行元素的遍历。FindAll是获取所有符合遍历条件的AutomationElement,因此是返回AutomationElementCollection集合,而FindFirst是返回第一个符合遍历条件的AutomationElement,因此是返回AutomationElement。

         FindFirst、FindAll的第一个参数代表搜索的范围,最常用的就是TreeScope.Children和TreeScope.Descendants,TreeScope.Children代表在直接子节点中搜索,而TreeScope.Descendants代表递归的在所有子孙节点中搜索。FindFirst、FindAll的第二个参数代表搜索条件。

         定位到要操作的AutomationElement之后,可以进行模拟点击(比如按钮)或者读写值(比如输入框)。比如下面的代码中element指向的是一个按钮,下面的代码就是模拟点击这个按钮:

1
2
var clickPattern = (InvokePattern)element.GetCurrentPattern(InvokePattern.Pattern);
clickPattern.Invoke();

  

    比如下面的代码中element指向的是一个文本框,下面的代码就是使用字符串填充这个输入框:

1
2
ValuePattern valuePattern = (ValuePattern)element.GetCurrentPattern(ValuePattern.Pattern);
valuePattern.SetValue(“如鹏网”);

  

    下面是我实现的一个模拟点击计算器计算两个数的乘法的数:

1
2
3
4
5
6
7
8
9
10
11
AutomationElement desktop = AutomationElement.RootElement;
var calcFrame1 = desktop.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ClassNameProperty, "CalcFrame"));
 
ClickCalcButton(calcFrame1, "3");
ClickCalcButton(calcFrame1, "6");
ClickCalcButton(calcFrame1, "5");
ClickCalcButton(calcFrame1, "*");
ClickCalcButton(calcFrame1, "1");
ClickCalcButton(calcFrame1, "2");
ClickCalcButton(calcFrame1, "=");

  

其中ClickCalcButton是我封装的一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void InvokeButton(AutomationElement e)
{
    InvokePattern invoke = (InvokePattern)e.GetCurrentPattern(InvokePattern.Pattern);
    invoke.Invoke();
}
 
private static void ClickCalcButton(AutomationElement calcFrame1, string name)
{
    Condition conditionBtnPlus = new AndCondition(
       new PropertyCondition(AutomationElement.ClassNameProperty, "Button"),
       new PropertyCondition(AutomationElement.NameProperty, name)
       );
    var btn = calcFrame1.FindFirst(TreeScope.Descendants, conditionBtnPlus);
    if (btn == null)
    {
throw new Exception("找不到名字为"+name+"的计算器按钮");
    }
    InvokeButton(btn);
}

  

     文章篇幅有限,特别是对于一些没有Win32基础的朋友,光看上面的文字会不太容易懂,因此我录制了一套大约90分的视频教程,详细的讲解了UIAutomation的使用,希望能够帮到大家。

     视频教程地址如下 http://www.rupeng.com/Courses/Chapter/298

 

Leave a Reply