Actually, it’s not.
First, a quick review. I know that lots of others have gone into this in great detail before (Dare and Eric in particular), but a bit of refreshing always helps.
The system calls a DLL’s DllMain entrypoint when a DLL is loaded into a process, when the DLL is unloaded from the process, when a thread is created and when a thread is destroyed. Four messages are used for this, DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, and DLL_THREAD_DETACH respectively.
When your DllMain entrypoint receives a DLL_PROCESS_DETACH, there is an additional piece of information provided: The lpvReserved parameter to DllMain is NULL if the DLL is being unloaded because of a call to FreeLibrary, it’s non NULL if the DLL is being unloaded due to process termination.
Ok, so much for the review.
When NT unloads a process gracefully (due to a call to ExitProcess()), it calls all the DLL entrypoints in roughly the reverse order that the DLL_PROCESS_ATTACH entrypoints were called (there’s absolutely no guarantee of the order though). NT tries to issue the DLL_PROCESS_DETACH message to a DLL after its issued DLL_PROCESS_DETACH messages for all the DLL’s that depend on that DLL, but it doesn’t always happen (because of circular dependencies, etc).
So consider the case where you have a DLL that instantiates a COM object at some point during its lifetime. If that DLL keeps a reference to the COM object in a global variable, and doesn’t release the COM object until the DLL_PROCESS_DETACH, then the DLL that implements the COM object will be kept in memory during the lifetime of the COM object. Effectively the DLL implementing the COM object has become dependant on the DLL that holds the reference to the COM object. But the loader has no way of knowing about this dependency. All it knows is that the DLL’s are loaded into memory.
Now the process terminates gracefully. The loader calls the DllMain entrypoint on all the DLL’s in the process, specifying DLL_PROCESS_DETACH. It’s entirely possible (in fact highly likely in this case) that the DLL_PROCESS_DETACH message for the DLL implementing the COM object will be called BEFORE the DLL_PROCESS_DETACH message for the DLL that holds the reference to the COM object.
So this means that the DLL that implements the COM object will get the DLL_PROCESS_DETACH message, even though there are still live COM objects that use the code in the DLL!
We ran into this with some of our leak detection code, it was generating a false positive – it reported a leak in the DLL_PROCESS_DETACH code when in fact the objects were being referenced by another DLL.
When I brought this up on an internal alias, one of the people on the NT base team indicated “There’s basically no way you can do anything other than freeing memory” in the case where a DLL_PROCESS_DETACH message is called from process shutdown. You can do reliable processing on the FreeLibrary case, but not in the process termination case.
Ultimately, I believe that the real culprit here is the DLL that keeps the COM object reference alive. That DLL is violating the “It is not safe to call FreeLibrary from a DllMain routine” stricture, because
(a) There’s no way of knowing if CoInitialize has been called on the current thread – COM might not be initialized currently.
(b) It’s possible that the call to ComObject->Release() would cause FreeLibrary to be called,