当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《从dll导出包含std::objects(vector,map等)的类》经验,为你挑选了5个好方法。

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

    class DLL_EXPORT FontManager
{

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

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

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

template class DLL_EXPORT std::allocator;
template class DLL_EXPORT std::vector >;
std::vector m_glyphProviders;

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

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

我正在使用Visual Studio 2003和标准STD库.

----更新----

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

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

或者应该禁止使用这样的容器,或者至少要特别小心,以确保没有赋值操作符,复制构造函数等内联到dll客户端?

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



1> Christopher..:

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

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

并非每个成员都必须使用dll-export标记,例如客户无法触及的私人成员.在这里你可以忽略/禁用警告(注意编译器生成的dtor/ctors).

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

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

    如果您的成员未被客户端使用/无法使用,请关闭警告.

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

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


template class DLL_EXPORT std::allocator;

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



2> Aaron Saarel..:

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

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

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

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



3> 小智..:

还有其他问题.

一些STL容器对于导出是"安全的"(例如矢量),而一些不是(例如map).

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

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

我相信STLPort不会这样做.



4> 小智..:

我发现处理这种情况的最佳方法是:

创建你的库,用库名中包含的编译器和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 

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

class DLL_EXPORT FontManager
{
public:
   std::map int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif



5> 小智..:

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


如果你的DLL接口使用C++类,你真的不能混合编译器 - 特别是如果你使用的是标准库类型.
推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有