如何安全地将对象(尤其是STL对象)传递到DLL和从DLL传递对象? 如何将类对象,尤其是STL对象传递给C++的DLL?

如何安全地将对象(尤其是STL对象)传递到DLL和从DLL传递对象? 如何将类对象,尤其是STL对象传递给C++的DLL?,c++,windows,dll,stl,abi,C++,Windows,Dll,Stl,Abi,我的应用程序必须以DLL文件的形式与第三方插件交互,我无法控制这些插件是用什么编译器构建的。我知道STL对象没有保证的ABI,我担心会导致我的应用程序不稳定。这个问题的简短答案是不要。因为没有标准的C++(应用程序二进制接口,一个调用约定的标准,数据打包/对齐,类型大小等),所以你必须跳过很多环来尝试和执行一个标准的方法来处理程序中的类对象。甚至不能保证在你跳过所有这些障碍之后它会工作,也不能保证在一个编译器版本中工作的解决方案在下一个编译器版本中也能工作 只需使用extern“C”创建一个普通

我的应用程序必须以DLL文件的形式与第三方插件交互,我无法控制这些插件是用什么编译器构建的。我知道STL对象没有保证的ABI,我担心会导致我的应用程序不稳定。

这个问题的简短答案是不要。因为没有标准的C++(应用程序二进制接口,一个调用约定的标准,数据打包/对齐,类型大小等),所以你必须跳过很多环来尝试和执行一个标准的方法来处理程序中的类对象。甚至不能保证在你跳过所有这些障碍之后它会工作,也不能保证在一个编译器版本中工作的解决方案在下一个编译器版本中也能工作

只需使用
extern“C”
创建一个普通的C接口,因为C ABI定义良好且稳定


<>如果你真的真的想通过一个DLL边界传递C++对象,那么在技术上是可能的。以下是您必须考虑的一些因素:

数据打包/对齐

在给定的类中,单个数据成员通常会被专门放在内存中,以便它们的地址对应于类型大小的倍数。例如,
int
可能与4字节边界对齐

如果DLL是使用与EXE不同的编译器编译的,则给定类的DLL版本可能与EXE版本具有不同的打包,因此当EXE将类对象传递给DLL时,DLL可能无法正确访问该类中的给定数据成员。DLL将尝试从其自己的类定义(而不是EXE的定义)指定的地址读取数据,并且由于所需的数据成员实际上未存储在该地址中,因此会产生垃圾值

您可以使用预处理器指令解决此问题,该指令将强制编译器应用特定的打包,因此,如果选择一个较大的打包值,则类在编译器之间仍然可以有不同的打包。解决方法是使用
#pragma pack(1)
,这将强制编译器在一个字节边界上对齐数据成员(基本上不应用任何打包)这不是一个好主意,因为它可能会导致性能问题,甚至在某些系统上崩溃。但是,它将确保类的数据成员在内存中对齐方式的一致性

成员重新排序

如果您的类不是,则编译器。如何做到这一点没有标准,因此任何数据重新排列都可能导致编译器之间的不兼容。因此,向DLL来回传递数据需要标准的布局类

呼叫约定

给定的函数可以有多个。这些调用约定指定了如何将数据传递给函数:参数是存储在寄存器中还是存储在堆栈上?参数被推到堆栈上的顺序是什么?函数完成后,谁清理堆栈上剩余的参数

保持一个标准的通话习惯很重要;如果你声明一个函数为代码> ycDECL ,默认为C++,并尝试调用它使用<代码>代码> > CCDLL< /Cord>是C++函数的默认调用约定,但是,除非在一个地方指定<代码> > STDCALL> <代码>,另一个地方指定<代码> > CCDLL <代码>,否则这是不会中断的。 数据类型大小


