none
Problems with atexit() in a DLL

    Question

  • I am trying to get the PhoenixSingleton pattern example found in "Modern C++ Design" to run. I am using the Singleton in a DLL.
    I don't deliver any references of the Singleton to outside-DLL callers so that's not my problem. The singleton works perfectly, the only problem is.. after reviving the singleton, it doesn't get destroyed, meaning the destructor is not called.
    I checked that by using the DLL in a simple test application and sending messages to std::cout in every member function.
    Every single function is called in the correct order as soon as the DLL is unloaded, the Singleton gets destroyed for the first time, it gets revived.. but the destructor doesn't get called a second time. Somehow I suspect atexit() doesn't work here.

    I am using Visual Studio 2005 and I already tried using _onexit() but that thing doesn't seem to accept member function pointers at least I always get conversion errors there.

    Any ideas or insights on how to get this to work?
    The object that I put into the singleton is just a simple logging class
    that I use in my application to write simple statements to a textfile and that holds only a fstream object, so when the application is terminated there shouldn't be any ressource leaks even if the destructor isn't called but being the perfectionist I am I'd like this to work as it is supposed to.

    Thanks in advance
    Wednesday, April 11, 2007 9:20 AM

All replies

  • Could you post some code please.
    Wednesday, April 11, 2007 11:44 AM
  • Code Snippet

    //----------------------------PhoenixSingleton.h-----------------------------

    #include <cstdlib>

    template <class T>
    class PhoenixSingleton : public T
    {
    public:

        static PhoenixSingleton& GetInstance();

    private:

        // Constructors, destructor and assignment operator
        // Declared private to prevent usage
        PhoenixSingleton();
        PhoenixSingleton(const PhoenixSingleton& source);
        PhoenixSingleton& operator=(const PhoenixSingleton& source);
        virtual ~PhoenixSingleton();

        // Private methods
        static void CreateInstance();
        static void DestroyInstance();
        static void ReviveInstance();

        // Attributes
        static PhoenixSingleton* pInstance_;
        static bool                         isInstanceDestroyed_;

    }; // class PhoenixSingleton

    template <class T>
    PhoenixSingleton<T>& PhoenixSingleton<T>::GetInstance()
    {
        if (NULL == pInstance_)
        {
            // Check if reference is dead
            if (isInstanceDestroyed_)
            {
                ReviveInstance();
            }
            else
            {
                CreateInstance();
            }
        }

        return *pInstance_;

    } // GetInstance()

    template <class T>
    PhoenixSingleton<T>::PhoenixSingleton()
    {
    } // PhoenixSingleton()


    template <class T>
    PhoenixSingleton<T>::~PhoenixSingleton()
    {
        pInstance_           = NULL;
        isInstanceDestroyed_ = true;

    } // ~PhoenixSingleton()


    template <class T>
    void PhoenixSingleton<T>::CreateInstance()
    {
        static PhoenixSingleton<T> objectInstance;
        pInstance_ = &objectInstance;

    } // CreateInstance()


    template <class T>
    void PhoenixSingleton<T>::DestroyInstance()
    {
        pInstance_->~PhoenixSingleton();

    } // DestroyInstance()


    template <class T>
    void PhoenixSingleton<T>::ReviveInstance()
    {
        CreateInstance();

        // Put new instance in the 'hull' of the old one
        new(pInstance_) PhoenixSingleton<T>;

        // Mark for destruction
        atexit(DestroyInstance);

        isInstanceDestroyed_ = false;

    } // ReviveInstance()


    template <class T>
    PhoenixSingleton<T>* PhoenixSingleton<T>::pInstance_ = NULL;


    template <class T>
    bool PhoenixSingleton<T>::isInstanceDestroyed_ = false;


    Wednesday, April 11, 2007 4:16 PM
  • How are you unloading the dll?

    The atexit callbacks will be executed on crt shutdown, and that should happen for each call to e.g. FreeLibrary. If your application calls LoadLibrary, FreeLibrary, then LoadLibrary and FreeLibrary a second time, I don't see how the atexit callback shouldn't be executed.

    If the problem persists, it would be great if you could shed some light on the lifecycle of your application.
    Wednesday, April 11, 2007 8:50 PM
    Moderator
  • I am not loading or unloading it manually at all. I just set it as a dependency in the project's properties.

    in comp.os.ms-windows.programmer.win32 I got an interesting opinion so far:

    Jan Ringoš wrote:
    > The standard is kinda weak when talking about atexit. I don't use VS2005
    > but you generaly can't be sure if you can or can not add more pointers
    > during program termination.
    Thursday, April 12, 2007 3:39 PM
  • In that case your DLL should be get a process detach notification on process shutdown which will trigger CRT cleanup including invoking atexit functions.

     

    You certainly shouldn't call atexit during cleanup, but I don't see where you do it. I only see one explicit atexit call and one local static with a nontrivial destructor (basically, the compiler generates a call to atexit with the dtor, which is executed on first entry to the function). That kind of race condition should only occur if you call GetInstance for the first time from within CRT cleanup.

     

    It is also worth mentioning that cleanup under the loader lock (which includes destruction of local static variables, global variables and static data members) has some problems. That a look at the DllMain documentation - specifically the DLL_PROCESS_DETACH.

     

    -hg

    Thursday, April 12, 2007 4:10 PM
  •  Holger Grund wrote:

    You certainly shouldn't call atexit during cleanup, but I don't see where you do it. I only see one explicit atexit call and one local static with a nontrivial destructor (basically, the compiler generates a call to atexit with the dtor, which is executed on first entry to the function). That kind of race condition should only occur if you call GetInstance for the first time from within CRT cleanup.



    That's exactly what happens.

    The singleton is wrapped around a logging class I implemented.

    And I call getInstance() in the destructor of another Singleton which is also static of course so it's destructor is called within CRT cleanup.
    I want to be able to log errors that happen within that destructor if any happen.

    This works without problems if all this is done within an application. That's the whole idea behind the PhoenixSingleton pattern anyway. The only time this doesn't happen is when I have the singleton inside a DLL and use that DLL in an application. Everything works find, at stack cleanup the destructor of my normal Singleton calls getInstance() of my PhoenixSingleton, it revives itself, but after that the destructor isn't called although it should have been registered by atexit().
    Thursday, April 12, 2007 8:17 PM