在DLL中,我有一个带有模板基类的导出的非模板类。此模板基类具有静态成员变量。我在可执行文件中使用静态基本成员,该可执行文件使用导出的非模板类链接到DLL。
在许多情况下,我得到了未解决的外部符号或有关不一致链接的抱怨。我找到了一个可行的方案,但它似乎是kludgey所以我想知道是否有更好的方法,如果更好的方式也可能指向VS2010 SP1的C ++编译器/链接器中的缺陷。
这是我可以提炼的DLL的最小场景 - 我不认为我可以在不破坏场景的情况下删除任何内容。
// 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>;
然后是DLL的用户
#include <header.h>
#include <iostream>
int main(void) {
MyClass c;
std::cout << c.g_initial_value;
return 0;
}
答案 0 :(得分:4)
在C ++中,普通类当有一个静态成员时,它应该在头文件中声明,但在源文件中实例化。否则会导致创建太多静态类成员实例。
模板有一些相同的方式,除了编译器对于非模板没有的模板有一些魔力。具体来说,它在构建的链接阶段神奇地消除了模板实例化的重复实例。
这是您的问题的根源:_MYDLL部分中的内容由包含此模板的每个源文件自动实例化,并且还生成TBaseClass对象。然后链接器会自动消除重复项。
麻烦的是,你有两个链接:DLL链接和客户端链接。两者都将进行TBaseClass实例化,并且两者都将生成那些g_initial_value对象。
要解决此问题:将_MYDLL条件中的内容移动到CPP文件中,这样客户端就不会获得构建实例本身的指令。
答案 1 :(得分:2)
您正在使用DLL和EXE中的模板类这一事实使事情变得更加混乱,但仍然可以使用。
首先,您应该完全在头文件中实现模板基类。如果您不知道原因,请务必阅读this question的接受答案。
现在让我们忘记模板和DLL,并考虑一个更简单的情况。假设您有C类,带有静态成员。您通常会以这种方式编写此类:
// C.h file
class C {
public:
static const double g_initial_value;
};
// C.cpp file
const double C::g_initial_value = 1e-5;
这里没什么奇怪或复杂的。现在考虑如果将静态声明移动到头文件会发生什么。如果只有一个包含标题的源文件,那么一切都会正常工作。但是如果两个或多个源文件包含此头文件,那么这个静态成员将被定义多次,并且链接器将不会那样。
同样的概念适用于模板类。您的#ifdef _MYDLL
黑客只能运行,因为从DLL中只包含此头文件一次。但是,当您从另一个源文件中包含此文件时,您将开始在DLL上获取链接器错误!所以我完全赞同你,这不是一个好的解决方案。
我认为使您的设置复杂化的一件事是您允许DLL和EXE实例化此模板基类。如果你为模板类的每个实例化找到一个“所有者”,我想你会有一个更清洁的解决方案。按照你的代码示例,让我们用MyDLLClass和MyEXEClass替换MyClass。那么你可以这样做:
// 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;
}
我希望这是有道理的。
答案 2 :(得分:1)
实际上,如果基类是模板类,导出的类的基类也会被导出,但事实并非如此。请参阅http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/
对于您的具体问题,我建议您在基本模板中定义一个静态方法,该方法返回一个感兴趣的变量(指针?)。那么只有一个定义会在多个dll或exe上发生,这取决于你的库。
答案 3 :(得分:0)
虽然我建议使用您当前的方法,但实际上可以通过使用较旧的语法从DLL导出模板来避免#ifdef。所有这些都转到了DLL的头文件:
#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>
{
};
在运行时,客户端代码中g_initial_value的地址位于DLL的地址空间内,因此它似乎正常工作。