2

According to the documentation, if the COM library is initialized in "apartment threaded" mode, the thread must have a message loop.

However, I found that using COM in a thread with no message loop works just fine. Here is a working example that demonstrate it.

#include <iostream>
#include <oleacc.h>

using namespace std ;

int main(){
    auto dektopWin = GetDesktopWindow() ;
    
    POINT point{} ;
    GetCursorPos(&point) ;
    
    auto hWnd = ChildWindowFromPoint(dektopWin, point) ;
    
    if( hWnd && hWnd!=dektopWin ){
        CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) ;
        IAccessible* accPtr{} ;
        auto result = AccessibleObjectFromWindow(hWnd, OBJID_WINDOW, IID_IAccessible, (void**)&accPtr) ;
        if( result==S_OK ){
            RECT rect{} ;
            VARIANT VAR_CHILDID_SELF{VT_I4, 0,0,0, CHILDID_SELF} ;
            result = accPtr->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, VAR_CHILDID_SELF) ;
            if( result==S_OK ){
                cout << "Window coordinates"
                     << rect.left << " "
                     << rect.top << " "
                     << rect.right << " "
                     << rect.bottom << endl ;
                return 0 ;
            }
        }
    }
    cout << "Something went wrong" ;
}

The program calls the IAccessible::accLocation method on the window under the mouse and then prints the window coordinates to the console.

Why does this code works if there's no message loop?
Can you explain what sort of situation requires the thread to have a message loop?


Alternative version of the code:

#include <iostream>
#include <oleacc.h>

using namespace std ;

void printWinAccLocation(HWND win){
    IAccessible* accPtr{} ;
    auto result = AccessibleObjectFromWindow(win, OBJID_WINDOW, IID_IAccessible, (void**)&accPtr) ;
    if( result==S_OK ){
        RECT rect{} ;
        VARIANT VAR_CHILDID_SELF{VT_I4, 0,0,0, CHILDID_SELF} ;
        result = accPtr->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, VAR_CHILDID_SELF) ;
        accPtr->Release() ;
        if( result==S_OK ){
            cout << "Window coordinates "
                 << rect.left << " "
                 << rect.top << " "
                 << rect.right << " "
                 << rect.bottom << endl ;
        }
    }
}

