Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Things you shouldn't do, part 2: Dll's can't ever call CoInitialize* on the application's thread.

Things you shouldn't do, part 2: Dll's can't ever call CoInitialize* on the application's thread.

  • Comments 7

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.

 

  • The not so great news is PostQueuedCompletionStatus is NT only. QueueUserAPC is available in 9x though.
  • Great article. I recently came across a DLL that called CoInitialize, and this caused my DLL (a COM component) to fail. Luckily I had the source code to this DLL. I'm not sure what could be done without the source.
  • Yech. Please tell me that that DLL wasn't part of a shipping system :).

    And if it was, that it wasn't from a Microsoft product :)
  • Definitely not a shipping system. The DLL was a project from CodeProject.com. So basically it was sample code. It did work perfectly apart from the CoInit/UnInit though.
  • That's a relief. Now the problem is: How do you fix it? Any clients that successfully use this module depend on it's calling CoInitialize/CoUninitialize. 'Tis a puzzlement.

    In your case you can just rename your private version of the DLL. But what if it had shipped?
  • PingBack from http://www.keyongtech.com/428983-serious-bug-in-net-framework/2

Page 1 of 1 (7 items)