据介绍,在Windows上,无论应用程序是32位还是64位,大多数基本数据类型的大小都相同。但是,由于给定数据类型的大小是由编译器强制执行的,而不是由任何标准强制执行的(所有标准保证是
1==sizeof(char)您无法安全地跨DLL边界传递STL对象,除非所有模块(.EXE和.DLL)是用相同的C++编译器版本和CRT的相同设置和味道构建的,这是高度约束的,而且显然不是你的情况。

如果您想从DLL公开面向对象接口,则应该公开C++纯接口(类似于COM)。请阅读关于CODEPROST:

的这篇有趣文章。

您可能还想考虑在DLL边界上公开一个纯C接口,然后在调用方站点上构建一个C++包装器。


这与Win32中发生的情况类似:Win32实现代码几乎是C++,但是很多Win32 API暴露了纯C接口(也有API暴露COM接口)。然后,ATL/WTL和MFC将这些纯C接口与C++类和对象包在一起。

< P> @ CypFielk已经写了一个很好的解释,说明为什么ABI的缺乏阻止一般情况下跨DLL边界传递C++对象,即使类型定义在用户控制之下,并且在两个程序中都使用相同的令牌序列。(有两种情况可行:标准布局类和纯接口)

< C++标准中定义的对象类型(包括从标准模板库中修改的对象类型)定义这些类型的令牌在多个编译器上不相同,因为C++标准没有提供完整的类型定义,只提供了最低要求。此外,在这些类型定义中出现的标识符的名称查找也不能解决相同的问题。一个C++ ABI,试图在模块边界上共享这种类型,导致一个定义规则违反导致大量未定义行为。< /强>

这是Linux程序员不习惯处理的问题,因为g++的libstdc++是事实上的标准,几乎所有程序都使用它
EXPORTS
    GetCCDLL=_Z8GetCCDLLv @1
EXPORTS
    GetCCDLL=?GetCCDLL@@YAPAUCCDLL_v1@@XZ @1
//POD_base.h: defines a template base class that wraps and unwraps data types for safe passing across compiler boundaries

//define malloc/free replacements to make use of Windows heap APIs
namespace pod_helpers
{
  void* pod_malloc(size_t size)
  {
    HANDLE heapHandle = GetProcessHeap();
    HANDLE storageHandle = nullptr;

    if (heapHandle == nullptr)
    {
      return nullptr;
    }

    storageHandle = HeapAlloc(heapHandle, 0, size);

    return storageHandle;
  }

  void pod_free(void* ptr)
  {
    HANDLE heapHandle = GetProcessHeap();
    if (heapHandle == nullptr)
    {
      return;
    }

    if (ptr == nullptr)
    {
      return;
    }

    HeapFree(heapHandle, 0, ptr);
  }
}

//define a template base class. We'll specialize this class for each datatype we want to pass across compiler boundaries.
#pragma pack(push, 1)
// All members are protected, because the class *must* be specialized
// for each type
template<typename T>
class pod
{
protected:
  pod();
  pod(const T& value);
  pod(const pod& copy);
  ~pod();

  pod<T>& operator=(pod<T> value);
  operator T() const;

  T get() const;
  void swap(pod<T>& first, pod<T>& second);
};
#pragma pack(pop)

//POD_basic_types.h: holds pod specializations for basic datatypes.
#pragma pack(push, 1)
template<>
class pod<unsigned int>
{
  //these are a couple of convenience typedefs that make the class easier to specialize and understand, since the behind-the-scenes logic is almost entirely the same except for the underlying datatypes in each specialization.
  typedef int original_type;
  typedef std::int32_t safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  safe_type* data;

  original_type get() const
  {
    original_type result;

    result = static_cast<original_type>(*data);

    return result;
  }

  void set_from(const original_type& value)
  {
    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type))); //note the pod_malloc call here - we want our memory buffer to go in the process heap, not the possibly-isolated DLL heap.

    if (data == nullptr)
    {
      return;
    }

    new(data) safe_type (value);
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data); //pod_free to go with the pod_malloc.
      data = nullptr;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
  }
};
#pragma pack(pop)
#pragma pack(push, 1)
template<typename charT>
class pod<std::basic_string<charT>> //double template ftw. We're specializing pod for std::basic_string, but we're making this specialization able to be specialized for different types; this way we can support all the basic_string types without needing to create four specializations of pod.
{
  //more comfort typedefs
  typedef std::basic_string<charT> original_type;
  typedef charT safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const charT* charValue)
  {
    original_type temp(charValue);
    set_from(temp);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  //this is almost the same as a basic type specialization, but we have to keep track of the number of elements being stored within the basic_string as well as the elements themselves.
  safe_type* data;
  typename original_type::size_type dataSize;

  original_type get() const
  {
    original_type result;
    result.reserve(dataSize);

    std::copy(data, data + dataSize, std::back_inserter(result));

    return result;
  }

  void set_from(const original_type& value)
  {
    dataSize = value.size();

    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type) * dataSize));

    if (data == nullptr)
    {
      return;
    }

    //figure out where the data to copy starts and stops, then loop through the basic_string and copy each element to our buffer.
    safe_type* dataIterPtr = data;
    safe_type* dataEndPtr = data + dataSize;
    typename original_type::const_iterator iter = value.begin();

    for (; dataIterPtr != dataEndPtr;)
    {
      new(dataIterPtr++) safe_type(*iter++);
    }
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data);
      data = nullptr;
      dataSize = 0;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
    swap(first.dataSize, second.dataSize);
  }
};
#pragma pack(pop)
//CCDLL.h: defines a DLL interface for a pod-based DLL
struct CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) = 0;
};

CCDLL_v1* GetCCDLL();
struct CCDLL_v1_implementation: CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) override;
};

CCDLL_v1* GetCCDLL()
{
  static CCDLL_v1_implementation* CCDLL = nullptr;

  if (!CCDLL)
  {
    CCDLL = new CCDLL_v1_implementation;
  }

  return CCDLL;
}
#include "CCDLL_implementation.h"
void CCDLL_v1_implementation::ShowMessage(const pod<std::wstring>* message)
{
  std::wstring workingMessage = *message;

  MessageBox(NULL, workingMessage.c_str(), TEXT("This is a cross-compiler message"), MB_OK);
}
//main.cpp
#include "../CCDLL/CCDLL.h"

typedef CCDLL_v1*(__cdecl* fnGetCCDLL)();
static fnGetCCDLL Ptr_GetCCDLL = NULL;

int main()
{
  HMODULE ccdll = LoadLibrary(TEXT("D:\\Programming\\C++\\CCDLL\\Debug_VS\\CCDLL.dll")); //I built the DLL with Visual Studio and the EXE with GCC. Your paths may vary.

  Ptr_GetCCDLL = (fnGetCCDLL)GetProcAddress(ccdll, (LPCSTR)"GetCCDLL");
  CCDLL_v1* CCDLL_lib;

  CCDLL_lib = Ptr_GetCCDLL(); //This calls the DLL's GetCCDLL method, which is an alias to the mangled function. By dynamically loading the DLL like this, we're completely bypassing the name mangling, exactly as expected.

  pod<std::wstring> message = TEXT("Hello world!");

  CCDLL_lib->ShowMessage(&message);

  FreeLibrary(ccdll); //unload the library when we're done with it

  return 0;
}