UI Automation でアプリ一覧を取得する [C++Builder]

「タスクバーに載っているアプリ一覧を取得する」方法を調べていたところ stackoverflow の List all running Taskbar applications with .NET に「UI Automation」を使った回答があったので試してみました。

UI Autimation (以降、UIA) の使い方については、C# での情報が多く、C++ での情報は殆ど見つかりません。C++ で UIA でアプリ一覧を取得できたので、C++ で UIA を扱いたい人の参考になればと思います。

実行結果

この後に書いてあるコードを実行すると次のような一覧が取得できます。

> .\Project1.exe
No. hWnd exename caption
--- ---------- -------------------------------- --------------------------------------------- - -
1 0x00870b36 WindowsTerminal.exe Windows PowerShell
2 0x00161786 WindowsTerminal.exe C:\WINDOWS\system32\cmd.exe
3 0x00520ad0 bds.exe Project1 - C++Builder 12 Community Edition - File1.cpp [ビルド完了]
4 0x000e0e98 Q-Dir.exe Q-Dir 6.41
5 0x001d170a Notepad.exe タイトルなし - メモ帳
6 0x002413b2 EXCEL.EXE 新規 Microsoft Excel ワークシート.xlsx - Excel
7 0x0001056c LogiOverlay.exe MainWindow
8 0x0012023c AFX.EXE あふ
9 0x00041542 ApplicationFrameHost.exe 電卓
--- ---------- -------------------------------- --------------------------------------------- - -

起動したアプリがほぼ取れています。ただし、タスクバーのアプリ一覧とは異なった内容です。

タスクバーのアプリ一覧とは異なる

上記の UIA で取得した一覧はタスクバーに載っているアプリ一覧とは異なっています。

上記のコマンド実行時、アプリは10個立ち上げていました。

1. C++ Builder 2. VSCode 3. Q-Dir 4. あふ 5. 電卓 6. Chrome 7. Excel 8. メモ帳 9. コマンドプロンプト 10. PowerShell

コマンド実行結果は、この実際のタスクバーの内容と異なっています。差異は3つあり、1) VSCode が無い、 2) Chorome が無い、3) LogiOverlay.exe がある、です。

このように UIA で取得した一覧 と タスクバーの一覧 とは違うものになります。タスクバーに載っているアプリ一覧を取得したい場合は、別の方法を探す必要があります。

UIA でアプリ一覧を取得するコード

C++ 版

C++ でのコードは次の通りです。デスクトップの子要素を対象にウィンドウコントール要素を取得しています。

#include <vector>
#include <uiautomationclient.h>
HRESULT _InitializeUIAutomation(IUIAutomation **ppAutomation);
void _BuildAppList(IUIAutomation *iUiAutomation, std::vector<HWND>& hWnds);

#include <iostream>
void _PrintList(std::vector<HWND>& hWnds);
UnicodeString _GetExePath(HWND hWnd);
UnicodeString _GetCaption(HWND hWnd);

//--------------------------------------------------------------------------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
    // COM ライブラリを初期化する。
    // C++ では CoInitialize() をしないと CoCreateInstance() が CLASS_E_NOAGGREGATION で失敗する。
    CoInitialize(NULL);

    // UIA の開始手続き。CUIAutomation オブジェクトのインスタンスを取得する。
	IUIAutomation *iUiAutomation;
    HRESULT hResult = _InitializeUIAutomation(&iUiAutomation);
    if (hResult != S_OK) {
        return -1;
    }

    // アプリ一覧を取得して vector<HWND> に格納する。
    std::vector<HWND> hWnds = std::vector<HWND>();
    _BuildAppList(iUiAutomation, hWnds);

    // CUIAutomation を開放する。
    iUiAutomation->Release();

    // 取得した一覧をコンソールに出力する。
    _PrintList(hWnds);
    return 0;
}

//--------------------------------------------------------------------------------------------------

// CUIAutomation オブジェクトのインスタンスを取得する。
HRESULT _InitializeUIAutomation(IUIAutomation **ppAutomation)
{
    return CoCreateInstance(CLSID_CUIAutomation, NULL,
        CLSCTX_INPROC_SERVER, IID_IUIAutomation,
        reinterpret_cast<void**>(ppAutomation));
}

