Interface Smuggling

While I'm on the subject of COM and extension compatibility, another issue that affected a small number of extensions in IE7 involved passing an interface to a worker thread without first marshalling the interface using CoMarshalInterface() (or the longer and more convenient form, CoMarshalInterThreadInterfaceInStream()).

IE uses the uses the Single Threaded Apartment (STA) model for its UI threads, which in normal scenarios works like the following:

  • If you're on the same thread, calling a method on a COM object is the same as calling directly into the object through the vtable like an ordinary C++ class.
  • If you're on a different thread, all calls need to be marshaled back to the original thread (via. window messages) so that it's impossible for multiple threads to call into the object at the same time, and all calls originate from the same thread the object was instantiated on (i.e. some objects may use TLS).

If you create a worker thread and hand it a direct pointer to the object, it appears to work at first because it's all just memory; COM does not actively enforce its rules. However this breaks down quickly for a couple of reasons. Most obviously, STA objects are not designed to be thread safe. Passing a direct pointer to the object to a worker thread violates this assumption and will cause subtle bugs, hangs, and crashes.

But that's not what broke extensions between IE6 and IE7. In IE6 (and previous versions), developers often lucked out on the thread safety risk, but IE's new multithreaded architecture resulted in some public API calls needing to go across threads within Internet Explorer. In this scenario, when the pointer to the interface was smuggled onto a worker thread and then IE attempted to make an internal call through an internal pointer that had been marshaled for the non-worker thread, the call would fail with an error such as RPC_E_WRONG_THREAD.

In one case we managed to work around the problem (it was in a very popular extension) by changing our implementation for that specific API to use global/shared memory instead of internally requiring a cross-thread COM call. This of course isn't sustainable and it's not something we can do most of the time. If you're an extension developer please keep in mind that you might still be lucking out, even in IE7, so check your implementation to see if you're using multiple threads, especially if you pass IWebBrowser2 or similar interfaces to the other threads, and that you're marshalling interfaces correctly.

 

Fine print: This applies to the STA model; details for other threading models may vary. Also, never use the dreaded main threading model in IE extensions. Ever. Actually, it's a good idea not to use it outside of IE extensions too, unless you like reentrancy. :-)