On Friday I talked about some of the problems with unbalanced CoInitialize() and CoUninitialize() calls and how to debug an unbalanced CoUninitialize inside IE7. At the end I mentioned that using IInitializeSpy in an extension to “fix” an unbalanced init or uninit was a bad thing and to never do it. Here’s one story that illustrates why.
In the late stages of IE7 beta testing a coworker found that IE7 on XP would sometimes hang when sending messages through Outlook Web Access. At first the problem was very difficult to reproduce, but eventually we caught it under a debugger and found out that an unbalanced CoInitialize() call was causing COM to do cleanup while holding the loader lock, leading to a deadlock (as described in Friday’s post).
But how could this be? On XP IE7 still uses IInitilaizeSpy to force this into balance, and then does the final CoUninitialize() right at the end of the threadproc! It should be impossible for there to be an unbalanced init in this scenario.
After a lot of debugging we found that in scenarios where IE hung, there was an additional spy loaded in the process, and when IE would attempt its final uninit the spy would jump in and call CoInitialize() again. No matter how late or how many times IE called CoUninitialize(), it would always bump it back up and out of balance.
The component doing this was part of the alternative Input Method Editor (IME), which is used for things such as entering Japanese characters. It’s not installed on an English XP build by default, which is one reason this was hard to track down. Years ago, to work-around an application compatibility issue, they decided to leverage IInitializeSpy to fix an unbalanced CoUninitialize().
After more investigation we found that the IME component called CoInitialize() from their spy if there were any other top-level windows still visible on that thread. IE closes its normal windows well before the end of the threadproc, but in the OWA scenario it turned out that if you hovered over the “Send” button until the tooltip showed, and then sent the message, that tooltip window would stick around until the end of the threadproc and cause the IME component to do the extra CoInitialize(), triggering the hang.
The brilliant counterplan we devised to combat their spy was to hide all windows on the thread before calling the final CoUninitialize(). This way the IME component stops hooking the windows and won’t call CoInitialize() to force the thread out of balance.