C++ Singleton Across DLLs

Started by
9 comments, last by auron777 14 years, 8 months ago
Hi, I've implemented a simple singleton solution as shown below:

// Singleton.h
template< typename T >
class Singleton
{
public:
    Singleton();
    ~Singleton();

    static T & Instance( void )
    {
        return mSingleton;
    }
    static T * InstancePtr( void )
    {
        return &mSingleton;
    }

protected:
    static T mSingleton;

private:
    Singleton( Singleton const & ); // Hide copy ctor
    Singleton & operator = ( Singleton const & ); // Hide assignment operator
};

template< typename T >
T Singleton< T >::mSingleton = T();


Using this solution, I'm curious about the implications of using this across DLLs, for example:

class __declspec( dllexport ) 
MyClass : public Singleton< MyClass >
{
public:
    void Print( void )
    { printf( "Hello!" ); }
};


If I were to compile the above code into a single library DLL, would external usage of the MyClass singleton access the library version or create it's own copy of the static variable?

#include "MyClass.h"
...
MyClass::Instance().Print();


Thanks in advance. EDIT: Changed the Singleton class code a little. [Edited by - auron777 on August 11, 2009 7:17:26 AM]
Advertisement
Using the template across modules will probably create a separate singleton instance for each module.
Right. It's a bit complicated, but if you stick to this method you should be ok

First up:

Singleton.h
#ifdef BUILDING_DLL#define EXPORT __declspec(dllexport)#else#define EXPORT __declspec(dllimport)#endif// requires DLL export flags for win32!template< typename T >class EXPORT Singleton{public:    static T & Instance( void )    {        return mSingleton;    }    static T * InstancePtr( void )    {        return &mSingleton;    }protected:    static T mSingleton;private:    Singleton(); // Hide ctor.    ~Singleton(); // Hide dtor.    Singleton( Singleton const & ); // Hide copy ctor    Singleton & operator = ( Singleton const & ); // Hide assignment operator};template< typename T >T Singleton< T >::mSingleton = T();


But that doesn't export the template! It only tells the compiler that any instantiation of the template will be exported. (which may cause problems. More on that later). So. When you are building your dll, which requires the template to be exported. You need to be able to export the related template specialization from your DLL. In this case Singleton<MyClass>

So. Since your template is likely to exist in some CommonGuff.lib you may need a bit of #define hackery to get the correct export/import used. The following assumes that BUILDING_MY_DLL is only defined when you are compiling the DLL that uses the template.

MyClass.h
// a hack that tells singleton.h to use DLL_EXPORT when compiling your dll.#ifdef BUILDING_MY_DLL#define BUILDING_LIB #endif#include "Singleton.h"class __declspec( dllexport ) MyClass : public Singleton< MyClass >{public:    void Print( void )    { printf( "Hello!" ); }};


almost there. The final part is exporting the specialisation of Singleton<MyClass> from the dll. Which you can do like so:

MyClass.cpp
#include "MyClass.h"// You must also export the template instantiation Singleton< MyClass > into // the DLL you are compiling. That should be it!template __declspec( dllexport ) class Singleton<MyClass>; 


This should now remove all of the warning C4251 messages when compiling in visual C++. (Which is actually the one warning that will tell you if you've done this properly or not - it might be a warning level 4 warning though - can't remember).

Anyhow. Now onto the problems.

* It's easy to forget to export the template specliasation, and that should be the first thing to check if you ever see any linker errors surrounding the Singleton class (i.e. can't find Singleton<MyClass>::blah())
* You can hit some very nasty problems if you are exporting a Singleton specialisation from two separate DLL's. Say you have Singleton<Renderer> and Singleton<AssetManager>. You may have problems compiling Singleton<Renderer> in cases like this:

Renderer.cpp
// This will include Singleton.h, which will mark the Singleton<> template// as dllimport.#include "AssetManager.h"// This will also include Singleton.h, but this time you want Singleton<> // to be declared dllexport (since we are exporting Singleton<renderer>)// This however will not happen, since Singleton.h has previously been included// and Singleton<> has already been defined as dllimport (since it was included// by AssetManager.h). #include "Renderer.h"


There are ways around the problem, but they really do end up being god awful #define quagmires, where you end up removing sentry, start defining things in different namespaces and all manner of other hateful bits of code.

