
Internet Explorer runs on multiple threads, and doing operations such as asynchronous web page fetches may require your BHO to spawn its own threads. This means that any code that isn't directly called through your Invoke() entry-point may be on a different thread than the browser window it's attached to. This means that you have to think carefully about how you access data that other threads might be using, to avoid crashes and data corruption. One of the most common situations this will happen is when you're trying to access the document object model.
In my case I was seeing a lot of crashes once I started analyzing search results, and fetching pages. I discovered that the crashes all seemed to happen when multiple threads were all accessing the same document. I was passing a pointer to the IHTMLDocument3 object to the XMLHttpRequest worker threads, and after a little thought, it made sense that this object wasn't thread-safe, and so I'd need to synchronize my access to it somehow.
I couldn't figure out a good way to call back my BHO on the main thread, because the BHO doesn't control the message loop for the main thread, that's all in IE. And since IE could be using the same object, and I don't control that code, I couldn't implement my own manual locking.
Luckily, I came across a life-saving post from Tony Schreiner. He explains that COM objects in IE are not thread-safe by default, but that you can get a wrapped version that adds thread-safe locking by calling CoMarshalInterface(). This gives you a version of the object that you can use on a different thread, without worrying about crashes caused by other threads accessing it at the same time. Using this technique solved all my crashing issues.
One drawback with this is that you can only pass a pointer once to another thread, since retrieving it destroys the marshalled representation. This is known as marshal once/unmarshal once. There are a couple of more complex ways to create marshalled representations you can use multiple times, I later ended up using the Global Interface Table method in PeteSearch in some other places.
Here's an MS document that has an overview of how to pass COM pointers between threads.
I did run into some quirks when doing DOM access from different threads. I needed to call the IHTMLWindow2 interface from event callbacks, to get information about the last event, but I would get an E_NOINTERFACE error every time I tried to unmarshal one of these, and also got NULL back if I called get_parentWindow() on the unmarshalled document interface. In the end, I had to resort to passing the raw pointer between threads, since that appeared to work, and keep my fingers crossed this won't cause problems down the line.
I also found that I'd hang the whole of IE if I accidentally accessed an element interface pointer from one thread on another one!