DLL-导出模板基类的静态成员

发布于 2024-12-11 08:02:03 字数 1028 浏览 0 评论 0原文

在 DLL 中,我有一个带有模板基类的导出非模板类。该模板基类有一个静态成员变量。我在链接到具有导出的非模板类的 DLL 的可执行文件中使用静态基成员。

在许多情况下,我会收到未解决的外部符号或有关不一致链接的投诉。我发现了一种可行的方案,但它似乎很混乱,所以我想知道是否有更好的方法,以及该更好的方法是否也可能指出 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;
}

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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

甜扑 2024-12-18 08:02:03

一般来说,在 C++ 中,当普通类具有静态成员时,应在标头中声明它,但在源文件中实例化。否则会导致创建太多静态类成员的实例。

模板的方式与此类似,只是编译器对模板具有一些非模板所没有的魔力。具体来说,它在构建的链接阶段神奇地消除了模板实例化的重复实例。

这是问题的根源: _MYDLL 部分内的内容由包含此模板的每个源文件自动实例化,并且还生成 TBaseClass 对象。然后链接器会自动消除重复项。

问题是,您有两个链接:DLL 链接和客户端链接。两者都会进行 TBaseClass 实例化,并且都会进行 g_initial_value 对象。

要解决此问题:将 _MYDLL 条件中的内容移至 CPP 文件中,这样客户端就不会收到构建实例本身的指令。

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.

水中月 2024-12-18 08:02:03

事实上,您同时使用 DLL 和 EXE 中的模板类,这让事情变得更加混乱,但它仍然可以工作。

首先,您应该完全在头文件中实现模板基类。如果您不知道原因,请务必阅读 这个问题

现在让我们忘记模板和 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 hack 只能起作用,因为从 DLL 中您只包含此头文件一次。但是当您从另一个源文件包含此文件时,您将开始在 DLL 上收到链接器错误!所以我完全同意你的观点,这不是一个好的解决方案。

我认为使您的设置变得复杂的一件事是您允许 DLL 和 EXE 实例化此模板基类。我认为如果您为模板类的每个实例化找到一个“所有者”,您将获得一个更清晰的解决方案。按照您的代码示例,让我们将 MyClass 替换为 MyDLLClass 和 MyEXEClass。然后你可以像这样工作:

// 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;
}

我希望这是有道理的。

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.

思念满溢 2024-12-18 08:02:03

事实上,如果基类是模板类,则导出类的基类也会被导出,但反之则不然。请参考http://www. codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/

对于您的具体问题,我建议您在基本模板中定义一个静态方法,该方法返回感兴趣的变量(指针?)。然后,多个 dll 或 exe 中只会发生一个定义,具体取决于您的库。

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.

野味少女 2024-12-18 08:02:03

虽然我建议使用您当前的方法,但实际上可以通过使用旧语法从 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 的地址空间内,因此它似乎可以正常工作。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文