0

I'm programming a console application in C++ in Visual Studio 2022 and I would like to get the IUIAutomationElement of the "main form" (the big, outer form) of a specific application so that I can later get its children. I see two approaches:

  1. Using the Process ID or Process Name. I have working source code that can get the Process ID from the Process Name and, of course, the Process Name (ends with .exe) is known.
  2. In a "find all applications"-manner and then filter the desired application by comparing a substring (not the full string because this can vary!) in the form's title.

Please note that I don't want to use any location (e.g. the mouse location) to locate the IUIAutomationElement. Can someone please help me?

arnold_w
  • 59
  • 9
  • You can get the main HWND of the process https://stackoverflow.com/questions/2531828/how-to-enumerate-all-windows-belonging-to-a-particular-process-using-net (C# but using Win32 API) and then get the automation element from it https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-obtainingelements https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomation-elementfromhandle – Simon Mourier Apr 25 '23 at 16:25
  • @SimonMourier, can you please detail which functions I should call? In order to call EnumThreadWindows I need the dwThreadId, but I only have the dwProcessId. How do I obtain the dwThreadId from the dwProcessId? – arnold_w Apr 26 '23 at 11:43
  • There are multiple threads per process https://stackoverflow.com/questions/1206878/enumerating-threads-in-windows – Simon Mourier Apr 26 '23 at 11:47
  • @SimonMourier thanks, I was able to do it using the function ListProcessModules found on https://learn.microsoft.com/en-us/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes . Since I'm only interested in the "main" form dwThreadId, I only take the first found dwThreadId. – arnold_w Apr 26 '23 at 12:00

1 Answers1

0

With help from @SimonMourier and some googling, I finally solved it. First, I need to get the first dwThreadId from my dwProcessId and I do that using the function GetFirstThreadId:

// Found this function (original name was ListProcessModules) on 
// https://learn.microsoft.com/en-us/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes
static DWORD GetFirstThreadId(DWORD dwProcessId) {
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
    THREADENTRY32 te32;

    // Take a snapshot of all running threads  
    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadSnap == INVALID_HANDLE_VALUE) {
        return(FALSE);
    }

    // Fill in the size of the structure before using it. 
    te32.dwSize = sizeof(THREADENTRY32);

    // Retrieve information about the first thread,
    // and exit if unsuccessful
    if (!Thread32First(hThreadSnap, &te32)) {
        wprintf(L"Thread32First"); // show cause of failure
        CloseHandle(hThreadSnap);          // clean the snapshot object
        return(FALSE);
    }

    // Now walk the thread list of the system,
    // and display information about each thread
    // associated with the specified process
    do {
        if (te32.th32OwnerProcessID == dwProcessId) {
//            wprintf(L"\n\n     THREAD ID      = 0x%08X", te32.th32ThreadID);
//            wprintf(L"\n     Base priority  = %d", te32.tpBasePri);
//            wprintf(L"\n     Delta priority = %d", te32.tpDeltaPri);
//            wprintf(L"\n");
            CloseHandle(hThreadSnap);          // clean the snapshot object
            return te32.th32ThreadID;
        }
    } while (Thread32Next(hThreadSnap, &te32));

    CloseHandle(hThreadSnap);          // clean the snapshot object
    return 0;
}

Then it's just a matter of calling EnumThreadWindows and ElementFromHandle:

static UIA_HWND g_hwnd;
static int numCallbackCallsLeft;

int _tmain(int argc, _TCHAR* argv[]) {
    const std::wstring processName = L"myExecutable.exe";
    DWORD dwProcessID = FindProcessId(processName);
    DWORD dwThreadID = GetFirstThreadId(dwProcessID);
    HWND hTop = NULL;
    numCallbackCallsLeft = 1;
    BOOL bResult = EnumThreadWindows(dwThreadID, EnumWindowsCallBackFnc, (LPARAM)&hTop);
    while (0 < numCallbackCallsLeft) {
        Sleep(1);
    }
    if (SUCCEEDED(CoInitialize(NULL))) {
        if (SUCCEEDED(g_pAutomation.CoCreateInstance(CLSID_CUIAutomation8))) { // or CLSID_CUIAutomation 
            CComPtr<IUIAutomationElement> pElement;
            if (SUCCEEDED(g_pAutomation->ElementFromHandle(g_hwnd, &pElement))) {
                // Hooray, we now have pElement!!!
            }
        }
    }
    CoUninitialize();
    return 0;
}

// Pointer to this function is passed as parameter to EnumThreadWindows function
static BOOL CALLBACK EnumWindowsCallBackFnc(HWND hwnd, LPARAM lParam) {
    wprintf(L"EnumWindowsCallBackFnc. hwnd: %d\n", (int)hwnd);
    g_hwnd = hwnd;

    // This function is called for all siblings windows owned by given thread
    // So, store hwnd for the later use...
    numCallbackCallsLeft--;
    return (0 < numCallbackCallsLeft); // return TRUE if you want to continue windows enumeration.
    // If you found the proper window, just return FALSE and windows enumeration will stop
}
arnold_w
  • 59
  • 9