You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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方法的实现逻辑:

  1. 用object[]接收AccessibleChildren的返回结果,而非强制声明为IAccessible[]
  2. 逐个判断返回元素的类型:
    • 若为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 14:20:57