c++ - visual - vs2017生成dll




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

我正在尝试从包含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<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库。

----更新----

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

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

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

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


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

另请参阅Microsoft的KB 168958文章如何导出标准模板库(STL)类的实例化以及包含作为STL对象的数据成员的类 。 来自文章:

导出STL类

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

和:

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

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

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

    如果要导出的类包含类类型的数据成员,则还必须导出数据成员的类。

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


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


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

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

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

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

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

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

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

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

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

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


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

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

-用于静态lib版本的libFontManager-msvc10-mt.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

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


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

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

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

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

感谢任何启示


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

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

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

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





dll