[...] 我知道您不应该将 STL 容器传递给 DLL [...] - 这是错误的假设。
先决条件
事实上,您可以跨 DLL 边界传递 STL 对象。此外,您可以以跨平台的方式进行。像往常一样,巧妙的预处理器操作来拯救。以下配置标头对于任何跨平台 DLL 都是必须的:
Config.hpp
:
#ifndef MY_DLL_CONFIG_HPP
#define MY_DLL_CONFIG_HPP
#ifdef __cplusplus
#define MY_DLL_CPP
#else
#define MY_DLL_C
#endif
#if \
defined (__CYGWIN__) || \
defined (__CYGWIN32__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_WINDOWS
#define MY_DLL_OS_CYGWIN
#elif \
defined (_WIN16) || \
defined (_WIN32) || \
defined (_WIN64) || \
defined (__WIN32__) || \
defined (__TOS_WIN__) || \
defined (__WINDOWS__)
#define MY_DLL_OS
#define MY_DLL_OS_WINDOWS
#elif \
defined (macintosh) || \
defined (Macintosh) || \
defined (__TOS_MACOS__) || \
(defined (__APPLE__) && defined (__MACH__))
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_MAC
#elif \
defined (linux) || \
defined (__linux) || \
defined (__linux__) || \
defined (__TOS_LINUX__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_LINUX
#elif \
defined (__FreeBSD__) || \
defined (__OpenBSD__) || \
defined (__NetBSD__) || \
defined (__bsdi__) || \
defined (__DragonFly__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_BSD
#elif \
defined (sun) || \
defined (__sun)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_SOLARIS
#elif \
defined (_AIX) || \
defined (__TOS_AIX__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_AIX
#elif \
defined (hpux) || \
defined (_hpux) || \
defined (__hpux)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_HPUX
#elif \
defined (__QNX__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#define MY_DLL_OS_QNX
#elif \
defined (unix) || \
defined (__unix) || \
defined (__unix__)
#define MY_DLL_OS
#define MY_DLL_OS_UNIX
#endif
#ifndef MY_DLL_OS
#error "Sorry, but current OS is not supported by MyDLL."
#endif
#if \
defined (__MINGW32__) || \
defined (__MINGW64__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_MINGW
#elif \
defined (__GNUC__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_GNU
#define MY_DLL_COMPILER_GNU_VERSION_MAJOR __GNUC__
#define MY_DLL_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__
#define MY_DLL_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__
#elif \
defined (__clang__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_CLANG
#elif \
defined (_MSC_VER)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_MICROSOFT
#elif \
defined (__BORLANDC__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_BORLAND
#elif \
defined (__CODEGEARC__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_CODEGEAR
#elif \
defined (__INTEL_COMPILER) || \
defined (__ICL)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_INTEL
#elif \
defined (__xlc__) || \
defined (__xlC__) || \
defined (__IBMC__) || \
defined (__IBMCPP__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_IBM
#elif \
defined (__HP_aCC)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_HP
#elif \
defined (__WATCOMC__)
#define MY_DLL_COMPILER
#define MY_DLL_COMPILER_WATCOM
#endif
#ifndef MY_DLL_COMPILER
#error "Sorry, but current compiler is not supported by MyDLL."
#endif
#if \
defined (MY_DLL_OS_WINDOWS) && \
!defined (BOOST_DISABLE_WIN32)
#define MY_DLL_EXPORT_DECLARATION __declspec(dllexport)
#define MY_DLL_IMPORT_DECLARATION __declspec(dllimport)
#elif \
MY_DLL_COMPILER_GNU_VERSION_MAJOR >= 4
#define MY_DLL_EXPORT_DECLARATION __attribute__((visibility("default")))
#define MY_DLL_IMPORT_DECLARATION __attribute__((visibility("default")))
#else
#define MY_DLL_EXPORT_DECLARATION
#define MY_DLL_IMPORT_DECLARATION
#endif
#ifdef MY_DLL_EXPORT
#define MY_DLL_API MY_DLL_EXPORT_DECLARATION
#define MY_DLL_FUNCTION
#define MY_DLL_TYPE
#else
#define MY_DLL_API MY_DLL_IMPORT_DECLARATION
#ifdef MY_DLL_CPP
#define MY_DLL_FUNCTION extern "C"
#else
#define MY_DLL_FUNCTION extern
#endif
#define MY_DLL_TYPE extern
#endif
#endif
注意:在 OP 的问题中,有些平台检测可能被认为是矫枉过正。但是,它们在创建生产 DLL 方面非常重要,所以我不想剥离它们。
例子
MyDLL.hpp
:
#include "Config.hpp"
#include <vector>
// Instantiate class std::vector<int>.
// NOTE: This does not create an object. It only enforces the generation
// of all of the members of class std::vector<int>. It exports them from
// the DLL and imports them into any artifact linked against this DLL.
MY_DLL_TYPE template class MY_DLL_API std::vector<int>;
// Instantiate class std::vector<float>.
// NOTE: This does not create an object. It only enforces the generation
// of all of the members of class std::vector<float>. It exports them from
// the DLL and imports them into any artifact linked against this DLL.
MY_DLL_TYPE template class MY_DLL_API std::vector<float>;
class MY_DLL_API MyClass {
public:
static std::vector<char> floatVector;
void
setIntVector(std::vector<int> intVector);
std::vector<int>&
intVector();
bool operator < (MyClass const& c) const
{ return _intVector < c._intVector; }
bool operator == (MyClass const& c) const
{ return _intVector == c._intVector; }
private:
std::vector<int> _intVector;
};
// Instantiate class std::vector<MyClass>.
// NOTE: This does not create an object. It only enforces the generation
// of all of the members of class std::vector<MyClass>. It exports them from
// the DLL and imports them into any artifact linked against this DLL.
MY_DLL_TYPE template class MY_DLL_API std::vector<MyClass>;
MyDLL.cpp
:
#include "MyDLL.hpp"
std::vector<float> MyClass::floatVector;
void
MyClass::
setIntVector(std::vector<int> intVector)
{ _intVector = intVector; }
std::vector<int>&
MyClass::
intVector()
{ return _intVector; }
注意:使用 MY_DLL_EXPORT
定义构建。最佳实践是在编译期间提供此定义。例如,在 MSVC 上它将是/DMY_DLL_EXPORT
,而在 GCC 上它将是-DMY_DLL_EXPORT
.
Application.cpp
:
#include "MyDLL.hpp"
#include <iostream>
using std::cout;
using std::endl;
using std::vector;
int main() {
MyClass x;
x.setIntVector({-3, -2, -1});
for (int i = 0; i < 7; ++i)
x.intVector().push_back(i);
for (int i = 0; i < 10; ++i)
x.floatVector.push_back(i);
for (auto value : x.intVector())
cout << value << endl;
cout << endl;
for (auto value : x.floatVector)
cout << value << endl;
cout << endl;
vector<MyClass> v;
for (int i = 0; i < 5; ++i)
v.push_back(MyClass());
}
注意:构建没有 MY_DLL_EXPORT
定义。
因此,您的 DLL 的使用者将使用从 DLL 导出的二进制代码,而不是实例化他们自己的。
免责声明
有一些你可能不知道的陷阱,所以我觉得我应该提到以上所有功能正常运行的重要限制。
您必须确保使用同一编译器的同一版本
来构建您的 DLL 及其使用者。
对您的 DLL 及其使用者而言,可能影响 STL 对象布局的构建设置应该完全相同。
您的 DLL 及其使用者都应该链接到相同的编译器运行时。
也请注意这一点。
MSVC 可能会生成警告:C4231 "nonstandard extension used : 'extern' before
template explicit instantiation.
,可以安全地忽略它。
一些 STL 类使用其他 STL 类。这些其他类也必须导出。必须导出的类列在编译器警告中。
一些 STL 类包含嵌套类。这些类无法导出。例如,deque
包含一个嵌套类deque::iterator
。如果您导出deque
,您将收到必须导出的警告deque::iterator
。如果您导出
deque::iterator
,您会收到必须导出的警告deque
。我可以保证两者vector
都string
可以导出。其他容器都包含嵌套类,无法导出。我不确定这是否仍然相关,您必须进行实验。
当您导出使用用户定义类型 (UDT) 参数化的 STL 容器时,您必须为您的 UDT 定义运算符<
和==
。例如,如果您导出
vector<MyClass>
,则必须定义MyClass::operator <
和MyClass operator ==
。这是因为所有 STL 容器类都有成员比较运算符,这些运算符需要运算符的存在<
和==
包含的类型。
注意:其中一些可能已经无关紧要,因此必须在实践中进行检查。