MSAA原理及应用

MSAA的全称是Microsoft Active Accessibility(微软活动辅助功能)。这是类似DCOM技术。技术模型是这样的,UI程序可以暴露出一个Interface,方便另一个程序对其进行控制。 MSAA技术的初衷是为了方便残疾人使用Windows 程序。比如盲人看不到窗口,但是盲人可以通过一个USB读屏器连接到电脑上, 读屏器通过UI程序暴露出来的这个Interface,就可以获取程序信息,通过盲文或者其它形式传递给盲人。

MSAA提供了如此方便的功能, UI自动化测试自然可以借用这项技术。MSAA暴露出来的Interface叫做 IAccessible(可访问到的)。测试程序和目标UI程序互操作流程如下:

1. 测试程序调用Windows APIAccessibleObjectFromWindow,传入目标UI程序HWND

2. AccessibleObjectFromWindow函数向UI程序发送WM_GETOBJECT消息。

3. UI程序创建实现了IAccessible的内部类,然后通过LresultFromObject API把IAccessible 接口返回给测试程序。

4. 测试程序拿到IAccessible接口,开始调用IAccessible接口函数操作测试目标。

IAccessible接口里面的几个关键函数是:

IAccessible::get_accChild/ IAccessible::get_accParent:通过这两个函数,调用者可以浏览目标程序的窗口关系树,定位到UI元素

IAccessible::accLocation/I Accessible::accHitTest:读取和分辨目标元素的屏幕位置

IAccessible::accName/ I Accessible:: accSelect:读取元素的名字,对UI元素进行指定的操作,比如选取Listbox里面的某一项等等。

IAccessible::accValue :开发人员可以自定义value属性的实现。比如针对折线图控件,开发人员可以在accValue中返回折线的坐标数列。

MSAA的理念类似于test hook。 通过主动让UI程序暴露一个接口来让调用者控制。 在具体使用中,测试人员往往是结合MSAA和Win32 API操作,取长补短。一方面对于UI元素丰富的属性,比如style,钩选状态,是否最大化和模拟用户输入等,继续采用Win32 API。 另一方面用MSAA的优势来弥补Win32 API的一些不足,比如:

由于MSAA有自己的get_accChild方法,使其控件树关系并不一定要和Win32 HWND关系对应一致。对于自绘窗口,虽然说只有一个HWND, 但是开发人员可以通过实现IAccessible接口来实现逻辑上的层次关系。比如Excel中就可以通过IAccessible把多个cell的子 IAccessible接口暴露给调用者。

IAccessible的实现是由开发者提供, 开发者可以灵活地根据实际情况决定方法的实现。比如前面提到了折线图控件可以返回坐标数列。对于.NET WinForm, 微软在Framework中就提供了IAccessible的默认实现,这样在具体实现中,就可以处理.NET动态维护HWND的细节等

MSDN:http://msdn.microsoft.com/en-us/library/dd318466%28VS.85%29.aspx

IAccessible

HWND hwnd = ::FindWindowW( L"CalcFrame",L"Calculator");
IAccessible *paccMainWindow = NULL;
AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**)&paccMainWindow);
long count = 0;
paccMainWindow->get_accChildCount(&count);

其他方法可参考:https://docs.microsoft.com/zh-cn/windows/win32/api/oleacc/nn-oleacc-iaccessible?redirectedfrom=MSDN

下面介绍如何获取子控件的IAccessible接口,在这个之前先讲点其它的东西:

获取控件如果使用Findwindow(FindwindowEx)我们需要知道的控件的ClassName和CaptionName,这两个信息可以通过Spy++等工具获取,但我们在测试工具中会常见到Role等很多属性,先看这些信息是从什么地方来的,在microsoft的站点上下载Inspect32.exe运行后如:

Inspect

上面告诉了我们一个控件的详细信息,现在我们只关心classname, role, name。

