When a Dll’s executing code on an application’s behalf, the Dll can NEVER call CoInitalizeEx on the application’s thread.
Why? Because you can’t know the application’s threading model, so you can’t get it right. If the app’s initialized COM in a single-threaded apartment and you attempt to put the thread into the multi-threaded apartment, the CoInitializeEx call will fail. Similarly, if the app’s called CoInitializeEx put the thread in the MTA, you can’t reinitialize in the STA. You could add code to allow the RPC_E_CHANGED_MODE error return that CoInitializeEx returns, but there are sometimes COM objects that require a particular threading model.
This is especially true if your DLL allocates some object that contains pointers to other COM objects and returns a pointer to that object. A good example of an API that uses this pattern is CryptAcquireContext – I’m not saying that it uses COM, it probably doesn’t, but it COULD.
An API written using the XxxAcquireContext/XxxReleaseContext design pattern could be tempted to call CoInitializeEx() in the XxxAcquireContext routine and call CoUnitialize in the XxxReleaseContext routine. And that would be utterly wrong. The problem is that in this case, if you initialized COM in the MTA during the XxxAcquireContext routine, and then the application attempted to create an STA for the thread, the app’s call will fail. And the app is entirely likely to be quite unhappy with its call to CoInitialize failing.
Even if the application ignored the error, then the application would potentially get callbacks on other threads, which is likely to break the application. So you say “Ok, I can deal with this, I’ll just initialize myself in an STA. But then if the app attempted to put the thread in the MTA, you’ve once again messed up the application.
You just can’t win.
In addition, this rule has some interesting corollaries: You either need to rely on the application to call CoInitialize for you, and add this to your documentation, or you need to do all your COM interactions on a separate worker thread. If you’re dealing with a legacy component however, you have no choice but to move your COM interactions to a separate worker thread. I first ran into this issue when I made some modifications to winmm.dll to add support that required interacting with a COM component. The good news is that it’s pretty easy to set up a worker thread to do the work – just create the thread and use PostQueuedCompletionStatus to post work items to the queue.