Visual Studio linker error when template class meets __declspec(import)

Viewed 718

This has started with a seemingly minor problem I've encountered when I was integrating my small exception handling library into a code base consisting of ~200 Visual C++ projects in a single Visual Studio solution.

I've got a linker problem expressed by a message like this

3>B_Utils.lib(B_Utils.dll) : error LNK2005: "public: __cdecl ExceptionBase<class std::runtime_error>::ExceptionBase<class std::runtime_error>(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (??0?$ExceptionBase@Vruntime_error@std@@@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) already defined in TranslationUnit_2.obj
3>B_Utils.lib(B_Utils.dll) : error LNK2005: "public: virtual __cdecl ExceptionBase<class std::runtime_error>::~ExceptionBase<class std::runtime_error>(void)" (??1?$ExceptionBase@Vruntime_error@std@@@@UEAA@XZ) already defined in TranslationUnit_2.obj
3>B_Utils.lib(B_Utils.dll) : error LNK2005: "public: __cdecl ExceptionBase<class std::runtime_error>::ExceptionBase<class std::runtime_error>(class ExceptionBase<class std::runtime_error> const &)" (??0?$ExceptionBase@Vruntime_error@std@@@@QEAA@AEBV0@@Z) already defined in TranslationUnit_2.obj

At first glance, it looked as yet another typical problem that can be solved with a typical advice "try changing the order of #include files" or "move implementation out of the header file", but it wasn't.

I've explored a number of similarly related questions like this or this one, but none of them fits my case. At least, the proposed recipes don't help with my issue.

Furthermore, a long ago people in our company have encountered another problem related to Visual Studio linker during migration to VS2010, which turned out to be a linker bug, see here. Anyway, neither that one matched my scenario.

So this tiny issue ended up with a whole mini-research. You may find the details and the toy example that reproduces the problem here at github. In the meantime, I'll try to describe the situation below as briefly as possible.


The key ingredients that lead to the build failure are:

  1. 3 projects (let's name them A,B,C) with dependency graph like this: B->A, C->B, C->A.
  2. In project A we define a template class. It gets intantiated within the header of that project A.
  3. DLL-exported class in project B which inherits from the same intantiation of template class from 2).
  4. Project C with at least two tranlation units, at least one of them includes both 2) and 3).

In the code, it looks like this:

A_SDK:

(ExceptionBase.h)
template<typename T>
class ExceptionBase;
--
(foo.h)
#include "ExceptionBase.h"
inline void foo() // same effect would be with template<typename T> void foo()
{
    throw ExceptionBase<std::runtime_error>("Problem");
}

B_Utils:

(Error.h)
#include "ExceptionBase.h"
#if defined(B_EXPORTS)
#define _B_UTILS_EXPORTS_CLASS      __declspec(dllexport)
#else
#define _B_UTILS_EXPORTS_CLASS      __declspec(dllimport)
#endif

struct _B_UTILS_EXPORTS_CLASS Error : public ExceptionBase<std::runtime_error>
{ Error(std::string&& s); } // ctor definition is in *.cpp file

C_Client:

(TranslationUnit_1.cpp)
#include "A_SDK/foo.h"
#include "B_Utils/Error.h"      // !!! Comment this line --> Build succeeds
void TranslationUnit_1() { 
  foo();                        // !!! Comment this line --> Build succeeds
}
(TranslationUnit_2.cpp)
#include "A_SDK/foo.h"
void TranslationUnit_2() {
  foo();                        // !!! Comment this line --> Build succeeds
}

Pay attention to those lines marked with // !!!. Commenting any of them will make the build successful. As mentioned above, full sources are available at github.

Could anyone explain what is going wrong here? Essentially, I'd like to understand:

  • Am I going against some rule here?
  • or is it Visual Studio linker issue?

P.S. There is a workaround that helped me to advance with my work. See github's README for details. However, the root cause of the problem remains unclear to me.

2 Answers
Related