Microsoft Active Accessibility (MSAA) 是基於 COM 的無障礙技術,讓螢幕閱讀器、放大鏡、語音識別等輔助工具能存取 UI 元素資訊。核心介面 IAccessible 提供統一方式查詢元素名稱、角色、狀態、值,並支援觸發預設動作 (如按鈕點擊)、模擬鍵盤滑鼠輸入、監聽 UI 事件。
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;
}
// 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));
跨程序監聽 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);
}
兩種表示子元素方式:
判斷規則:若 `get_accChild` 回傳 `S_OK` 且 `ppdisp` 不為 NULL → 子元素有獨立介面;否則使用父介面 + Child ID。
MSAA 為舊技術,新專案建議使用 UI Automation (UIA):