首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Microsoft Active Accessibility (MSAA) 程式設計深度解析

     編輯:關於VC++

什麼是 MSAA

Microsoft Active Accessibility (MSAA) 是基於 COM 的無障礙技術,讓螢幕閱讀器、放大鏡、語音識別等輔助工具能存取 UI 元素資訊。核心介面 IAccessible 提供統一方式查詢元素名稱、角色、狀態、值,並支援觸發預設動作 (如按鈕點擊)、模擬鍵盤滑鼠輸入、監聽 UI 事件。

核心概念

  • IAccessible 介面:每個可存取 UI 元素對應一個介面實例
  • Child ID:父元素管理子元素時使用的識別碼,`CHILDID_SELF (0)` 代表元素本身
  • WinEvents:UI 狀態變更通知機制,類似 Hook 但跨程序無需 DLL 注入
  • OLEACC.DLL:核心執行元件,提供標準控制項的預設 Proxy 實作

取得 IAccessible 指標

從視窗句柄開始


HWND hWnd = FindWindow(NULL, L"執行");
if (hWnd) {
    IAccessible* pAcc = NULL;
    HRESULT hr = AccessibleObjectFromWindow(hWnd, OBJID_WINDOW, 
                                            IID_IAccessible, (void**)&pAcc);
    if (SUCCEEDED(hr)) {
        // 取得視窗名稱、角色等
        BSTR name; pAcc->get_accName(CHILDID_SELF, &name);
        VARIANT role; pAcc->get_accRole(CHILDID_SELF, &role);
        // 使用完畢釋放
        pAcc->Release();
    }
}

樹狀導航尋找特定子元素


BOOL FindChild(IAccessible* pParent, LPCWSTR name, LPCWSTR role, LPCWSTR cls,
               IAccessible** ppChild, VARIANT* pVarChild)
{
    long childCount = 0;
    pParent->get_accChildCount(&childCount);
    
    // 取得列舉器
    CComPtr pEnum;
    pParent->QueryInterface(IID_IEnumVARIANT, (void**)&pEnum);
    if (pEnum) pEnum->Reset();
    
    for (long i = 1; i <= childCount; ++i) {
        VARIANT varChild; VariantInit(&varChild);
        ULONG fetched = 0;
        
        if (pEnum) 
            pEnum->Next(1, &varChild, &fetched);
        else 
            varChild.vt = VT_I4, varChild.lVal = i;
        
        CComPtr pDisp;
        if (varChild.vt == VT_I4)
            pParent->get_accChild(varChild, &pDisp);
        else
            pDisp = varChild.pdispVal;
        
        CComPtr pChildAcc;
        if (pDisp) pDisp->QueryInterface(IID_IAccessible, (void**)&pChildAcc);
        
        VARIANT varSelf; varSelf.vt = VT_I4; varSelf.lVal = CHILDID_SELF;
        IAccessible* pTarget = pChildAcc ? pChildAcc : pParent;
        VARIANT* pVar = pChildAcc ? &varSelf : &varChild;
        
        // 比對名稱、角色、類別
        BSTR bstrName, bstrRole, bstrClass;
        pTarget->get_accName(*pVar, &bstrName);
        pTarget->get_accRole(*pVar, (VARIANT*)&bstrRole); // 簡化
        // ... 比對邏輯
        
        if (match) { *ppChild = pTarget; *pVarChild = *pVar; return TRUE; }
        if (pChildAcc && FindChild(pChildAcc, name, role, cls, ppChild, pVarChild))
            return TRUE;
    }
    return FALSE;
}

實戰:自動化「執行」對話框啟動 Regedit


// 1. 找到輸入框並輸入 "regedit"
IAccessible* pEdit = NULL; VARIANT varEdit;
FindChild(pMainWnd, L"開啟(O):", L"可編輯文字", L"Edit", &pEdit, &varEdit);
pEdit->put_accValue(varEdit, CComBSTR(L"regedit"));
pEdit->Release(); VariantClear(&varEdit);

// 2. 找到「確定」按鈕並觸發預設動作
IAccessible* pBtn = NULL; VARIANT varBtn;
FindChild(pMainWnd, L"確定", L"按下按鈕", L"Button", &pBtn, &varBtn);
pBtn->accDoDefaultAction(varBtn);
pBtn->Release(); VariantClear(&varBtn);

模擬鍵盤/滑鼠輸入

當元素不支援 MSAA 介面時,使用 `SendInput` 模擬:


// 模擬 Alt+F4 關閉視窗
INPUT inputs[4] = {0};
inputs[0].type = inputs[1].type = inputs[2].type = inputs[3].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = inputs[2].ki.wVk = VK_MENU;      // Alt 按下/放開
inputs[1].ki.wVk = inputs[3].ki.wVk = VK_F4;       // F4 按下/放開
inputs[2].ki.dwFlags = inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(4, inputs, sizeof(INPUT));

監聽 WinEvents

跨程序監聽 UI 建立/銷毀/焦點變更等事件:


// 設定 Out-of-context Hook (無需 DLL 注入)
HWINEVENTHOOK hHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL,
    WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);

void CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD event,
                           HWND hwnd, LONG idObject, LONG idChild,
                           DWORD dwEventThread, DWORD dwmsEventTime)
{
    if (event != EVENT_OBJECT_CREATE) return;
    
    IAccessible* pAcc = NULL; VARIANT varChild; VariantInit(&varChild);
    HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
    if (FAILED(hr)) return;
    
    BSTR name; pAcc->get_accName(varChild, &name);
    if (StrStr(name, L"執行")) {
        PostThreadMessage(GetCurrentThreadId(), WM_TARGET_FOUND, 0, 0);
    }
    pAcc->Release(); VariantClear(&varChild);
}

IAccessible/Child ID 配對機制詳解

兩種表示子元素方式:

  1. 獨立介面:每個子元素有自己的 `IAccessible`,Child ID 為 `CHILDID_SELF`
  2. 共享介面:父元素實作 `IAccessible`,子元素透過 Child ID (1-based 索引) 區分

判斷規則:若 `get_accChild` 回傳 `S_OK` 且 `ppdisp` 不為 NULL → 子元素有獨立介面;否則使用父介面 + Child ID。

開發工具

  • Inspect.exe (Windows SDK):取代舊版 `inspect32.exe`,檢視 UI 元素完整屬性樹
  • AccExplorer.exe:舊版 MSAA SDK 工具,仍可參考
  • UIA Verify:新一代 UI Automation 驗證工具

遷移建議

MSAA 為舊技術,新專案建議使用 UI Automation (UIA):

  • 更豐富的控制型別與模式
  • 更好的效能與安全性
  • 支援 WPF、Win32、WinForms 統一模型
  • .NET 原生支援 (`System.Windows.Automation`)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved