Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DLL-Exporting static members of template base class

Within a DLL I have an exported non-template class with a template base class. This template base class has a static member variable. I use the static base member in an executable that links to the DLL with the exported non-template class.

In many scenarios I get unresolved external symbols or complaints about inconsistent linkage. I have found one scenario that works, but it seems to be kludgey so I'm wondering if there is a better way and if that better way might also point to deficiencies in VS2010 SP1's C++ compiler/linker.

This is the minimal scenario of the DLL that I could distill - I don't think I could remove anything here without breaking the scenario.

// Header file
template<typename T>
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };

class _MYDLL_EXPORTS MyClass : public TBaseClass<MyClass>
  {    
  };

// Kludge: use this code only when building the DLL, not when including
// from the DLL's client
#ifdef _MYDLL
  template<typename T>
  const double TBaseClass<T>::g_initial_value = 1e-5;
#endif


// CPP file
#include "header.h"
// Explicit instantiation of the template for the correct parameter.
template class TBaseClass<MyClass>;

Then the user of the DLL

#include <header.h>  
#include <iostream>
int main(void) {
 MyClass c;
 std::cout << c.g_initial_value;
return 0;
}
like image 299
Joris Timmermans Avatar asked Oct 21 '11 11:10

Joris Timmermans


3 Answers

In C++ generally, when a plain class has a static member, it should be declared in the header, but instantiated in a source file. To do otherwise would cause too many instances of the static class member to be created.

Templates are kind of the same way, except the compiler has some magic for templates that it doesn't have for non-templates. Specifically, it magically eliminates duplicate instances of a template instantiation during the linking phase of a build.

This is the source of your problem: The stuff inside the _MYDLL portion is automatically instantiated by every source file that includes this template and also makes TBaseClass objects. Then the linker automatically eliminates duplicates.

Trouble is, you have two links: the DLL link and the client link. Both will make TBaseClass instantiations, and both will make those g_initial_value objects.

To solve this: Move the stuff in the _MYDLL conditional into the CPP file, so the client won't get instructions to build the instance itself.

like image 164
Alan Baljeu Avatar answered Oct 18 '22 05:10

Alan Baljeu


In fact, the exported class's base class is exported too if the base class is a template class, but not true on the contrary. Please refer to http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/

For your specific question, I suggest you define a static method in the base template which returns a variable(pointer?) of interest. Then only one definition will happen across multiple dlls or exe which depends on your library.

like image 41
langdead Avatar answered Oct 18 '22 07:10

langdead


The fact that you are using your template class from both the DLL and the EXE make things more confusing, but still, it can work.

First of all, you should implement your template base class entirely in the header file. If you don't know why, then make sure you read the accepted answer to this question.

Now let's forget about templates and DLLs, and consider a much simpler case. Let's say you have class C, with a static member. You would normally code this class in this way:

// C.h file
class C {
public:
    static const double g_initial_value;
};

// C.cpp file
const double C::g_initial_value = 1e-5;

Nothing odd or complicated here. Now consider what would happen if you move the static declaration to the header file. If there is only one source file that includes the header, then everything will work just fine. But if two or more source files included this header, then this static member will be defined multiple times, and the linker will not like that.

The same concept applies to a template class. Your #ifdef _MYDLL hack only works because from the DLL you are including this header file only once. But the moment you include this file from another source file you'll start getting linker errors on the DLL! So I completely agree with you, this is not a good solution.

I think one thing that complicates your setup is that you allow both the DLL and the EXE to instantiate this template base class. I think you would have a much cleaner solution if you find an "owner" for each instantiation of the template class. Following your code example, let's replace MyClass with MyDLLClass and MyEXEClass. Then you could make this work like this:

// dll.h
template<typename T>
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };

class _MYDLL_EXPORTS MyDLLClass : public TBaseClass<MyDLLClass>
  {    
  };

// dll.cpp
#include "dll.h"

// this file "owns" MyDLLClass so the static is defined here
template<> const double TBaseClass<MyDLLClass>::g_initial_value = 1e-5;

// exe.h
#include "dll.h"

class MyEXEClass : public TBaseClass<MyEXEClass>
  {    
  };

// exe.cpp
#include "exe.h"
#include <iostream>

// this file "owns" MyEXEClass so the static is defined here
template<> const double TBaseClass<MyEXEClass>::g_initial_value = 1e-5;

int main(int argc, char* argv[])
{
    MyDLLClass dll;
    MyEXEClass exe;

    std::cout << dll.g_initial_value;
    std::cout << exe.g_initial_value;
}

I hope this makes sense.

like image 25
Miguel Avatar answered Oct 18 '22 07:10

Miguel