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. :-)

Comments

  • Anonymous
    December 12, 2006
    I commonly see people having problems on CodeProject.com's forums where a control or BHO works fine in the first IE6 window, and if a new window is created from the start menu or Quick Launch bar (which of course creates a new iexplore.exe process) but then behaves oddly when a new window is opened in an existing process (which creates a new thread in the same process). No doubt these multithreading-challenged extensions will have equal or greater problems in a tabbed IE7 browser!