从dll导出包含std :: objects(vector,map等)的类
我试图导出类包含对象,如std :: vectors和std :: strings的类 – 整个类声明为DLL导出通过:
class DLL_EXPORT FontManager {
问题是,对于复杂types的成员,我得到这个警告:
警告C4251:'FontManager :: m__fonts':class'std :: map <_Kty,_Ty>'需要使用types为'FontManager'的客户端的dll接口,其中[_Kty = std :: string,_Ty = tFontInfoRef ]
我可以删除一些警告,即使我不改变成员variables本身的types,在他们之前放置下面的转发类声明:
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将始终使用相同的库和编译器集,而这些是仅标头的类…
我正在使用Visual Studio 2003与标准的STD库。
—-更新—-
我想更多的针对你,因为我看到的答案是一般的,在这里我们谈论的是标准容器和types(如std :: string) – 也许问题是:
我们可以通过相同的库头来禁用标准容器和types对客户端和dll可用的警告,并像对待int或任何其他内置types一样对待它们吗? (这似乎在我身边正常工作。)如果是这样,应该是我们可以做到这一点的条件?
或者应该禁止使用这样的容器,或者至less要非常小心地确保没有赋值运算符,拷贝构造函数等等会被内联到dll客户端中?
一般来说,我想知道你是否觉得devise一个dll接口有这样的对象(例如使用它们返回的东西作为返回值types的客户端)是一个好主意或不是为什么 – 我想有这个function的“高级”界面…也许最好的解决scheme是Neil Butterworthbuild议的 – 创build一个静态库?
当你从客户端触碰你的类中的成员时,你需要提供一个DLL接口。 DLL接口意味着编译器在DLL本身中创build函数并使其可导入。
由于编译器不知道DLL_EXPORTED类的客户端使用哪种方法,因此必须强制执行所有方法都是dll导出的。 它必须强制所有可以被客户访问的成员必须也出口他们的function。 当编译器警告你未导出的方法和客户端的链接器发送错误时,会发生这种情况。
不是每个成员都必须用dll-export标记,例如私人成员不能被客户触摸。 在这里你可以忽略/禁止警告(小心编译器生成的dtor / ctors)。
否则,成员必须输出他们的方法。 使用DLL_EXPORT向前声明它们不会导出这些类的方法。 您必须将其编译单元中的相关类标记为DLL_EXPORT。
它归结为…(对于不可导出的成员)
-
如果您的会员不是/不能被客户使用,请closures警告。
-
如果您有必须由客户使用的成员,创build一个dll导出包装或创build间接方法。
-
为了减less外部可见成员的数量,可以使用诸如PIMPL成语的方法 。
template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
这确实在当前编译单元中创build了模板特化的实例化。 所以这在dll中创buildstd :: allocator的方法并导出相应的方法。 这不适用于具体类,因为这只是模板类的实例化。
该警告告诉你,你的DLL的用户将无法通过DLL边界访问你的容器成员variables。 明确地导出它们使它们可用,但这是一个好主意吗?
一般来说,我会避免从你的DLL导出std容器。 如果你可以绝对保证你的DLL将被用于相同的运行时和编译器版本,那么你将是安全的。 您必须确保使用相同的内存pipe理器释放您的DLL中分配的内存。 否则,最好在运行时断言。
所以,不要直接在DLL边界上公开容器。 如果您需要公开容器元素,请通过访问器方法来实现。 在你提供的情况下,将接口从实现中分离出来,并在DLL级别暴露接口。 您使用std容器是您的DLL的客户端不需要访问的实现细节。
或者,做Neilbuild议并创build一个静态库而不是DLL。 您无法在运行时加载库,并且在更改库时,库的使用者必须重新链接。 如果这些是你可以忍受的妥协,静态库至less可以帮助你解决这个问题。 我仍然会争辩说你不必要地暴露实现细节,但它可能对你的特定库有意义。
还有其他的问题。
有些STL容器是“安全的”出口(如向量),有些则不是(如地图)。
例如Map是不安全的,因为它(在MS STL分布中)包含一个名为_Nil的静态成员,它的值在迭代中进行比较以testing结束。 每个使用STL编译的模块对于_Nil都有不同的值,因此在一个模块中创build的映射将不会从另一个模块迭代(它永远不会检测到结束并爆发)。
即使静态链接到一个库,这也是适用的,因为你永远不能保证_Nil的值是什么(它是未初始化的)。
我相信STLPort不会这样做。
我发现处理这种情况的最佳方式是:
创build你的库,命名它与库名称中包含的编译器和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
很less有人会考虑的另一种方法是不使用DLL,而是静态链接到静态.LIB库。 如果你这样做的话,所有的导出/导入问题都会消失(尽pipe如果你使用不同的编译器,你仍然会遇到名称混乱的问题)。 你当然会失去DLL体系结构的function,比如运行时加载函数,但这在很多情况下可能是一个小的代价。
find这篇文章 。 总之,亚伦有上面的“真实”答案。 不要将标准容器暴露于图书馆边界。
虽然这个线程相当老,我最近发现了一个问题,这让我再次想到在我的导出类中有模板:
我写了一个有std :: maptypes的私有成员的类。 一切工作都很好,直到它在发布模式下编译,即使在构build系统中使用,确保所有编译器设置对于所有目标都是相同的。 地图完全隐藏,没有任何东西直接暴露给客户。
结果是代码在释放模式下崩溃了。 我猜,因为不同的二进制std :: map实例是为实现和客户端代码创build的。
我猜C ++标准并没有说明如何处理导出的类,因为这几乎是编译器特有的。 所以我想最大的可移植性规则是尽可能地暴露接口并使用PIMPL惯用法。
感谢任何启示
在这种情况下,考虑使用pimpl习语。 隐藏单个void *后的所有复杂types。 编译器通常不会注意到您的成员是私有的,并包含在DLL中的所有方法。
从dll导出包含std :: objects(vector,map等)的类
另请参阅Microsoft的KB 168958文章如何导出标准模板库(STL)类的实例和包含作为STL对象的数据成员的类 。 从文章:
导出一个STL类
- 在DLL和.exe文件中,都链接到C运行时的相同DLL版本。 既可以链接到Msvcrt.lib(发布版本),也可以链接到Msvcrtd.lib(debugging版本)。
- 在DLL中,在模板实例化声明中提供__declspec说明符来从DLL中导出STL类实例。
- 在.exe文件中,在模板实例化声明中提供extern和__declspec说明符以从DLL中导入类。 这导致警告C4231“在模板显式实例化之前使用的非标准扩展:'extern'”。 你可以忽略这个警告。
和:
导出包含作为STL对象的数据成员的类
- 在DLL和.exe文件中,都链接到C运行时的相同DLL版本。 既可以链接到Msvcrt.lib(发布版本),也可以链接到Msvcrtd.lib(debugging版本)。
- 在DLL中,在模板实例化声明中提供__declspec说明符来从DLL中导出STL类实例。
注意:您不能跳过第2步。您必须导出用于创build数据成员的STL类的实例化。
- 在DLL中,在类的声明中提供__declspec说明符以从DLL中导出类。
- 在.exe文件中,在类的声明中提供__declspec说明符以从DLL中导入类。 如果要导出的类有一个或多个基类,那么还必须导出基类。
如果要导出的类包含具有类types的数据成员,则还必须导出数据成员的类。
如果使用DLL在事件“DLL PROCESS ATTACH”处初始化所有对象,并将指针导出到其类/对象。
您可以提供特定的函数来创build和销毁对象和函数以获取创build的对象的指针,因此您可以将这些调用封装在包含文件的访问包装类中。
上面的解决办法都不是MSVC可以接受的,因为像stl容器这样的模板类中的静态数据成员
每个模块(DLL / EXE)获得每个静态定义的自己的副本…哇! 这将导致可怕的事情,如果你以某种方式“导出”这样的数据(如上面“指出”)…所以不要在家里尝试
在这种情况下使用最好的方法是使用PIMPLdevise模式。