【问题标题】:What are my options for passing variable size arrays to DLLs?将可变大小的数组传递给 DLL 有哪些选择?
【发布时间】:2013-04-18 22:02:57
【问题描述】:

我想知道如果我想将在运行时确定大小的数组传递给 DLL 函数,我有哪些选项。目前我在 DLL 中的函数签名看起来像

#ifdef BUILDING_DLL
#define DBUILDING __declspec(dllexport)
#else
#define DBUILDING __declspec(dllimport)
#endif

void __cdecl DBUILDING myFunc(const double t, const double x[], double *xdot);

所以我传递的是在某个类中动态分配的普通旧数据类型,然后必须手动删除这些数据类型。我宁愿不必调用 delete 并担心内存管理,所以我想看看其他选项。显而易见的选择是向量,但我知道您不应该将 STL 容器传递给 dll,所以我需要其他东西。我需要处理内存管理但也可以传递给 dll 的东西。

我目前的想法可能是使用 boost::scoped_array 及其 get() 函数以指针形式返回原始数据。这似乎是最好的主意还是我忽略了什么?

【问题讨论】:

  • 我觉得不错? { 数组 [256]; myFunc(t, array, xdot) } 不需要删除
  • x 和 xdot 的大小是在编译时确定的,因此在代码中的某个点我有... double *x = new double[size...]
  • “编译时间”实际上是指“运行时间”吗?
  • 天哪...是的运行时间对不起!

标签: c++ arrays memory-management boost dll


【解决方案1】:

您说得对,跨 DLL 边界共享 C++ 对象(尤其是标准中定义的类)是个坏主意。只是其中一些原因是违反单一定义规则和混合不同的分配器。

但是,没有什么能阻止您将 vector 的内容传递到 DLL 边界:

vector<double> x;
// ... fill it in, change the size
myFunc(t, &x[0], x.size(), xdot);

请注意,您几乎总是必须将大小作为额外参数传递。

【讨论】:

  • 简单有效,从来没想过。感谢您的回答。
【解决方案2】:

[...] 我知道您不应该将 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 导出的二进制代码,而不是实例化他们自己的。

免责声明


有一些你可能不知道的陷阱,所以我觉得我应该提到以上所有功能正常运行的重要限制。

  1. 您必须确保同一编译器的同一版本 用于构建您的 DLL 及其使用者。

  2. 可能影响 STL 对象布局的构建设置应 对您的 DLL 及其使用者来说完全相同。

  3. 您的 DLL 及其使用者都应该链接到同一个编译器运行时。

  4. 也要小心this

  5. MSVC 可能会生成警告:C4231 "nonstandard extension used : 'extern' before template explicit instantiation.,可以放心地忽略它。

  6. 某些 STL 类使用其他 STL 类。这些其他类也必须导出。 必须导出的类列在编译器警告中。

  7. 一些 STL 类包含嵌套类。这些类无法导出。为了 例如,deque 包含一个嵌套类 deque::iterator。如果你导出deque,你 将收到一个警告,您必须导出 deque::iterator。如果你出口 deque::iterator,您会收到必须导出 deque 的警告。我可以保证 vectorstring 都可以导出。其他容器都包含 嵌套类,不能导出。 我不确定这是否仍然相关,你会 必须进行实验。

  8. 当您导出使用用户定义类型 (UDT) 参数化的 STL 容器时,您必须 为您的 UDT 定义运算符 &lt;==。例如,如果您导出 vector&lt;MyClass&gt;,您必须定义 MyClass::operator &lt;MyClass operator ==。 这是因为所有 STL 容器类都有成员比较运算符 要求包含的类型存在运算符 &lt;==

注意:其中一些可能已经无关紧要,因此必须在实践中进行检查。

【讨论】:

  • 哎呀,这是一个沉重的职位。很多东西,但现在我保持简单,只在我的笔记本电脑上运行。当它需要这个时,我会回来看一看。谢谢你的回答。
  • 这篇文章以 Disclaimer 开头的部分是对 OP 听到的经验法则的一个相当好的解释。为什么其余的答案使这些问题变得微不足道(尤其是#4,这比“也要提防这个”所暗示的要严重得多并且没有解决方法),我不知道。
  • @Ben Voigt:您是否看到本文适用于哪些版本的 Visual C++?如果没有,那么您可能应该再次检查它。如果 OP 使用最先进的 Visual Studio,其中许多问题很可能已经过时。我自己检查过它是否有效,而您显然没有,否则您和@Cody Gray 都不会下结论并写出无意义的批评。最后,我已经声明,跨 DLL 边界传递 vectorstring 是安全的,这已经足以满足 OP 的需求,而您的答案只是对 vector 实现细节的利用。
  • @Haroogan:你说过通过vectorstring 是安全的,并给出了不安全的8 个原因。
猜你喜欢
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-20
  • 1970-01-01
  • 2021-10-31
相关资源
最近更新 更多