The advent of neutral COM objects introduces the interesting consequence that your thread might have to share an apartment. This means that if you assume all asynchronous calls into your thread are on the same apartment, you probably have a subtle bug. Here’s an example that I ran into recently:

  • My STA thread runs a WndProc which receives asynchronous posted messages WM_DOWORK. The WndProc is associated with a thread-affine object that holds pointers to other STA objects and in response to WM_DOWORK, marshals one of these objects into the stream and returns that stream to the asynchronous caller. This is a common pattern that I’ve seen many times.
  • That very same thread calls an API which eventually CoCreate’s a neutral object. The neutral object runs in a new apartment hosted on the same thread, a neutral apartment. While running, the neutral object decides to itself call CoCreate, which ends up pumping messages. The message pumping therefore happens in the neutral apartment.
  • During this message loop, my thread reenters and processes a WM_DOWORK and in response does its job of marshaling one of its objects into a stream by calling CoMarshalInterThreadInterfaceInStream. COM, when deciding how to marshal the interface, looks at the current call context and sees that the object is in the neutral apartment. As a result, the object is marshaled from the neutral apartment instead of the STA.
  • Later, when the object is unmarshaled from another thread, a transition happens into the neutral apartment on the same thread, which results in the object’s pointer being accidentally smuggled across threads.

If you’re lucky, then some visible problem (like a concurrency issue) will result from this sort of problem.

If you’re really lucky, it will repro somewhat consistently.

The main problem here is that, while the assumption under STA is that apartment implies thread, we cannot assert the consequent and assume that thread implies apartment. So, any asynchronous entry into the thread has to somehow ensure the correct COM context before making calls into apartment-affine objects.

Luckily, COM provides an API for doing just this. One solution to the problem described above, then, is:

  • When the WndProc is created, it remembers its COM context by calling CoGetCallContext for IContextCallback.
  • Later, when the WndProc is called, it calls IContextCallback::ContextCallback to get back into the correct COM context. Then it proceeds to dispatch messages.

Q: But Marc, this is crazy, no one calls this API today.
A: You’re right.