模板类遇到__declspec(导入)时Visual Studio链接器错误

时间:2017-07-16 22:21:56

标签: c++ visual-studio visual-c++ linker linker-errors

这是从一个看似很小的问题开始的,当我将一个小的异常处理库集成到一个由单个Visual Studio解决方案中的~200个Visual C ++项目组成的代码库中时,我遇到了这个问题。

我有一个像这样的消息表达的链接器问题<​​/ p>

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

乍一看,它看起来是另一个典型的问题,可以通过典型的建议“尝试更改#include文件的顺序”或“将实现移出头文件”来解决,但事实并非如此。 / p>

我已经探讨过许多类似的相关问题,例如thisthis one,但这些问题都不适合我的情况。至少,提议的食谱对我的问题没有帮助。

此外,很久以前我们公司的人员在迁移到VS2010期间遇到了与Visual Studio链接器相关的另一个问题,结果证明是链接器错误see here。无论如何,这个都不符合我的情景。

所以这个小小的问题最终得到了一个小小的研究。您可以找到重现问题的详细信息和玩具示例here at github。与此同时,我将尝试尽可能简短地描述下面的情况。

导致构建失败的关键因素是:

  1. 3个项目(让我们将它们命名为A,B,C),依赖图如下:B-> A,C-> B,C-> A。
  2. 在项目A中,我们定义了一个模板类。它在项目A的标题中得到实例化。
  3. 项目B中的DLL导出类,它继承自2)的模板类的相同实例化。
  4. 项目C,至少两个转换单位,其中至少有一个包括2)和3)。
  5. 在代码中,它看起来像这样:

    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
    }
    

    注意那些标有// !!!的行。评论任何将使构建成功。如上所述,可以获得完整的来源at github

    有人能解释这里出了什么问题吗? 基本上,我想了解:

    • 我在这里反对一些规则吗?
    • 或者是Visual Studio链接器问题吗?

    P.S。有一种解决方法可以帮助我推进我的工作。有关详细信息,请参阅github's README。但是,问题的根本原因对我来说仍然不清楚。

2 个答案:

答案 0 :(得分:1)

Visual Studio支持团队对这个问题进行了分类,结果他们提交了一个错误。正在进行调查,详情可见on Visual Studio forum。 一旦我得到他们的进一步反馈,我就会更新这个答案。

PS :这是否会被识别为链接器错误,我想说明从DLL中导出复杂的C ++接口并不是一个好的做法。一般。不幸的是,我无法在我正在使用的代码库中更改它。

就个人而言,对于基于DLL插件的架构,我建议使用一些编组层(如COM)或根据&#34; Hourglass原则&#34;明确设计接口。这是a great talk from CppCon 2014,它解释了那是什么。

答案 1 :(得分:1)

我在你的解决方案上挖了一下,并且逐渐相信它是一个编译器错误。它可以缩小到一个小得多的repro,只有2个二进制文件和零标准库依赖项 - 你可以检查它here

似乎引起混乱的关键代码就是这个 -

struct BaseWithVirtual
{
    virtual void what() {}       // !!! make this non-virtual --> Build succeeds
};

template<typename dummy>
struct ExceptionBase : public BaseWithVirtual
{
};

struct ExceptionChild : public  ExceptionBase<int>
{
};

inline void ThrowChild()
{
    ExceptionChild up;
    throw up;   // !!! comment this --> Build succeeds
}

这是Dropbox链接中的ThrowChild.h,原始的等价物分布在标准库中的多个文件中 - 主要是&lt;例外&GT;

如果他们解决此问题,请在此处更新。