int main(){
    auto dektopWin = GetDesktopWindow() ;
    
    POINT point{} ;
    GetCursorPos(&point) ;
    
    auto win = ChildWindowFromPoint(dektopWin, point) ;
    
    if( win && win!=dektopWin ){
        CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) ;
        
        printWinAccLocation(win) ;
        Sleep(2000) ; //is the thread idle here?
        printWinAccLocation(win) ;
        Sleep(2000) ; //also here?
        printWinAccLocation(win) ;
        
        CoUninitialize() ;
    }
}
Seth
  • 83
  • 7
  • *what sort of situation requires the thread to have a message loop* this need only for process incomming calls from other apartments – RbMm Mar 07 '22 at 13:36
  • 3
    The code doesn't make any calls from a worker thread. The message loop ensures such a call executes on the thread that created the interface. And perhaps more relevant to Automation, the code does not depend on any callbacks (aka events), such a callback is highly likely to be triggered by code that belongs to another process and thus another thread. It just won't fire. So sure, you got away with it. For now. https://stackoverflow.com/a/10661408/17034 – Hans Passant Mar 07 '22 at 13:53
  • 1
    Single-threaded apartments must run a message loop when they are idle. (Clearly they cannot run a message loop when they are busy.) Your thread never goes idle: Once it finishes doing work, it terminates. So you get away without having a message loop. In a real program, the thread will go idle at some point, and then it needs to pump messages. – Raymond Chen Mar 08 '22 at 02:01
  • @RaymondChen, in my real program, I don't use windows or GUIs of any kind. It's a "background" program of sorts with no visible elements. So I don't need a message loop. But I do need to use COM. So, this question is meant to give me the understanding of why my program works and whether I will need a message loop in the future. – Seth Mar 08 '22 at 02:13
  • 1
    It is fine for a thread to initialize COM in apartment mode, do stuff, and the uninitialize COM. The point is that when your thread is not doing anything, and COM is initialized, then it must pump messages. – Raymond Chen Mar 08 '22 at 03:38
  • 1
    @RaymondChen My program communicates through the network with another program. Most of the time it's doing nothing except wait for network messages. Is it wrong to keep COM initialized while the program is idle? – Seth Mar 08 '22 at 04:13
  • What I say three times is true: If COM is initialized and the thread is idle, it must pump messages. If you don't want to pump messages when idle, then you need to uninitialize COM before you go idle. Note that before you uninitialize, you must destroy any COM objects you had created. In the above example, you must release the `accpPtr` before you uninitialize COM. – Raymond Chen Mar 08 '22 at 04:27
  • 2
    @RaymondChen, I read that each time, but please understand that saying "It must be done this way" without a little explanation of why is rather frustrating. Plus, the concept of "being idle" is not sufficiently clear to me. For example, is a thread idle when it calls `Sleep(2000)`? I added an example of this in the question above. – Seth Mar 08 '22 at 05:43
  • 2
    Sorry. Here's what's going on: If COM wants to do something on your thread (for example, another thread wants to call a method on an object that belongs to your thread), the request arrives via the message pump. Therefore, your thread needs to pump messages in order to process that inbound request. Of course, if your thread is actively doing work, it isn't available to do other work (a thread can do only one thing at a time). But when your thread isn't actively doing work (like sleeping), it should allow other work to run so that those other threads can make progress. – Raymond Chen Mar 08 '22 at 14:07
  • @RaymondChen, thanks for the explanation. It makes sense now. – Seth Mar 08 '22 at 23:57
  • @RaymondChen "*If COM is initialized and the thread is idle, it must pump messages.*" can be confusing. A thread (with COM initialized) could do work, sleep a bit (or do unrelated IO), do work again, and then pump messages; then loop. – Acorn Mar 25 '22 at 22:31
  • @Acorn And if it does that, the thread cannot handle COM work, and other threads are waiting for objects on that thread will hang. If you want to keep things moving, you should pump messages when you aren't actively doing work. – Raymond Chen Mar 25 '22 at 23:25
  • @RaymondChen It's handling COM work when pumping every iteration ("loop" in my other comment meant "repeat again the sequence of work, idle, work, pump", not "forever spin thus never pump again"). Or are you saying COM requires a thread is never in a waiting state as long as COM is initialized? – Acorn Mar 26 '22 at 00:38
  • If you are okay with your thread not responding, then go for it. – Raymond Chen Mar 26 '22 at 01:17
  • @RaymondChen Why would the thread not respond (necessarily)? Consider a simple game with a loop iterating at 60 hertz. The game idles for some milliseconds somewhere in the loop (say, waiting for vsync, or for crude frame limit purposes, or performs a fast blocking IO call), but the game is still pumping every iteration. Does a game really need to uninitialize and reinitialize COM every time it sleeps or issues a blocking IO call? In other words, were you thinking about "long" pauses or is there a fundamental requirement using COM? – Acorn Mar 30 '22 at 15:30
  • If you don't pump, then the thread cannot service inbound requests. Stalling inbound requests for 1/60 second might be okay if you're not expecting a lot of them. – Raymond Chen Mar 30 '22 at 15:35
  • @RaymondChen That makes more sense, thanks a lot. I was suddenly worried there was a fundamental requirement around idling with COM enabled that I may not be aware of (say, considered completely unsupported). – Acorn Mar 30 '22 at 15:36
  • 1
    As I previously said, "If you are okay with your thread not responding, then go for it." COM doesn't know the difference between "this thread is doing 16ms of important computation" and "this thread has gone into a spin loop for 16ms waiting for the vblank." Or the difference between "This thread is waiting for an I/O to complete" vs "This thread is waiting for a timer to become signaled." All COM knows is "This thread is not pumping messages and therefore I have no way to get it to run some code as requested by another component." – Raymond Chen Mar 30 '22 at 15:40
  • Ah, I misunderstood that sentence as "not supported, you may end up with a thread never responding again, but feel free to try". Now it makes sense. Thanks! – Acorn Mar 30 '22 at 16:08

0 Answers0