做RPA最怕碰到的就是这种事:客户系统在本地跑得好好的,对方是一个老牌的C++桌面客户端,启动快、内存占用低、界面流畅,唯一的问题就是自动化脚本一进去,Inspect 一扫描,元素树里只有一个孤零零的窗口节点,里面什么都没有。按钮、输入框、列表、下拉框,全部隐身。这就是典型的 duilib 自绘UI给RPA出的难题。而曲辕RPA在处理这类“不支持无障碍接口的桌面元素”时,用的并不是魔法,而是一套完整的混合识别方案。这篇文章就把它的底层原理拆开讲清楚,包括 Windows 无障碍接口的工作机制、duilib 为什么天生不兼容、曲辕RPA 捕获桌面元素的四个核心环节,以及我在实际项目中踩过的各种坑。
这个内容适合两类人:一类是做RPA实施、需要面对各类国产或老旧客户端软件的工程师,另一类是桌面应用开发人员,想搞清楚自绘UI和系统无障碍适配之间到底隔着什么。
1. 先说清楚:RPA为什么拿duilib这类应用没辙
很多做RPA的同事第一次遇到duilib时,第一反应是“窗口是不是加载失败了”,然后去查句柄、查进程、反复刷新,最后才发现问题不是窗口挂了,而是RPA的可访问性模型在自绘UI面前根本“看”不到控件。
1.1 RPA识别桌面元素的常规路径
RPA工具识别桌面元素,通常有三条路:UI自动化接口、图像识别、坐标模拟。优先级排序也是这个顺序,能用UI自动化拿控件树,就绝不依赖图像和坐标,因为控件树最稳定、最准确,拿到属性的同时还能拿到控件状态和事件。
Windows上的UI自动化接口以两代为主:老一代的 MSAA(Microsoft Active Accessibility),通过 IAccessible 接口暴露控件信息,核心消息是 WM_GETOBJECT;新一代的 UI Automation(UIA),基于 IRawElementProvider 接口和 IUIAutomation 客户端API,能力更强、信息更丰富。Inspect 这类工具之所以能高亮出控件位置和属性,靠的就是这两套接口。RPA工具也一样,本质上就是向目标窗口发送查询请求,由应用自身来回答“我这里有哪些元素”。
这条路径能走通,前提是应用得“愿意配合”。无论控件是原生 Win32 的还是 WPF 的,框架底层都会把这些信息交给系统,RPA工具再通过系统拿过来。
1.2 duilib为什么“看不到”
duilib 是一个轻量级的 DirectUI 界面库,很多国内客户端软件都在用,包括一些装机量很大的软件。它的核心特点是:所有UI控件都不是独立的Windows窗口,而是在一个主窗口客户区内,用GDI/GDI+自绘出来的“画”。
Windows操作系统能够感知的最小界面单位是窗口句柄,也就是 HWND。系统管理和消息分发都以HWND为单元。原生应用中,每个按钮、编辑框都有自己独立的HWND,因此系统天然能枚举到它们,向它们发消息、获取它们的位置和名称都很容易。但duilib把所有控件画在同一个窗口的同一个HDC上,对Windows来说,整个界面就是一张白纸加一个顶层窗口。控件区域只是代码里维护的一个个 CRect 矩形,命中了就处理鼠标消息,没命中就不管。RPA工具用UIA扫描时,系统向这个窗口发 WM_GETOBJECT,duilib默认不处理这个消息,于是整个元素树就只能返回最顶层的窗口节点。
这就是核心矛盾所在:界面看起来有几百个可操作区域,但在系统的无障碍模型里,它们是“不存在”的。
1.3 曲辕RPA的总体思路
曲辕RPA应对这种问题,思路很直接:既然应用不主动暴露,那么RPA就在外部建立一套“感知层”,用多种手段组合出结构化的元素信息。整体可以分为四个环节:
- 窗口级定位:通过窗口句柄、进程模块信息确认目标窗口,并判断是否属于自绘UI。
- 图像语义识别:截取客户区图像,通过模板匹配和OCR引擎,找出按钮、文本框、图标的候选区域。
- 坐标映射与操作回放:把图像上的候选区域坐标换算为屏幕坐标,再以Windows消息或鼠标事件的方式执行点击、输入等操作。
- 不稳态校验:操作后再次截图,对比变化区域,确认操作生效。
这种方法不要求应用做任何改动,也不需要客户提供源码或SDK,属于“事后再建模”的做法,非常适合没法推动老系统改造的场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 无障碍适配的基础:Windows可访问性接口是怎么工作的
要理解曲辕RPA为什么选择外部感知这条路,得先把 Windows 无障碍接口的
