如何在我的 dll 接口或 ABI 中使用标准库 (STL) 类?
在导出包含与 Visual Studio 警告 C4251 相关的 stl 类的类之前,存在一些问题:例如这个问题或这个问题.我已经阅读了 UnknownRoad 的精彩解释.
There have been a few questions before on exporting a class which contains stl classes in relation to visual studio warning C4251: E.g. this question or this question. I have already read the excellent explanation at UnknownRoad.
盲目禁用警告似乎有点危险,尽管它可能是一种选择.包装所有这些 std 类并导出它们也不是一个真正的选择.它毕竟被称为标准模板库......也就是说,人们想要为这些标准类提供一个接口.
Blindly disabling the warning seems a little dangerous, though it may be an option. Wrapping all those std classes and exporting those is also not really an option. It is after all called the Standard Template Library... I.e., one wants to provide an interface with these standard classes.
如何在我的 dll 接口中使用 stl 类?有哪些常见做法?
How can I use stl-classes in my dll-interface? What are common practices?
推荐答案
在进一步阅读之前请记住一件事:我的答案来自编写可在由模块组成的应用程序中使用的可移植代码的角度在不同的编译器下编译.这可能包括同一编译器的不同版本甚至不同的补丁级别.
Keep in mind one thing before you read further: My answer is coming from the point of view of writing portable code that can be used in applications made up of modules compiled under different compilers. This can include different versions or even different patch levels of the same compiler.
如何在我的应用程序中使用 stl-classesdll接口?
How can I use stl-classes in my dll-interface?
答案:通常不能1.
原因: STL 是一个代码库,而不是像 DLL 那样的二进制库.它没有单一的 ABI 可以保证无论您在哪里使用它都相同.事实上,STL 确实代表标准模板库",但这里除了标准之外的一个关键操作词是模板.
Reason: The STL is a code library, not a binary library like a DLL. It does not have a single ABI that is guaranteed to be the same wherever you might use it. Indeed, STL does stand for "Standard Template Library," but a key operative word here besides Standard is Template.
该标准定义了每个 STL 类需要提供的方法和数据成员,并定义了这些方法要做什么;但没有了.特别是,标准没有指定编译器编写者应该如何实现标准定义的功能.编译器编写者可以自由地提供 STL 类的实现,该类添加了未在标准中列出的成员函数和成员变量,只要标准中定义的那些成员仍然在那里并按照标准执行.
The Standard defines the methods and data members each STL class is required to provide, and it defines what those methods are to do; but no more. In particular, the Standard doesn't specify how compiler writers should implement the Standard-defined functionality. Compiler writers are free to provide a implementation of an STL class that adds member functions and member variables not listed in the Standard, so long as those members which are defined in the Standard are still there and do what the Standard says.
也许可以举个例子.basic_string
类在标准中定义为具有某些成员函数 &变量.标准中的实际定义几乎有 4 页,但这里只是其中的一个片段:
Maybe an example is in order. The basic_string
class is defined in the Standard as having certain member functions & variables. The actual definition is almost 4 pages in the Standard, but here's just a snippet of it:
namespace std {
template<class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string {
[snip]
public:
// 21.3.3 capacity:
size_type size() const;
size_type length() const;
size_type max_size() const;
void resize(size_type n, charT c);
void resize(size_type n);
size_type capacity() const;
void reserve(size_type res_arg = 0);
void clear();
bool empty() const;
[snip]
};
考虑 size()
和 length()
成员函数.标准中没有任何内容指定用于保存此信息的成员变量.事实上,根本没有定义成员变量,甚至没有定义字符串本身.那么这是如何实现的?
Consider the size()
and length()
member functions. There is nothing in the Standard that specified member variables for holding this information. Indeed, there are no member variables defined at all, not even to hold the string itself. So how is this implemented?
答案是,许多不同的方式.一些编译器可能使用 size_t
成员变量来保存大小,使用 char*
来保存字符串.另一个可能使用指向保存该数据的其他数据存储的指针(在引用计数实现中可能是这种情况).事实上,同一编译器的不同版本甚至补丁级别可能会改变这些实现细节.你不能依赖他们.因此,MSVC 10 的实现可能如下所示:
The answer is, many different ways. Some compilers might use a size_t
member variable to hold the size and a char*
to hold the string. Another one might use a pointer to some other data store which holds that data (this might be the case in a reference-counted implementation). In fact, different versions or even patch levels of the same compiler may change these implementation details. You can't rely on them. So, MSVC 10's implementation might look like this:
namespace std {
template<class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string {
[snip]
char* m_pTheString;
};
size_t basic_string::size() const { return strlen(m_pTheString;) }
...而带有 SP1 的 MSVC 10 可能如下所示:
...Whereas MSVC 10 with SP1 might look like this:
namespace std {
template<class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string {
[snip]
vector<char> m_TheString;
};
size_t basic_string::size() const { return m_TheString.size(); }
我不是说他们确实看起来像这样,我是说他们可能会.这里的重点是实际的实现是依赖于平台的,你真的无法知道它在其他地方会是什么.
I'm not saying they do look like this, I'm saying they might. The point here is the actual implementation is platform-dependent, and you really have no way of knowing what it will be anywhere else.
现在假设您使用 MSVC10 编写一个导出此类的 DLL:
Now say you use MSVC10 to write a DLL that exports this class:
class MyGizmo
{
public:
std::string name_;
};
sizeof(MyGizmo)
是什么?
假设我在上面建议的实现,在 MSVC10 下它将是 sizeof(char*)
,但在 SP1 下它将是 sizeof(vector
.如果您在使用 DLL 的 VC10 SP1 中编写应用程序,则对象的大小看起来将与实际不同.二进制接口改变了.
Assuming my proposed implementations above, under MSVC10 its going to be sizeof(char*)
, but under SP1 it will be sizeof(vector<char>)
. If you write an application in VC10 SP1 that uses the DLL, the size of the object will look different than it actually is. The binary interface is changed.
有关此问题的另一种处理方式,请参阅 C++ 编码标准(亚马逊 链接)问题 #63.
For another treatment of this, please see C++ Coding Standards (Amazon link) issue # 63.
1:你通常不能"你实际上可以以相当高的可靠性导出标准库组件或任何其他代码库组件(例如Boost)当您完全控制工具链和库时.
1: "You often can't" You actually can export Standard Library components or any other code library components (such as Boost) with a fair amount of reliability when you have complete control over the toolchains and the libraries.
根本问题是,对于源代码库,不同编译器和不同版本的库之间的大小和定义可能不同.如果您在使用代码的任何地方都可以控制这两种情况的环境中工作,那么您可能不会有问题.例如,在所有系统均由内部编写并仅在内部使用的贸易公司中,可能会这样做.
The fundamental problem is that with source code libraries the sizes and definitions of things can be different between different compilers and different versions of the library. If you are working in an environment where you control both of these things everywhere your code is used, then you probably won't have a problem. For example at a trading firm where all the systems are written in-house and used only in-house, it might be possible to do this.
相关文章