下面开始实现获取子控件IAccessible接口的代码,先看函数定义:

BOOL Find (IAccessible* paccParent,
    LPSTR szName,
    LPSTR szRole,
    LPSTR szClass,
    IAccessible** paccChild,
    VARIANT* pvarChild)
第一个就是父窗体的IAcessible接口,依次分别为:name, role, classname, 子控件接口,类似于ID的东西(后面再解释) >看实现:
BOOL Find (
    IAccessible* paccParent,
    LPSTR szName,
    LPSTR szRole,
    LPSTR szClass,
    IAccessible** paccChild,
    VARIANT* pvarChild)
{
    HRESULT hr;
    long numChildren;
    unsigned long numFetched;
    VARIANT varChild;
    int indexCount;
    IAccessible* pCAcc = NULL;
    IEnumVARIANT* pEnum = NULL;
    IDispatch* pDisp = NULL;
    BOOL found = false;
    char szObjName[MAX_PATH], szObjRole[MAX_PATH], szObjClass[MAX_PATH], szObjState[MAX_PATH];
    //Get the IEnumVARIANT interface
    hr = paccParent -> QueryInterface(IID_IEnumVARIANT, (PVOID*) & pEnum);
    if(pEnum)
        pEnum -> Reset();
    // Get child count
    paccParent -> get_accChildCount(&numChildren);
    for(indexCount = 1; indexCount <= numChildren && !found; indexCount++)
    {
        pCAcc = NULL;
        // Get next child
        if (pEnum)
            hr = pEnum -> Next(1, &varChild, &numFetched);  
        else
        {
            varChild.vt = VT_I4;
            varChild.lVal = indexCount;
        }
        // Get IDispatch interface for the child
        if (varChild.vt == VT_I4)
        {
            pDisp = NULL;
            hr = paccParent -> get_accChild(varChild, &pDisp);
        }
        else
            pDisp = varChild.pdispVal;
 
        // Get IAccessible interface for the child
        if (pDisp)
        {
            hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);
            hr = pDisp->Release();
        }
        // Get information about the child
        if(pCAcc)
        {
            VariantInit(&varChild);
            varChild.vt = VT_I4;
            varChild.lVal = CHILDID_SELF;
            *paccChild = pCAcc;
        }
        else
            *paccChild = paccParent;
        // Skip invisible and unavailable objects and their children
        GetObjectState(*paccChild, &varChild, szObjState, sizeof(szObjState));
        if(NULL != strstr(szObjState, "unavailable"))
        {
            if(pCAcc)
                pCAcc->Release();
            continue;
        }
        GetObjectName(*paccChild, &varChild, szObjName, sizeof(szObjName));
        GetObjectRole(*paccChild, &varChild, szObjRole, sizeof(szObjRole));
        GetObjectClass(*paccChild, szObjClass, sizeof(szObjClass));
        if ((!szName || !strcmp(szName, szObjName)) && (!szRole || !strcmp(szRole, szObjRole)) && (!szClass || !strcmp(szClass, szObjClass)))
        {
            found = true;
            *pvarChild = varChild;
            break;
        }
        if(!found && pCAcc)
        {
            // Go deeper
            found = Find(pCAcc, szName, szRole, szClass, paccChild, pvarChild);
            if(*paccChild != pCAcc)
                pCAcc->Release();
        }
    }
    // Clean up
    if(pEnum)
        pEnum -> Release();
    return found;
}

看获取控件状态的函数:

