Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

When global destructors bite

When global destructors bite

Rate This
  • Comments 10

In my work, I use a lot of ATL.  And in general, I'm pretty impressed with it.  I recently ran into a cool bug that I figured would be worth posting about.

First, what's wrong with the following code?

 

main.cpp:

#include <stdafx.h>

 CComPtr<IUnknown> g_pUnknown;

 

 void __cdecl main(int argc, char *argv[])

 {

    HRESULT hr;

    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    if (hr == S_OK)

    {

           hr = g_pUnknown.CoCreateInstance(CLSID_IXMLDOMDocument);

                   :

                   :

    }

    CoUninitialize();

}

Assume that the program uses ATL and the ATL definitions are included in the stdafx.h file.

Looks pretty simple, right?  Well, if you run the app under the debugger, you're likely to find that it silently access violates when it exits.

The problem occurs because CComPtr's are auto-pointers.  This means that when they’re destroyed, they release the contained pointer.  Normally, that's a good thing - the reference counts are removed and the object is released.  This works AWESOMELY if the CComPtr is scoped to a function or as a member variable in a class.

But when the CComPtr is a global variable, when is the destructor called?

The answer's that the destructor is called when the C runtime library runs down all the global variables.  And that happens when the C runtime library DLL is unloaded.

So why is that a problem?  Well, when the last thread in the process calls CoUninitialize(), COM says "Hey, he's done using COM.  I can now unload all the DLL's that I loaded into the process".  This includes the MSXML3.DLL that contains the XML DOM.  So when the C runtime library runs the destructor for the CComPtr, it tries to release the reference to the embedded IUnknown pointer.  And that tries to access code in the MSXML3.DLL file which is no longer loaded.  And blam!

 

 

  • Yeah I have run into this scenario. But I figured what the problem was and got around it. If you have any elegant solution for this, please post it.
  • I don't have any elegant solutions - except the obvious one: change the CComPtr<IFoo> to IFoo* and go without the automatic destruction semantics. Or manually call g_pUnknown.Release() before calling CoUninitialize()

    In this case, the automatic dereferencing doesn't buy you anything so...
  • The rule in our code is, never declare any global variables with constructor. Always use pointers for that, and dynamically generate the object.

    The reason goes beyond what this blog talks about. It has to do how C++ initialize global variables, and how it destroy them.

    Martyn Lovell had a series on C++ initialization (http://blogs.gotdotnet.com/martynl/). Too bad he did not finish it.
  • I've been bitten by global destructors in a DLL. It's very difficult to arrange things so that you don't do dangerous things under the OS loader lock, either on construction or destruction. Basically, global objects under DLLs are pretty dangerous things.

    The solution was to add library initialisation and termination functions, and call them explicitly.

    Debugging stuff after main (or WinMain) has returned definitely feels odd...
  • I keep running into this bug too (and most often with MSXML), but I still don't fully understand it. Isn't a DLL-based COM server supposed to keep itself locked for as long as there is any outstanding reference on an object? Well, I know at least CComObjectGlobal<> does something like this. Is there some general way to make any object that keeps only its own reference count to also increase/decrease the reference count on the module containing it?
  • So the solution would be either to have a global CoInitializerUninitializer object that would get constructed before the CComPtr and thus destructed afterwards, or the CComPtr shouldn’t be global (actually, it’s not being global which matters; it’s static storage duration[3.7.1]).

    Speaking of DLLs, as Mike Dimmick pointed out, non-trivially-destructed objects with static storage duration in DLLs are Pure Evil™, as they will be destroyed during DllMain, which is the last place you want to do anything interesting.
  • Centaur: You're right, I had forgotten about static storage - I ran into the problem with a global CComPtr, so that's the one I wrote about.

    The problem with the global CoInitializeUninitializer object is that you can't guarantee the order of construction of static objects. Also, you can't do this in a DLL, since you can't call CoInitializeEx in your DllMain.
  • Chango V.: Yes, a DLL based COM server is supposed to keep itself loaded into memory. That's the point of the DllCanUnloadNow entrypoint.

    But when you call CoUninitialize for the last time in the process, you're telling COM that you're done using COM. NO calls into COM will succeed after you've called it, since you're done. Those include calls into COM objects you've allocated.
Page 1 of 1 (10 items)