从dll导出包含std :: objects(vector,map等)的类

时间:2009-04-20 09:38:27

标签: c++ visual-studio dll

我正在尝试从包含std :: vectors和std :: strings等对象的DLL中导出类 - 整个类通过以下方式声明为dll导出:

    class DLL_EXPORT FontManager
{

问题是,对于复杂类型的成员,我收到此警告:

  

警告C4251:'FontManager :: m__fonts':class'std :: map< _Kty,_Ty>'需要让'FontManager'类的客户端使用dll接口           同           [               _Kty =的std :: string,               _Ty = tFontInfoRef           ]

我可以通过在它们之前放置以下前向类声明来删除一些警告,即使我没有更改成员变量本身的类型:

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;

看起来前向声明“注入”了成员编译时的DLL_EXPORT但它是否安全?当客户端编译此标头并使用他身边的std容器时,它是否真的会改变任何东西?它是否会在将来使用这样的容器DLL_EXPORT(并且可能不是内联的?)?它是否真的解决了警告试图警告的问题?

这个警告是我应该担心的,还是最好在这些结构的范围内禁用它?客户端和DLL将始终使用相同的库和编译器集构建,并且这些只是标题类...

我正在使用带有标准STD库的Visual Studio 2003。

----更新----

我想更多地针对你,因为我看到答案是一般性的,这里我们讨论的是std容器和类型(例如std :: string) - 也许问题确实是:

我们是否可以通过相同的库标题禁用客户端和dll可用的标准容器和类型的警告,并将它们视为我们处理int或任何其他内置类型? (它确实似乎在我身边正常工作。)如果可以,我们可以做到这一点的条件是什么?

或者应该禁止使用这样的容器,或者至少要特别小心,以确保不会将任务操作员,复制构造函数等内联到dll客户端中?

一般来说,我想知道你是否觉得设计一个具有这些对象的dll接口(例如使用它们将东西作为返回值类型返回给客户端)是一个好主意或不是为什么 - 我是喜欢拥有这个功能的“高级”界面...也许最好的解决方案是Neil Butterworth建议的 - 创建一个静态库?

12 个答案:

答案 0 :(得分:52)

当您从客户端触摸类中的成员时,您需要提供DLL接口。 DLL接口意味着编译器在DLL本身中创建函数并使其可导入。

因为编译器不知道DLL_EXPORTED类的客户端使用哪些方法,所以必须强制所有方法都是dll导出的。 它必须强制要求客户端可以访问的所有成员也必须dll导出它们的功能。当编译器警告您未导出的方法以及客户端的链接器发送错误时,会发生这种情况。

并非每个成员都必须使用dll-export进行标记,例如私人会员不能被客户触摸。在这里你可以忽略/禁用警告(注意编译器生成的dtor / ctors)。

否则成员必须导出他们的方法。 使用DLL_EXPORT声明它们不会导出这些类的方法。您必须将编译单元中的相应类标记为DLL_EXPORT。

它归结为......(对于不是可导出dll的成员)

  1. 如果您的会员不是/不能被客户使用,请关闭警告。

  2. 如果您有必须由客户端使用的成员,请创建dll-export包装器或创建间接方法。

  3. 要减少外部可见成员的数量,请使用PIMPL idiom等方法。


  4. template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
    

