我试图从包含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建议的 - 创建一个静态库?
当您从客户端触摸类中的成员时,您需要提供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的方法并导出相应的方法.这不适用于具体类,因为这只是模板类的实例化.
该警告告诉您DLL的用户将无法访问DLL边界上的容器成员变量.明确地导出它们使它们可用,但这是一个好主意吗?
通常,我会避免从DLL导出std容器.如果您绝对可以保证您的DLL将与相同的运行时和编译器版本一起使用,那么您将是安全的.您必须确保使用相同的内存管理器取消分配DLL中分配的内存.否则,最好在运行时断言.
因此,不要直接在DLL边界上公开容器.如果需要公开容器元素,请通过访问器方法执行此操作.在您提供的情况下,将接口与实现分开,并在DLL级别公开接口.您对std容器的使用是DLL的客户端不应该访问的实现细节.
或者,做Neil建议并创建一个静态库而不是DLL.您无法在运行时加载库,并且您的库的使用者必须在您更改库时随时重新链接.如果这些是您可以接受的妥协,静态库至少会让您解决这个问题.我仍然会争辩说你不必要地暴露实现细节,但它可能对你的特定库有意义.
还有其他问题.
一些STL容器对于导出是"安全的"(例如矢量),而一些不是(例如map).
例如,Map是不安全的,因为它(无论如何在MS STL分发中)包含一个名为_Nil的静态成员,其值在迭代中进行比较以测试结束.使用STL编译的每个模块都具有不同的_Nil值,因此在一个模块中创建的映射将不能从另一个模块迭代(它永远不会检测到结束并且爆炸).
即使你静态链接到lib,这也适用,因为你无法保证_Nil的值是什么(它是未初始化的).
我相信STLPort不会这样做.
我发现处理这种情况的最佳方法是:
创建你的库,用库名中包含的编译器和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
很少有人会考虑考虑的另一种选择是根本不使用DLL而是静态链接静态.LIB库.如果这样做,导出/导入的所有问题都会消失(尽管如果使用不同的编译器,仍会出现名称错误问题).您当然会失去DLL体系结构的功能,例如函数的运行时加载,但在许多情况下这可能是一个很小的代价.