11

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;
}
Joris Timmermans
  • 10,814
  • 2
  • 49
  • 75
  • 4
    You can write DLLs in C++, but the exported interface should always be C-compatible. Anything else is asking for trouble. Your design is fragile, and may break at the slightest discrepancy in compiler options between the library and client. – Ben Voigt Oct 24 '11 at 21:10
  • 1
    @Ben Voigt - while I agree with your point when concerning general-purpose libraries, in this case both the library and the client are under my full control, and they are guaranteed to be built with the same compiler and settings through various mechanisms. The maco _MYDLL_EXPORTS expands to _declspec(dllexport) or _declspec(dllimport) depending on the location of the include - a very common pattern with larger native C++ win32 applications in my experience. – Joris Timmermans Oct 25 '11 at 07:13
  • Your solution is already correct. Linker automatically eliminates duplicates in case of templates. Also VC++ linker should probably prefer dllimport version of TBaseClass::g_initial_value, but it does not. By using #ifdef _MYDLL you help it. You could file a bug to MS Connect but they tend to close such issues as "won't fix". – Vyacheslav Lanovets Oct 30 '11 at 07:23

4 Answers4

7

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.

Alan Baljeu
  • 2,383
  • 4
  • 25
  • 40
  • I cannot move the definition of the static template member into the CPP file, because the base template class is *also* used by the client for certain purposes (for instances/types that were not created in the DLL). Or can I? Would it be less kludgey to reimplement the specialized static on the client side? – Joris Timmermans Oct 25 '11 at 07:15
  • 1
    And have to repeat it once for each specialization? I like your original approach better. What you could do is use an additional conditional to ensure that the static member definition is only visible to source files where you perform explicit instantiations. – Nicola Musatti Oct 25 '11 at 09:24
  • @NicolaMusatti - I'm intruigued by that suggestion and will play with some ideas related to that. – Joris Timmermans Oct 25 '11 at 14:39
  • C++ is a nuissance because it separates declaration from implementation. C++ templates bypass that nuissance and allow everything in headers (in exchange for slow and fragile recompiling), but static member variables can't take advantage. – Alan Baljeu Oct 25 '11 at 19:51
  • Re: two instantiations of TBaseClass - I agree that's what happens, but I believe that it should not because, as you also say in your answer, the compiler should eliminate duplicate instances of the template, including the static, yet it does not (but only for the static). I'm starting to think that templates and DLLs don't mix anyway, for several other reasons too. – Joris Timmermans Oct 26 '11 at 07:22
  • I agree. The standard approach is that a template library is just headers. There may be associated non-template classes in a DLL, but not template classes in the DLL. I'd look at Microsoft's ATL library for ideas. – Alan Baljeu Oct 27 '11 at 13:17
  • @AlanBaljeu - I'm currently looking into various strategies of exporting only explicitly instantiated templates. This is mostly because of another issue (compiler detecting implicit instantiation of part of a template inside a DLL, with linker errors on the parts that were not implicitly instantiated). – Joris Timmermans Oct 27 '11 at 14:15
4

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.

Community
  • 1
  • 1
Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
  • You explain part the root of the problem more clearly than I did, for that I'd give you a +1. However, my kludgy hack will not fail inside the DLL! It looks like inside the DLL the compiler does manage to correctly detect that the base template has been instantiated and does not introduce a second instance of the static initialization of TBaseClass. – Joris Timmermans Oct 27 '11 at 07:13
  • 2
    Interesting. You are correct, the Microsoft C++ compiler (VS2010) does indeed allow you to define the static symbol multiple times (even with different values assigned to each!) yet the linker only sees one and accepts this silently. However, I also tested GCC, which definitely does not like that and behaves like I described above. I strongly recommend that you don't try to find a compiler specific solution, I consider this a VS bug. – Miguel Grinberg Oct 27 '11 at 15:52
1

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.

langdead
  • 11
  • 1
  • Interesting - looks like there are still many dark corners with templates and DLLs. In the end we resolved the issue by removing the const static members from the templates to elsewhere - and avoid statics as much as possible. – Joris Timmermans Dec 09 '11 at 08:13
0

While I would suggest using your current approach, actually it's possible to avoid #ifdef by using older syntax for exporting templates from a DLL. All this goes to the DLL's header file:

#pragma once

#ifdef _MYDLL
#define _MYDLL_EXPORTS __declspec(dllexport)
#else
#define _MYDLL_EXPORTS __declspec(dllimport)
#endif

template<typename T> 
class _MYDLL_EXPORTS TBaseClass // _MYDLL_EXPORTS is not needed here
{ 
public: 
    static double g_initial_value; 
}; 

template<typename T> 
double TBaseClass<T>::g_initial_value = 1e-5; 

class MyClass;

template class _MYDLL_EXPORTS TBaseClass<MyClass>;

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

At runtime the address of g_initial_value in the client code lies within DLL's address space so it seems to work correctly.

  • This solution does not work either - as soon as you try to instantiate a TBaseClass outside of the DLL that was not already explicitly (or implicitly) instantiated inside the DLL you get error C2491 - Definition of dllimport static data member not allowed. – Joris Timmermans Oct 31 '11 at 08:16