    这确实在当前编译单元中创建了模板特化的实例化。因此,这将在dll中创建std :: allocator的方法并导出相应的方法。这不适用于具体类,因为这只是模板类的实例化。

答案 1 :(得分:17)

该警告告诉您DLL的用户无法跨DLL边界访问容器成员变量。明确地导出它们使它们可用,但这是一个好主意吗?

通常,我会避免从DLL导出std容器。如果您绝对可以保证您的DLL将与相同的运行时和编译器版本一起使用,那么您将是安全的。您必须确保使用相同的内存管理器取消分配DLL中分配的内存。否则,最好在运行时断言。

因此,不要直接在DLL边界上公开容器。如果需要公开容器元素,请通过访问器方法执行此操作。在您提供的情况下,将接口与实现分开,并在DLL级别公开接口。您对std容器的使用是DLL的客户端不应该访问的实现细节。

或者,做Neil建议并创建一个静态库而不是DLL。您无法在运行时加载库,并且您的库的使用者必须在您更改库时随时重新链接。如果这些是您可以接受的妥协,静态库至少会让您解决这个问题。我仍然会说你不必要地暴露实现细节,但它可能对你的特定库有意义。

答案 2 :(得分:7)

还有其他问题。

某些STL容器可以“安全”导出(例如矢量),有些则不是(例如地图)。

例如Map是不安全的,因为它(无论如何在MS STL分发中)包含一个名为_Nil的静态成员,其值在迭代中进行比较以测试结束。使用STL编译的每个模块都有一个不同的_Nil值,因此在一个模块中创建的映射将无法从另一个模块迭代(它永远不会检测到结束并爆炸)。

即使您静态链接到lib,这也适用,因为您无法保证_Nil的值是什么(它是未初始化的)。

我相信STLPort不会这样做。

答案 3 :(得分:6)

我发现处理此方案的最佳方法是:

创建你的库,用库名中包含的编译器和stl版本命名它,就像boost库一样。

的示例:

- 用于dll版本的FontManager-msvc10-mt.dll ,特定于MSVC10编译器,默认为stl。

- 用于dll版本的FontManager-msvc10_stlport-mt.dll ,特定于MSVC10编译器,带有stl端口。

用于dll版本的

- FontManager-msvc9-mt.dll ,特定于MSVC 2008编译器,具有默认的stl

- libFontManager-msvc10-mt.lib 用于静态lib版本,特定于MSVC10编译器,具有默认stl。

遵循此模式,您将避免与不同stl实现相关的问题。请记住,vc2008中的stl实现与vc2010中的stl实现不同。

使用boost :: config库查看您的示例:

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#  pragma warning( push )
#  pragma warning( disable: 4251 )
#endif

class DLL_EXPORT FontManager
{
public:
   std::map<int, std::string> int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif

答案 4 :(得分:5)

很少有人会考虑考虑的另一种方法是根本不使用DLL,而是静态链接静态.LIB库。如果这样做,导出/导入的所有问题都会消失(尽管如果使用不同的编译器,仍会出现名称错误问题)。您当然会失去DLL体系结构的功能,例如函数的运行时加载,但在许多情况下这可能是一个很小的代价。

答案 5 :(得分:4)

找到this article。简而言之,亚伦上面有“真正的”答案;不要在库边界上公开标准容器。

答案 6 :(得分:2)

虽然这个帖子已经很老了,但我最近发现了一个问题,这让我再次想到在导出的类中使用模板:

我写了一个类,它有一个类型为std :: map的私有成员。一切都运行得很好,直到它在发布模式下编译,即使在构建系统中使用,也确保所有目标的所有编译器设置都相同。地图完全隐藏,没有任何东西直接暴露给客户。

因此,代码只是在发布模式下崩溃。我猜,因为为实现和客户端代码创建了不同的二进制std :: map实例。

我想C ++标准并没有说明如何处理导出的类,因为这几乎是编译器特定的。所以我想最大的可移植性规则是公开接口并尽可能多地使用PIMPL习惯。

感谢任何启蒙

答案 7 :(得分:2)

  

从dll

导出包含std :: objects(vector,map等)的类

另请参阅Microsoft的KB 168958文章How to export an instantiation of a Standard Template Library (STL) class and a class that contains a data member that is an STL object。来自文章:

  

导出STL类

     
      
  1. 在DLL和.exe文件中,链接与C运行时的相同DLL版本。要么链接Msvcrt.lib(发布版本),要么   链接Msvcrtd.lib(调试版本)。
  2.   
  3. 在DLL中,在模板实例化声明中提供__declspec说明符以从中导出STL类实例化   DLL。
  4.   
  5. 在.exe文件中,在模板实例化声明中提供extern和__declspec说明符,以从中导入类。   DLL。这导致警告C4231“使用非标准扩展:   模板显式实例化之前的'extern'。“你可以忽略它   警告。
  6.   

  

导出包含作为STL对象的数据成员的类

     
      
  1. 在DLL和.exe文件中,链接与C运行时的相同DLL版本。要么链接Msvcrt.lib(发布版本),要么   链接Msvcrtd.lib(调试版本)。
  2.   
  3. 在DLL中,在模板实例化声明中提供__declspec说明符以从中导出STL类实例化   DLL。

    注意:您不能跳过步骤2.您必须导出   用于创建数据成员的STL类的实例化。
  4.   
  5. 在DLL中,在类的声明中提供__declspec说明符,以便从DLL中导出类。
  6.   
  7. 在.exe文件中,在类的声明中提供__declspec说明符以从DLL导入类。如果   您导出的类有一个或多个基类,那么您必须   也导出基类。

    如果您要出口的课程   包含类类型的数据成员,然后您必须导出   数据成员的类别。
  8.   

答案 8 :(得分:1)

在这种情况下,请考虑使用pimpl习语。隐藏单个void *后面的所有复杂类型。编译器通常没有注意到您的成员是私有的,并且DLL中包含所有方法。

答案 9 :(得分:0)

如果使用DLL在事件“DLL PROCESS ATTACH”中初始化所有对象,并将指针导出到其类/对象。
您可以提供特定的函数来创建和销毁对象和函数以获取创建的对象的指针,因此您可以将这些调用封装在包含文件的访问包装类中。

答案 10 :(得分:0)

由于模板类中的静态数据成员(如stl容器)

,因此MSVC无法接受上述变通方法

每个模块(dll / exe)都有自己的每个静态定义的副本......哇!如果你以某种方式“输出”这样的数据(如上面的'尖头'),这将导致可怕的事情......所以不要在家里尝试这个

请参阅http://support.microsoft.com/kb/172396/en-us

答案 11 :(得分:0)

在这种情况下使用的最佳方法是使用PIMPL设计模式。