// アプリ一覧を取得して vector<HWND> に格納する。
void _BuildAppList(IUIAutomation *iUiAutomation, std::vector<HWND>& hWnds)
{
    // FindAll() での検索条件を持つプロパティを作成する。
    tagVARIANT variant;
    variant.vt = VT_I4;
    variant.lVal = UIA_WindowControlTypeId;
    IUIAutomationCondition *iUiAutomationCondition;
    iUiAutomation->CreatePropertyCondition(
        UIA_ControlTypePropertyId, variant, &iUiAutomationCondition);

    // FindAll() で検索する階層に デスクトップを表す UIA 要素を取得する。
    IUIAutomationElement *iUiRootElement;
    iUiAutomation->GetRootElement(&iUiRootElement);

    // FindAll() を実行する。
    // デスクトップ(RootElement)の子要素(TreeScope_Children)から、検索条件プロパティに一致する要素を取得する。
    IUIAutomationElementArray *iUiAutomationElmentArray;
    iUiRootElement->FindAll(TreeScope_Children, iUiAutomationCondition, &iUiAutomationElmentArray);

    // 条件に一致した要素数を取得する。
    int nElements;
    iUiAutomationElmentArray->get_Length(&nElements);

    // 条件に一致した要素をループしながら、ウィンドウハンドルを取得し、vector<HWND> へ格納する。
    for (int i = 0; i < nElements; i++) {
        IUIAutomationElement *element;
        iUiAutomationElmentArray->GetElement(i, &element);

        // ウィンドウハンドルを取得する。
        UIA_HWND UiaHwnd;
        HRESULT hResultGetHwnd = element->get_CurrentNativeWindowHandle(&UiaHwnd);

        // vector<HWND> へ格納する。
        hWnds.push_back((HWND)UiaHwnd);
    }
    return;
}

//--------------------------------------------------------------------------------------------------

// 取得した一覧をコンソールに出力する。
void _PrintList(std::vector<HWND>& hWnds)
{
    std::cout << "No. hWnd       exename                           caption" << std::endl;
    std::cout << "--- ---------- -------------------------------- --------------------------------------------- - -" << std::endl;

    int i = 1;
    std::vector<HWND>::iterator it;
    for(it = hWnds.begin(); it != hWnds.end(); it++) {
        HWND hWnd = *it;
		UnicodeString s = String().sprintf(L"%3d 0x%08x %-32s %s",
        	i, hWnd,
            ExtractFileName(_GetExePath(hWnd)).c_str(),
            _GetCaption(hWnd).c_str());
        std::cout << AnsiString(s).c_str() << std::endl;
        i++;
    }

    std::cout << "--- ---------- -------------------------------- --------------------------------------------- - -" << std::endl;
}

UnicodeString _GetExePath(HWND hWnd)
{
    UnicodeString result;

    DWORD pid;
    GetWindowThreadProcessId(hWnd, &pid);

    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, -1, pid);
    if (hProcess == NULL)
        return "";

	const int BUFSIZE = MAX_PATH;
    wchar_t exePath[BUFSIZE];
    unsigned long len = BUFSIZE;
    BOOL succeeded = QueryFullProcessImageName(hProcess, 0, exePath, &len);
    result = (succeeded)? UnicodeString(exePath) : "";

    CloseHandle(hProcess);
    return result;
}

UnicodeString _GetCaption(HWND hWnd)
{
	const int BUFSIZE = 256;
    wchar_t Caption[BUFSIZE];
    GetWindowText(hWnd, Caption, BUFSIZE);
    return Caption;
}

C# 版

最後に同じ一覧取得を C# で書いた場合のコードも記載しておきます。C# で使うと簡単に書けます。

// UIA で一覧を取得する。
var desktopWindows = 
    AutomationElement.RootElement.FindAll(
        TreeScope.Children,
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));

// 取得した一覧を List に変換する。
List<AutomationElement> list = desktopWindows.Cast<AutomationElement>().ToList();

// List の内容をコンソールに出力する。
// 出力内容は C++ より簡素にしてあります。
foreach (AutomationElement element in list)
{
    string msg =
        string.Format("0x{0,8:x8} {1}", 
            element.Current.NativeWindowHandle, element.Current.Name);
    Console.WriteLine(msg);
}

参考

コメント