在C#中处理完整MSAA可访问性树时返回空值与整数的问题
问题
我们原本用C++实现了获取另一个进程完整可访问性树的功能,现在计划迁移到C#。目前已经通过AccessibleObjectFromWindow成功连接到目标进程,也能用AccessibleChildren获取到顶层可访问对象的子项,但递归遍历一级子项的子项时,发现子项数量和Accessibility Explorer的结果一致,但子项内容要么是空值要么是整数。想知道有没有办法把这些整数转换回IAccessible对象?
相关代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Windows.Forms; using Accessibility; public partial class Form1 : Form { [DllImport("oleacc.dll")] internal static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint id, ref Guid iid, [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject); [DllImport("oleacc.dll")] public static extern uint AccessibleChildren(IAccessible paccContainer, uint iChildStart, uint cChildren, [Out] object[] rgvarChildren, out uint pcObtained); #region Enumerations //Obj ID internal enum OBJID : uint { WINDOW = 0x00000000, SYSMENU = 0xFFFFFFFF, TITLEBAR = 0xFFFFFFFE, MENU = 0xFFFFFFFD, CLIENT = 0xFFFFFFFC, VSCROLL = 0xFFFFFFFB, HSCROLL = 0xFFFFFFFA, SIZEGRIP = 0xFFFFFFF9, CARET = 0xFFFFFFF8, CURSOR = 0xFFFFFFF7, ALERT = 0xFFFFFFF6, SOUND = 0xFFFFFFF5, CHILDID_SELF = 0, SELFLAG_TAKEFOCUS = 0x01 } #endregion Enumerations IAccessible iAccessible; //interface: Accessibility namespace IntPtr handle; public Form1() { InitializeComponent(); } public bool checkHandle() { Guid guid = new Guid("{618736E0-3C3D-11CF-810C-00AA00389B71}"); object obj = null; // Use any window handle handle = (IntPtr)66956; int retVal = AccessibleObjectFromWindow(handle, (uint)OBJID.WINDOW, ref guid, ref obj); iAccessible = (IAccessible)obj; string[] data = new string[1]; string accWindowName = iAccessible.get_accName(0); string accWindowVal = iAccessible.get_accValue(0); System.Diagnostics.Debug.WriteLine("IAccessible Name : " + accWindowName); System.Diagnostics.Debug.WriteLine("IAccessible value : " + accWindowVal); System.Diagnostics.Debug.WriteLine("IAccessible Role is : " + iAccessible.get_accRole(0)); System.Diagnostics.Debug.WriteLine("IAccessible Type: " + iAccessible.GetType()); System.Diagnostics.Debug.WriteLine("IAccessible Focus is: " + iAccessible.accFocus); System.Diagnostics.Debug.WriteLine("IAccessible Selection is " + iAccessible.get_accState()); //iAccessible.accSelect((int)OBJID.SELFLAG_TAKEFOCUS, 0); //if (!accWindowName.Contains("Mozilla Firefox")) // return false; ProcessChildren(iAccessible, false); iAccessible = null; return false; } private void ProcessChildren(IAccessible iChild, bool v) { IAccessible[] childs = new IAccessible[iChild.accChildCount]; uint obtained = 0; uint firstChild = 0; uint childcount = (uint)iChild.accChildCount - 1; uint ret = AccessibleChildren(iChild, firstChild, childcount, childs, out obtained); int i = 0; foreach (IAccessible child in childs) { if (child == null) continue; if (!Marshal.IsComObject(child)) continue; string accWindowName = child.get_accName(0); string accWindowVal = child.get_accValue(0); ProcessChildren(child, false); Marshal.ReleaseComObject(child); } } private void button1_Click(object sender, EventArgs e) { checkHandle(); } }
解决方案
你遇到的整数其实是子对象ID(Child ID),并非独立的IAccessible对象。Windows辅助功能模型中,部分子元素不会以单独COM对象存在,而是通过父IAccessible对象加上子ID来标识。
要正确处理这种情况,需修改ProcessChildren方法的实现逻辑:
- 用
object[]接收AccessibleChildren的返回结果,而非强制声明为IAccessible[] - 逐个判断返回元素的类型:
- 若为
IAccessible类型,直接作为独立子对象处理并递归遍历其后代 - 若为整数类型,将其作为子ID,通过父IAccessible对象的
get_accName、get_accRole等方法传入该ID,获取对应子元素的属性
- 若为
修改后的核心代码示例:
private void ProcessChildren(IAccessible parent, bool isChildId) { if (parent == null) return; int childCount = parent.accChildCount; if (childCount <= 0) return; object[] children = new object[childCount]; uint obtained = 0; uint ret = AccessibleChildren(parent, 0, (uint)childCount, children, out obtained); for (int i = 0; i < obtained; i++) { var child = children[i]; if (child == null) continue; if (child is IAccessible accessibleChild) { // 处理独立的IAccessible子对象 string name = accessibleChild.get_accName(0); System.Diagnostics.Debug.WriteLine($"独立子对象名称: {name}"); ProcessChildren(accessibleChild, false); Marshal.ReleaseComObject(accessibleChild); } else if (child is int childId) { // 处理基于子ID的子元素 string name = parent.get_accName(childId); string role = parent.get_accRole(childId).ToString(); System.Diagnostics.Debug.WriteLine($"子ID {childId} 的元素名称: {name}, 角色: {role}"); // 基于子ID的元素无独立子对象,无需递归 } } }
额外注意事项:
AccessibleChildren的第三个参数应直接传入子项总数(uint)parent.accChildCount,你之前的childcount = (uint)iChild.accChildCount - 1会导致少获取一个子项- 不要提前将接收数组声明为
IAccessible[],否则会导致整数类型的子ID转换失败,出现空值 - 基于子ID的元素不属于容器对象,无需递归调用
ProcessChildren
内容的提问来源于stack exchange,提问作者Colin Armitage
相关产品推荐
相关产品推荐