UINT GetObjectState(IAccessible* pacc, VARIANT* pvarChild, LPSTR lpszState, UINT cchState)
{
    HRESULT hr;
    VARIANT varRetVal;
    *lpszState = 0;
    VariantInit(&varRetVal);
    hr = pacc->get_accState(*pvarChild, &varRetVal);
    if (!SUCCEEDED(hr))
        return(0);
    DWORD dwStateBit;
    int cChars = 0;
    if (varRetVal.vt == VT_I4)
    {
        // Convert state flags to comma separated list.
        for (dwStateBit = STATE_SYSTEM_UNAVAILABLE; dwStateBit < STATE_SYSTEM_ALERT_HIGH; dwStateBit <<= 1)
        {
            if (varRetVal.lVal & dwStateBit)
            {
                cChars += GetStateTextA(dwStateBit, lpszState + cChars, cchState - cChars);
                *(lpszState + cChars++) = ',';
            }
        }
        if(cChars > 1)
            *(lpszState + cChars - 1) = '\0';
    }
    else if (varRetVal.vt == VT_BSTR)
    {
        WideCharToMultiByte(CP_ACP, 0, varRetVal.bstrVal, -1, lpszState,
            cchState, NULL, NULL);
    }
    VariantClear(&varRetVal);
    return(lstrlenA(lpszState));
}

用来获取一个控件是否可用。 下来看一个例子:

HWND hwnd = ::FindWindowW( NULL,L"Run");
IAccessible *paccMainWindow = NULL;
AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**)&paccMainWindow);
long count = 0;
paccMainWindow->get_accChildCount(&count);
IAccessible*    paccControl = NULL;
VARIANT         varControl;
if (Find(paccMainWindow, "Open:", "editable text", "Edit", &paccControl, &varControl))
{
    paccControl->put_accValue(varControl, CComBSTR("123"));
}

运行结果:

123Text

### MSAA多尺度注意力机制概述 在计算机视觉和深度学习领域,MSAA(Multi-Scale Attention Aware)模块被设计用来提升模型对于不同尺度特征的关注能力。这种机制通过引入多个尺度的感受野来捕捉图像中的细节信息以及更广泛的上下文关系。 #### 结构特点 MSAA-Net采用了改进版的U-Net架构,在传统的基础上加入了专门针对多尺度特性的处理单元[^1]。具体来说: - **编码阶段**:负责提取输入数据的不同层次特征表示; - **解码阶段**:利用来自编码部分的信息重建目标对象轮廓或其他属性; - **跨层连接**:不同于标准跳跃链接仅传递相同位置像素间的关系,这里实现了更加灵活有效的特征融合方式; 为了进一步加强网络性能,CM-UNet框架下的MSAA模块不仅继承了上述优点还进行了创新优化。它能够更好地综合低级到高级的各种表征形式,并将其有效地传达给后续处理环节以辅助最终决策过程[^2]。 #### 工作原理 当涉及到具体的实现方法时,可以考虑以下几个方面: - 使用卷积神经网络(CNNs)作为基础构建块来进行初步的空间维度降维操作; - 应用自注意(self-attention)或其变体如通道注意力(channel-wise attention)[^4]等技术手段突出重要区域/通道的重要性; - 设计特定类型的残差路径(residual path),使得每一层都能获得足够的梯度反馈从而加快收敛速度并防止过拟合现象发生。 ```python import torch.nn as nn class MSAA(nn.Module): def __init__(self, in_channels, out_channels): super(MSAA, self).__init__() # Define convolutional layers with different kernel sizes for multi-scale feature extraction self.conv_3x3 = nn.Conv2d(in_channels, out_channels//4, kernel_size=3, padding=1) self.conv_5x5 = nn.Conv2d(in_channels, out_channels//4, kernel_size=5, padding=2) self.conv_7x7 = nn.Conv2d(in_channels, out_channels//4, kernel_size=7, padding=3) # Channel-wise attention module self.channel_attention = MultiScaleChannelAttentionModule(out_channels) def forward(self, x): feat_3x3 = self.conv_3x3(x) feat_5x5 = self.conv_5x5(x) feat_7x7 = self.conv_7x7(x) concat_features = torch.cat([feat_3x3, feat_5x5, feat_7x7], dim=1) attended_features = self.channel_attention(concat_features) return attended_features ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顺其自然~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值