Sadly. After you go to all the effort to use templates in that way (with DLL's), you realise you would have been better off writing the methods as macros instead. So.... write your singleton as a macro is my advice ;)

///  !!NOTE!! These forward slashes should actually be backslashes, but /// unfortunately gamedev's source tags remove them!#define SINGLETON_DEFINITION(T) /public:/     static T & Instance( void )/     {/         return mSingleton;/     }/     static T * InstancePtr( void )/     {/         return &mSingleton;/     }/protected:/     static T mSingleton;/private:/     T(){}/     ~T(){}/     T( T const & ) {}/     T& operator = ( T const & ) {return *this;}#define SINGLETON_IMPLEMENTATION(T)/   T T ::mSingleton = T();


then....

#include "Singleton.h"class __declspec( dllexport ) MyClass {public:    void Print( void )    { printf( "Hello!" ); }  SINGLETON_DEFINITION(MyClass)};


// myclass.cpp
#include "MyClass.h"SINGLETON_IMPLEMENTATION(MyClass)


(I've not checked the above macros, there may be errors!)

\edit I will mention that i personally think singleton's are abominations that should never be used. Just use a global instance. It's far easier to deal with just a simple:

DLL_EXPORT MyClass gMyClassInstance;
Sorry SiCrane. I think I didn't make myself clear enough.

My question is that if I used MyClass, which is already compiled with the Singleton<MyClass> base class, would it use the library instance?
Wow thanks, RobTheBloke!
That was a great help. :)
Quote:Original post by auron777
would it use the library instance?


If, and only if, you do all of the steps I outlined in my previous post.

Okay another question...
Let's say I have this Singleton class now:

template< typename T >class Singleton{public:    inline static T & Instance( void )    {        static T _instance;        return _instance;    }private:    /// Hide constructor.    Singleton();    /// Hide destructor.    ~Singleton();};#define DECLARE_API_SINGLETON( type )    template EXPORT_API ::Chewable::Singleton< type >


Note: EXPORT_API is the automagical macro that does the importing/exporting depending on if its library or client build.

This time, the usage is slightly different:

#include "Singleton.h"class MyClass{public:    typedef Singleton< MyClass >    Singleton;public:    void Hello( void ) { printf( "Hello" ); }};DECLARE_API_SINGLETON( MyClass );int main(){    MyClass::Singleton::Instance().Hello();    return 0;}


Is this a viable way to implement a Singleton as well?
Do you need a singleton declared in C++'s static context?

Can't you just define regular classes, and allocate them in some init() or something similar when library is loaded?
Quote:Original post by auron777
Is this a viable way to implement a Singleton as well?


It will just give warning C4251 since Singleton<> is no longer flagged as being dll exported, and MyClass is no longer dll exported. You must export MyClass, the singleton template, and in addition the template specialization. You are only doing the latter. You must do all three before it is safe to use any template across a DLL boundary.

Now then. Onto the next problem. Your revised solution is far far worse than the original. The first problem:
MyClass a;MyClass b;MyClass c;


Refactoring that to make the MyClass ctor private will only prevent the singleton from doing anything at all. Basically, your revised solution is extremely dangerous (and will cause crashes and/or extremely hard to debug errors because you have not exported the templates correctly), has an extremely bad design, and is functionally broken. I'd throw it away completely.

!!THIS IS IMPORTANT!!

Mixing templates and DLL's should be avoided at all costs! It will lead to exceptionally difficult to debug memory leaks and other fun problems; horrendous linker errors; compile errors due to dubious macros and various defines; redefining classes into multiple namespaces (to avoid linker and compiler clashes).

You are walking towards a coding nightmare. Stop. About face. And use macros!


My previous posts are a very quick guide as to how to make it work properly - but it does not cover a lot of the underlying problems, or theory. If you deviate from those instructions at all (note: you've omitted 2 extremely important steps!!!) you have failed to export the templates correctly. You must not underestimate this particular subject - it will come back to bite you very hard, very very soon if you get it wrong!

In your case, I'd strongly recommend the following options:

a) Do not use templates. Use macros.
b) Don't use templates. Don't use macros. Don't use singletons. Just export a global instance from the DLL.

If I was your lead programmer, I'd kick you to hell and back for doing anything other than option b! ;) (and by the sounds of it, Antheus would do the same!).
While I agree the singletons tend to be evil, the question I always ask in these cases is "Do you need to have DLLs?"

DLLs were considered a panacea when they were invented, with their potential for object reuse and memory saving and simplified bug fixing. They seem that way until something updates a DLL and, despite assurances of backwards compatibility, something else breaks. They add to load time more often than they help it, and as you've noticed, they have interesting (read: tricky) memory semantics.

I don't write them anymore and I've never looked back. See if you can live without them; you might just end up happier.

This topic is closed to new replies.

Advertisement