Thread: Sorry for 3 back-to-back-to-back posts, but... COM dot bomb strikes again!

  1. #1
    Registered User
    Join Date
    Sep 2021
    Posts
    10

    Question Sorry for 3 back-to-back-to-back posts, but... COM dot bomb strikes again!

    Hey again,

    I apologize for so many posts about Windows wonk. On the positive side, it has taught me more about the Visual Studio debugger and helped me gain a deeper appreciation of Linux.

    So, my program uses the Microsoft UI Automation API ("UIA" for short), and UIA uses the Component Object Model ("COM" for short - unrelated to COM ports or .COM executables, but probably from that same era, lol). COM is an ancient attempt at having libraries on windows, probably before the invention of the DLL. That or they were competing with Java and just had to have OOP, and didn't think to just write classes wrapping DLL functions. lol sorry, I'm way off track here.

    History lesson and background info aside, I am currently battling with this function:

    Code:
    bool ForEachElement(IUIAutomation* uia, IUIAutomationElement* window, const long controlType, ForEachElementCallback callback) {
        Log_Info("ForEachElement\tRunning...");
        
        // Declare variables
        int i = 0, length = 0;
        bool done = false;
        HRESULT result = S_OK;
        IUIAutomationElementArray* allButtons = nullptr;
        IUIAutomationElement* temp = nullptr;
        IUIAutomationCondition* isType = nullptr;
        BSTR name = nullptr;
        VARIANT varControlType;
        
        // Handle ID10T errors :)
        if (uia == nullptr) {
            Log_Error("ForEachElement\tUIA is null");
            goto ForEachElement_error;
        }
        if (window == nullptr) {
            Log_Error("ForEachElement\tWindow is null");
            goto ForEachElement_error;
        }
    
        // Set up the VARIANT object that UIA needs to know what the conditions actually are
        VariantInit(&varControlType);
        varControlType.vt = VT_I4;
        varControlType.lVal = controlType;
    
        // Create a condition: control type = %controlType%
        result = uia->CreatePropertyCondition(UIA_ControlTypePropertyId, varControlType, &isType);
        if (FAILED(result)) {
            char error[100];
            snprintf(error, 100, "ForEachElement\tCreatePropertyCondition failed with code 0x%x", result);
            Log_ErrorExtended(error);
            goto ForEachElement_error;
        }
    
        // Free memory used by varControlType
        result = VariantClear(&varControlType);
        if (FAILED(result)) {
            char error[100];
            snprintf(error, 100, "ForEachElement\tCreatePropertyCondition failed with code 0x%x", result);
            Log_ErrorExtended(error);
            goto ForEachElement_error;
        }
    
        // Find all buttons
        result = window->FindAll(TreeScope_Descendants, isType, &allButtons);
        if (FAILED(result)) {
            char error[100];
            snprintf(error, 100, "FindElementsByType\tFindAll failed with code 0x%x", result);
            Log_ErrorExtended(error);
            goto ForEachElement_error;
        }
        isType->Release();
    
        // Find out how many buttons there are
        if (FAILED(allButtons->get_Length(&length)) || length == 0) {
            Log_ErrorExtended("ForEachElement\tget_Length failed");
            goto ForEachElement_error;
        }
        
        // Loop through the list, calling the user's callback on every item
        for(; i<length; i++) {
            // Get element #i
            temp = nullptr;
            if (FAILED(allButtons->GetElement(i, &temp)) || temp == nullptr) {
                Log_ErrorExtended("ForEachElement\tGetElement failed");
                goto ForEachElement_error;
            }
    
            // Get its "Name" (label)
            name = nullptr;
            if (FAILED(temp->get_CurrentName(&name))) {
                Log_ErrorExtended("ForEachElement\tget_CurrentName failed");
                temp->Release();
                goto ForEachElement_error;
            }
            
            // Call the user-defined callback
            done = callback(temp, name);
            temp->Release();
            if (done) {
                Log_Info("ForEachElement\tCallback returned true (the program found what it was looking for).");
                break;
            }
        }
    
        // Now after the end of the loop, free the list of buttons and we're done.
        allButtons->Release();
        Log_Info("ForEachElement\tDone.");
        return true;    // Note that true != we found the thing we want; that's the "done" bool above.
                        // That's why I have global variables for storing info on stuff done inside the callbacks.
    
    // Free any memory we don't need anymore
    ForEachElement_error:
        if (allButtons != nullptr) allButtons->Release();
        if (isType != nullptr) isType->Release();
        return false;
    }
    Before I go any further, a few important notes:

    * My logging functions (Log_Info etc.) all work as expected... except in what I've been calling "COM dot bomb" scenarios like the insanity you are about to see.
    * II've also tried checking my inputs and ouptus against C-style NULL (I know C++ "nullptr" is something slightly different)
    * My custom type "ForEachElementCallback" is a function pointer. We never get to it, so it's not really relevant.
    * The code compiles with no errors and no warnings.
    * Interestingly, when I call it in other places, it works fine.

    Anyway, main() calls a function that calls this, and it gets fine until the line with window->FindAll. Then Visual Studio coughs up this nonsense:

    Code:
    A breakpoint instruction (__debugbreak() statement or a similar call) was executed in myProgram.exe.
    
    Debugger at this point:
    -        &allButtons    0x00000025808ff2f8 {0x0000000000000000 <NULL>}    IUIAutomationElementArray * *
        -            0x0000000000000000 <NULL>    IUIAutomationElementArray *
            -        IUnknown    <struct at NULL>    IUnknown
                __vfptr    <Unable to read memory>    void * *
                TreeScope_Descendants    TreeScope_Descendants (4)    TreeScope
    -        isType    0x00000170d2cda480 <No type information available in symbol file for UIAutomationCore.dll>    IUIAutomationCondition *
        -        IUnknown    {...}    IUnknown
            -        __vfptr    0x00007ff8317a8648 {UIAutomationCore.dll!const ATL::CComObject<class CPropertyCondition>::`vftable'{for `IUIAutomationPropertyCondition'}} {...}    void * *
                [0]    0x00007ff83165e360 {UIAutomationCore.dll!ATL::CComObject<...>::QueryInterface(void)}    void *
                [1]    0x00007ff8316c6290 {UIAutomationCore.dll!ATL::CComObject<class CAndCondition>::AddRef(void)}    void *
                [2]    0x00007ff83165bc30 {UIAutomationCore.dll!ATL::CComObject<class CPropertyCondition>::Release(void)}    void *
    -        window    0x00000170d2cc4bd0 <No type information available in symbol file for UIAutomationCore.dll>    IUIAutomationElement *
        -        IUnknown    {...}    IUnknown
            -        __vfptr    0x00007ff8317a07f0 {UIAutomationCore.dll!const CAutomationElement::`vftable'{for `IUIAutomationElement9'}} {...}    void * *
                    [0]    0x00007ff83163ee10 {UIAutomationCore.dll!CAutomationElement::QueryInterface(void)}    void *
                    [1]    0x00007ff83165a340 {UIAutomationCore.dll!CAutomationElement::AddRef(void)}    void *
                    [2]    0x00007ff831655eb0 {UIAutomationCore.dll!CAutomationElement::Release(void)}    void *
    Call stack at this point:
    ntdll.dll!00007ff852599152() Unknown
    ntdll.dll!00007ff8525a1582() Unknown
    ntdll.dll!00007ff8525a188a() Unknown
    ntdll.dll!00007ff8525aa899() Unknown
    ntdll.dll!00007ff85254726d() Unknown
    ntdll.dll!00007ff8524e06e1() Unknown
    ucrtbase.dll!00007ff8504514cb() Unknown
    ucrtbase.dll!00007ff85045ad22() Unknown
    UIAutomationCore.dll!MessageParser::GetIntArray() Unknown
    UIAutomationCore.dll!LocalUiaNodeProxy::CrossProce ss_GetRuntimeId() Unknown
    UIAutomationCore.dll!UiaNode::CrossProcess_GetRunt imeId() Unknown
    UIAutomationCore.dll!ElementFinder::Find() Unknown
    UIAutomationCore.dll!CAutomationElement::Find() Unknown
    UIAutomationCore.dll!CAutomationElement::FindAll(e num TreeScope,struct IUIAutomationCondition *,struct IUIAutomationElementArray * *) Unknown
    > myProgram.exe!ForEachElement(IUIAutomation * uia, IUIAutomationElement * window, const long controlType, bool(*)(IUIAutomationElement *, wchar_t *) callback) Line 154 C++
    myProgram.exe!GetStatusWidgetState() Line 388 C++
    myProgram.exe!SetStatus(const wchar_t * status) Line 154 C++
    myProgram.exe!wmain(int argc, const wchar_t * * argv) Line 99 C++
    [External Code]

    Then I press the "continue" button (looks like a little green arrow) and VS barfs some more:

    Code:
    Unhandled exception at 0x00007FF8525991C9 (ntdll.dll) in myProgram.exe: 0xC0000374: A heap has been corrupted (parameters: 0x00007FF8526027F0).
    
    Debugger says:
    -        &allButtons    0x00000025808ff2f8 {0x0000000000000000 <NULL>}    IUIAutomationElementArray * *
        -            0x0000000000000000 <NULL>    IUIAutomationElementArray *
            -        IUnknown    <struct at NULL>    IUnknown
                    __vfptr    <Unable to read memory>    void * *
                    TreeScope_Descendants    TreeScope_Descendants (4)    TreeScope
    -        isType    0x00000170d2cda480 <No type information available in symbol file for UIAutomationCore.dll>    IUIAutomationCondition *
        -        IUnknown    {...}    IUnknown
            -        __vfptr    0x00007ff8317a8648 {UIAutomationCore.dll!const ATL::CComObject<class CPropertyCondition>::`vftable'{for `IUIAutomationPropertyCondition'}} {...}    void * *
                    [0]    0x00007ff83165e360 {UIAutomationCore.dll!ATL::CComObject<...>::QueryInterface(void)}    void *
                    [1]    0x00007ff8316c6290 {UIAutomationCore.dll!ATL::CComObject<class CAndCondition>::AddRef(void)}    void *
                    [2]    0x00007ff83165bc30 {UIAutomationCore.dll!ATL::CComObject<class CPropertyCondition>::Release(void)}    void *
    -        window    0x00000170d2cc4bd0 <No type information available in symbol file for UIAutomationCore.dll>    IUIAutomationElement *
        -        IUnknown    {...}    IUnknown
            -        __vfptr    0x00007ff8317a07f0 {UIAutomationCore.dll!const CAutomationElement::`vftable'{for `IUIAutomationElement9'}} {...}    void * *
                    [0]    0x00007ff83163ee10 {UIAutomationCore.dll!CAutomationElement::QueryInterface(void)}    void *
                    [1]    0x00007ff83165a340 {UIAutomationCore.dll!CAutomationElement::AddRef(void)}    void *
                    [2]    0x00007ff831655eb0 {UIAutomationCore.dll!CAutomationElement::Release(void)}    void *
    
    Stack trace:
         ntdll.dll!00007ff8525991c9()    Unknown
         ntdll.dll!00007ff852599193()    Unknown
         ntdll.dll!00007ff8525a1582()    Unknown
         ntdll.dll!00007ff8525a188a()    Unknown
         ntdll.dll!00007ff8525aa899()    Unknown
         ntdll.dll!00007ff85254726d()    Unknown
         ntdll.dll!00007ff8524e06e1()    Unknown
         ucrtbase.dll!00007ff8504514cb()    Unknown
         ucrtbase.dll!00007ff85045ad22()    Unknown
         UIAutomationCore.dll!MessageParser::GetIntArray()    Unknown
         UIAutomationCore.dll!LocalUiaNodeProxy::CrossProcess_GetRuntimeId()    Unknown
         UIAutomationCore.dll!UiaNode::CrossProcess_GetRuntimeId()    Unknown
         UIAutomationCore.dll!ElementFinder::Find()    Unknown
         UIAutomationCore.dll!CAutomationElement::Find()    Unknown
         UIAutomationCore.dll!CAutomationElement::FindAll(enum TreeScope,struct IUIAutomationCondition *,struct IUIAutomationElementArray * *)    Unknown
    >    myProgram.exe!ForEachElement(IUIAutomation * uia, IUIAutomationElement * window, const long controlType, bool(*)(IUIAutomationElement *, wchar_t *) callback) Line 154    C++
         myProgram.exe!GetStatusWidgetState() Line 388    C++
         myProgram.exe!SetStatus(const wchar_t * status) Line 154    C++
         myProgram.exe!wmain(int argc, const wchar_t * * argv) Line 99    C++
         [External Code]
    Wow... Okay... So... Hmmmmmm... But... Whaaaaaaa? :P

    Okay, let's see if we can try and decrypt this steaming pile of melted Microsoft...

    * We know our inputs ("window" and "uia") are not NULL. There are NULL-checks and fail-checks all over the code that calls this, and I added a few more just cuz (by "ID10T errors" lol)
    * We know our condition, "isType", is NOT null. So CreatePropertyCondition worked as expected. This is one of the things that get passed into FindAll so this is important to note. The debugger recognize it as an "IUnknown" (one of the parent classes of "IUIAutomationCondition" I guess - lol not an OOP fan so I don't really care). I do tho wonder why it says there is not type info in whatever symbol list it uses...
    * When the code gets all happy-go-crashy, the variable "allButtons" is still a NULL pointer. This makes sense if something in FindAll went sideways; its purpose is to fill it with a list of elements.
    * Now I love low-level code. I'm a hard core C guy. I do 6502 Assembly but don't know x86 (yet). Neither of the memory addresses in the error messages correspond to anything in the debugger, so AFAIK the memory addresses are totally useless.
    * For some reason, VS actually points to the line _after_ FindAll (the if (FAILED(RESULT)) part); now [FAILED](FAILED macro (winerror.h) - Win32 apps | Microsoft Learn) is just a macro. And after refactoring my code many, many times (dueling with the same mysterious opponent) I'm pretty sure it's still FindAll that's actually in Bionic Baloney Mode. Also, the stack trace points at FindAll in both points. But, the IDE says FAILED is what failed, so I figured that was worth mentioning.
    * The error message, "a heap was corrupted" sounds like I did something goofy with memory. I'm all too familiar with double-frees, dereferencing NULL pointers, etc... but I don't see anything even remotely like that in my code. I do all the standard NULL-checks and fail-checks to the max, and it's not like we're dealing with C-style linked lists or even simple arrays that I allocated on the heap. So what does this mean, a heap was corrupted? What heap memory got corrupted? How did it get corrupted? What does "corrupted" even mean? Like a segfault? Obviously, the error message itself is pretty useless too, offering more questions than answers.
    * And of course, I looked up the [docs for FindAll](IUIAutomationElement::FindAll (uiautomationclient.h) - Win32 apps | Microsoft Learn) but it didn't say anything about conditions that would cause it to throw (up).

    My only guess... and I'm really down to guessing and an infinite loop of trial/error/DuckDuckGo... is that it's something buried deep in the internals of UIA and/or COM. But I don't know where else to turn. I wish this were like my previous two threads I posted, where Visual Studio decided to spew an epic rage-barf over simple syntax errors. the more I use VS, the more I run into mind-grinding, headache-inducing nonsense like this that just... I'm sorry, hard not to get a little cranky when you've been slamming your head ingo the same stupid bugwall all stupid day. Why, Microsoft? Why?
    Last edited by LighthouseMike; 11-16-2022 at 07:09 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 11
    Last Post: 07-10-2019, 08:29 PM
  2. Some woman back ended my car today, and back hurts
    By Terrance in forum A Brief History of Cprogramming.com
    Replies: 19
    Last Post: 08-20-2003, 12:42 AM
  3. Aran strikes back
    By Aran in forum A Brief History of Cprogramming.com
    Replies: 8
    Last Post: 08-11-2002, 11:22 AM
  4. Original end to Empire Strikes back
    By Fordy in forum A Brief History of Cprogramming.com
    Replies: 4
    Last Post: 06-24-2002, 08:56 AM

Tags for this Thread