c++comcom-interopatlapartments

CoInitializeEx returning S_OK when called inside a COM object


Some time ago, I had to modify an old COM DLL (Visual C++ 2010, ATL) migrating it from "Apartment" threading model to "Both", i.e. it can now be called from both STA and MTA threads without serializing calls (of course, I had to add internal synchronization for shared data). This in turn caused problems when translating COM events (connection points) to .NET events when my DLL is called via Interop from a .NET application (I must support both STA and MTA even in .NET applications). To tackle these problems, I changed the way events are fired.

1) If the DLL is called in a STA context, it works like before, i.e. it creates an invisible window, then, when the event must be raised, it calls PostMessage to that window, then the main STA thread calls the actual event-firing code, i.e. CProxy_IMyEventFiringInterface member functions (CProxy_IMyEventFiringInterface derives from IConnectionPointImpl).

2) if the DLL is called in a MTA context, I don't have a main COM thread and I cannot do PostMessage, so I use a custom thread I create and let that thread call IConnectionPointImpl functions.

But AFAIK there is no Windows API that detects if the calling thread is STA or MTA. Many websites suggest to use CoInitializeEx like this:

HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
switch(hr)
{
    case S_FALSE:  // we are in a Multi-Threaded Apartment (MTA)
    CoUninitialize(); // each successful call to CoInitialize or CoInitializeEx, including any call that returns S_FALSE, must be balanced by a corresponding call to CoUninitialize
    break;
    case RPC_E_CHANGED_MODE:  // we are in a Single-Threaded Apartment (STA)
    break;
    default:  // IMPOSSIBLE!!!!
}

I decided to put this call to CoInitializeEx in CMyComComponent::FinalConstruct. Everything worked fine... till today. In a customer scenario, I see from my tracing tool that for a certain .NET EXE application (I do not have the source code) the above code ends up in the default branch because CoInitializeEx returned S_OK. How can this be possible? Microsoft documentation says that S_OK means "The COM library was initialized successfully on this thread", but I am INSIDE a COM object, the COM library MUST be already initalized! By the way, the default branch does not shut down the application, but, since S_OK was returned, it called CoUninitialize (each successful call to CoInitialize or CoInitializeEx, including any call that returns S_FALSE, must be balanced by a corresponding call to CoUninitialize) and then the DLL goes on assuming STA (a wrong move in hindsight). But it's not STA: in fact, a later PostMessage returns FALSE.

I can simply change the code to use MTA as the default if CoInitializeEx(NULL, COINIT_MULTITHREADED) returns S_OK, I should have done it right from the start. But I also want to be SURE that this is the right thing to do, to avoid further problems in the future. Thank you very much Demetrio


Solution

  • This is possible when you are on implicit MTA thread. The correct function is like this:

    BOOL IsMultiThreadedApartment() throw()
    {
        HRESULT nResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
        if(SUCCEEDED(nResult))
            CoUninitialize();
        return SUCCEEDED(nResult);
    }
    

    You are hitting this unusual situation because the caller application failed to initialize COM on the thread it instantiates your class on. However be aware that if later the caller initializes the thread as STA, you might get into tricky situation having your class running in MTA mode on STA thread. Doing CoInitializeEx yourself on the other hand might cause failure of the caller to do erroneously late COM initialization.