C++ 从 dll 导出包含 std:: 对象(向量、映射等)的类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/767579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 17:12:03  来源:igfitidea点击:

Exporting classes containing std:: objects (vector, map, etc) from a dll

c++visual-studiodll

提问by RnR

I'm trying to export classes from a DLL that contain objects such as std::vectors and std::strings - the whole class is declared as dll export through:

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

    class DLL_EXPORT FontManager
{

The problem is that for members of the complex types I get this warning:

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

warning C4251: 'FontManager::m__fonts' : class 'std::map<_Kty,_Ty>' needs to have dll-interface to be used by clients of class 'FontManager' with [ _Kty=std::string, _Ty=tFontInfoRef ]

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

I'm able to remove some of the warnings by putting the following forward class declaration before them even though I'm not changing the type of the member variables themselves:

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

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

Looks like the forward declaration "injects" the DLL_EXPORT for when the member is compiled but is it safe? Does it realy change anything when the client compiles this header and uses the std container on his side? Will it make all future uses of such a container DLL_EXPORT (and possibly not inline?)? And does it really solve the problem that the warning tries to warn about?

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

Is this warning anything I should be worried about or would it be best to disable it in the scope of these constructs? The clients and the dll will always be built using the same set of libraries and compilers and those are header only classes...

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

I'm using Visual Studio 2003 with the standard STD library.

我将 Visual Studio 2003 与标准 STD 库一起使用。

---- Update ----

- - 更新 - -

I'd like to target you more though as I see the answers are general and here we're talking about std containers and types (such as std::string) - maybe the question really is:

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

Can we disable the warning for standard containers and types available to both the client and the dll through the same library headers and treat them just as we'd treat an int or any other built-in type? (It does seem to work correctly on my side.) If so would should be the conditions under which we can do this?

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

Or should maybe using such containers be prohibited or at least ultra care taken to make sure no assignment operators, copy constructors etc will get inlined into the dll client?

或者是否应该禁止使用此类容器,或者至少要格外小心以确保没有赋值运算符、复制构造函数等会被内联到 dll 客户端中?

In general I'd like to know if you feel designing a dll interface having such objects (and for example using them to return stuff to the client as return value types) is a good idea or not and why - I'd like to have a "high level" interface to this functionality... maybe the best solution is what Neil Butterworth suggested - creating a static library?

一般来说,我想知道您是否觉得设计一个具有此类对象的 dll 接口(例如使用它们将内容作为返回值类型返回给客户端)是否是一个好主意以及为什么 - 我想要这个功能的“高级”接口......也许最好的解决方案是尼尔巴特沃斯建议的 - 创建一个静态库?

采纳答案by Christopher

When you touch a member in your class from the client, you need to provide a DLL-interface. A DLL-interface means, that the compiler creates the function in the DLL itself and makes it importable.

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

Because the compiler doesn't know which methods are used by the clients of a DLL_EXPORTED class it must enforce that all methods are dll-exported. It must enforce that all members which can be accessed by clients must dll-export their functions too. This happens when the compiler is warning you of methods not exported and the linker of the client sending errors.

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

Not every member must be marked with with dll-export, e.g. private members not touchable by clients. Here you can ignore/disable the warnings (beware of compiler generated dtor/ctors).

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

Otherwise the members must export their methods. Forward declaring them with DLL_EXPORT does not export the methods of these classes. You have to mark the according classes in their compilation-unit as DLL_EXPORT.

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

What it boils down to ... (for not dll-exportable members)

它归结为……(对于非 dll 可导出成员)

  1. If you have members which aren't/can't be used by clients, switch off the warning.

  2. If you have members which must be used by clients, create a dll-export wrapper or create indirection methods.

  3. To cut down the count of externally visible members, use approaches such as the PIMPL idiom.

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

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

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



template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

This does create an instantiation of the template specialization in the current compilation unit. So this creates the methods of std::allocator in the dll and exports the corresponding methods. This does not work for concrete classes as this is only an instantiation of template classes.

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

回答by Aaron Saarela

That warning is telling you that users of your DLL will not have access to your container member variables across the DLL boundary. Explicitly exporting them makes them available, but is it a good idea?

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

In general, I'd avoid exporting std containers from your DLL. If you can absolutely guarantee your DLL will be used with the same runtime and compiler version you'd be safe. You must ensure memory allocated in your DLL is deallocated using the same memory manager. To do otherwise will, at best, assert at runtime.

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

So, don't expose containers directly across DLL boundaries. If you need to expose container elements, do so via accessor methods. In the case you provided, separate the interface from the implementation and expose the inteface at the DLL level. Your use of std containers is an implementation detail that the client of your DLL shouldn't need to access.

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

Alternatively, do what Neil suggest and create a static library instead of a DLL. You lose the ability to load the library at runtime, and consumers of your library must relink anytime you change your library. If these are compromises you can live with, a static library would at least get you past this problem. I'll still argue you're exposing implementation details unnecessarily but it might make sense for your particular library.

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

回答by Adrien

There are other issues.

还有其他问题。

Some STL containers are "safe" to export (such as vector), and some aren't (e.g. map).

有些 STL 容器可以“安全”导出(例如 vector),有些则不是(例如 map)。

Map for instance is unsafe because it (in the MS STL distribution anyway) contains a static member called _Nil, the value of which is compared in iteration to test for the end. Every module compiled with STL has a different value for _Nil, and so a map created in one module will not be iterable from another module (it never detects the end and blows up).

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

This would apply even if you statically link to a lib, since you can never guarantee what the value of _Nil will be (it's uninitialised).

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

I believe STLPort doesn't do this.

我相信 STLPort 不会这样做。

回答by Rodrigo Pinho Pereira de Souza

The best way I found to handle this scenario is:

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

create your library, naming it with the compiler and stl versions included in the library name, exactly like boost libraries do.

创建您的库,使用库名称中包含的编译器和 stl 版本命名它,就像 boost 库所做的那样。

examples:

例子:

- FontManager-msvc10-mt.dllfor dll version, specific for MSVC10 compiler, with the default stl.

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

- FontManager-msvc10_stlport-mt.dllfor dll version, specific for MSVC10 compiler, with the stl port.

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

- FontManager-msvc9-mt.dllfor dll version, specific for MSVC 2008 compiler, with the default stl

- FontManager-msvc9-mt.dllfor dll 版本,特定于 MSVC 2008 编译器,默认 stl

- libFontManager-msvc10-mt.libfor static lib version, specific for MSVC10 compiler, with the default stl.

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

following this pattern, you will avoid problems related with different stl implementations. remember, the stl implementation in vc2008 differs from the stl implementation in the vc2010.

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

See your example using boost::config library:

使用 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

回答by Rodrigo Pinho Pereira de Souza

One alternative that few people seem to consider is not to use a DLL at all but to link statically against a static .LIB library. If you do that, all the issues of exporting/importing go away (though you will still have name-mangling issues if you use different compilers). You do of course lose the features of the DLL architecture, such as run-time loading of functions, but this can be a small price to pay in many cases.

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

回答by Chris Huang-Leaver

Found this article. In short Aaron has the 'real' answer above; Don't expose standard containers across library boundaries.

找到了这篇文章。简而言之,Aaron 在上面给出了“真实”的答案;不要跨库边界公开标准容器。

回答by TheBigW

Though this thread is pretty old, I found a problem recently, which made me think again about having templates in my exported classes:

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

I wrote a class which had a private member of type std::map. Everything worked quite well untill it got compiled in release mode, Even when used in a build system, which ensures that all compiler settings are the same for all targets. The map was completely hidden and nothing was directly exposed to the clients.

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

As a result the code was just crashing in release mode. I gues, because different binary std::map instances were created for implementation and client code.

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

I guess the C++ Standard is not saying anaything about how this shall be handled for exported classes as this is pretty much compiler specific. So I guess the biggest portability rule is to just expose Interfaces and use the PIMPL idiom as much as possible.

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

Thanks for any enlightenment

感谢您的启发

回答by jww

Exporting classes containing std:: objects (vector, map, etc) from a dll

从 dll 导出包含 std:: 对象(向量、映射等)的类

Also see Microsoft's KB 168958 article 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. From the article:

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

To Export an STL Class

  1. In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
  2. In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.
  3. In the .exe file, provide the extern and __declspec specifiers in the template instantiation declaration to import the class from the DLL. This results in a warning C4231 "nonstandard extension used : 'extern' before template explicit instantiation." You can ignore this warning.

导出 STL 类

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

And:

和:

To Export a Class Containing a Data Member that Is an STL Object

  1. In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
  2. In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.

    NOTE: You cannot skip step 2. You must export the instantiation of the STL class that you use to create the data member.
  3. In the DLL, provide the __declspec specifier in the declaration of the class to export the class from the DLL.
  4. In the .exe file, provide the __declspec specifier in the declaration of the class to import the class from the DLL. If the class you are exporting has one or more base classes, then you must export the base classes as well.

    If the class you are exporting contains data members that are of class type, then you must export the classes of the data members as well.

导出包含作为 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 导入类。如果您要导出的类具有一个或多个基类,那么您也必须导出基类。

    如果您要导出的类包含类类型的数据成员,那么您也必须导出数据成员的类。

回答by MSalters

In such cases, consider the uses of the pimpl idiom. Hide all the complex types behind a single void*. Compiler typically fails to notice that your members are private and all methods included in the DLL.

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

回答by lsalamon

If you use a DLL make initialization of all objects at event "DLL PROCESS ATTACH" and export a pointer to its classes/objects.
You may provide specific functions to create and destroy objects and functions to obtain the pointer of the objects created, so you can encapsulate these calls in a wrapper class of access at include file.

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