【问题标题】:C++ wrapper DLLs to static LIBsC++ 包装 DLL 到静态 LIB
【发布时间】:2011-08-30 17:37:38
【问题描述】:

我在我的项目中使用了一些静态编译的库 (.lib),这些库是用 C++ 编写并在 Windows 和 Linux 上构建的。在我的项目的这些库的入口点,我只使用静态库套件中“主”库中的一两个函数,真的(但我确信这些函数调用套件中其他库中的许多其他函数) .

理想情况下,我希望拥有一套动态链接库 (DLL),它环绕静态库套件中的每个库;我已经阅读/听说在 Windows(例如 Visual Studio 2005/2008/2010)上执行此操作的方法是“创建一个包装 DLL”,其中包含一些调用底层静态库函数的公开函数。如果有人能给我一些详细的分步信息,包括一些 sn-ps,我将非常感激如何在 MS Visual Studio 2005/2008/2010 中执行此操作。我相信你们中的一些人可能已经每天都在这样做;非常感谢您的经验。

编辑:

为了像我这样的其他人的利益,我发布了我发现的第一个“有用”链接: http://tom-shelton.net/index.php/2008/12/11/creating-a-managed-wrapper-for-a-lib-file/

【问题讨论】:

  • 创建一个dll项目,链接到静态库,导出你想要的函数

标签: c++ windows visual-studio


【解决方案1】:

“将库转换为另一种库类型”似乎很容易,但实际上并非如此。因为 C++ 和 DLL 不能很好地协同工作,所以没有直接的分步方法来做到这一点,并且您的代码需要进行调整以支持 DLL 接口。

描述问题的简洁方式是这样的:

  • .lib 的接口是 C++
  • .dll 的接口是 C

因此,DLL 的接口根本不支持 C++,您需要 聪明 使其工作 - 这就是现有答案模棱两可的原因。

一种标准方式是通过 COM,这意味着为库构建一个完整的 COM 包装器,包括类工厂、接口、对象,并使用 BSTR 而不是 std::string。我猜是不实用的。

另一个解决方案是为您的 C++ 库创建一个 DLL 安全的 C 接口。这意味着基本上创建一个 winapi 风格的界面,这又可能不实用或完全违背了使用库的目的。这就是@David Heffernan 的建议。但他没有解决的是您必须如何更改代码以与 DLL 兼容。

一个重要但微妙的问题是您不能跨 DLL 边界传递任何模板化的 C++ 对象。这意味着将std::string 传入或传出DLL 函数被认为是不安全的。每个二进制文件都有自己的std::string 代码副本,并且不能保证它们会碰巧相互配合。每个二进制文件(可能)也有自己的 CRT 副本,您将通过操作另一个模块的对象来搞乱一个模块的内部状态。

编辑:您可以在 MSVC 中使用 __declspec(dllexport) 导出 C++ 对象并使用 __declspec(dllimport) 导入它们。但是对此有很多限制和导致问题的微妙之处。基本上,这是让编译器为导出的类或函数创建廉价的 C 风格接口的捷径。问题是它不会警告您正在发生多少不安全的事情。重申:

  1. 如果有任何模板符号跨越 DLL 边界,则不安全(例如std::*)。
  2. 任何具有 CRT 托管状态的对象都不应跨越 DLL 边界(例如FILE*)。

【讨论】:

  • 当然可以,但是对它的限制非常复杂且不直观除非您了解编译器如何将其转换为 C 接口并在以后解开。但是,是的,我应该提到这一点。
  • @David:我发誓在过去的一年里,我认为你的姓氏是 Hefferman。今天我学到了。
  • @tenfour 从 DLL 导入 C++ 代码比从 .lib 导入更受限制?关于我名字的拼写,你不会相信我一生中有多少不同的变化!
  • 它受到更多限制,因为运行时库是在链接时协商的。运行时冲突时模块不会链接,从而防止 CRT 状态出现问题。关于内联代码,理论上问题仍然存在于 lib 中,但是由于运行时在 lib 和您的代码之间“同步”,因此实际上出现问题的可能性为零。您需要使用与 lib 不同的实现,如果您成功链接到它,这是极不可能的。
  • c++ 和 DLL 的困难众所周知,主要与版本控制有关。这对于您无法控制的标准库之类的东西尤其危险。如果您的 DLL 被编译为使用具有一种内部实现类型(例如 char 指针和计数)的字符串,然后被针对字符串的不同实现编译的程序使用(例如,具有复制- on-write 数据成员,或不同的缓冲区排列以支持移动语义,或者......)那么你可以不明显的错误。坚持使用 c DLL。
【解决方案2】:

作为评论添加到 tenfour 的回复中,这有点大...

如果您希望在使用 DLL 包装器时仍保持 C++ API,您可以将 C++ 到 C 的转换函数放在头文件中。这确保了只有 C 兼容的数据类型才能跨越 DLL 边界。

举个例子

//MyDLL.h

class MyDLL {
 public:
  ...
  int Add2ToValues(std::vector<int>& someValues) {
   int* cValues = new int[someValues.size()];
   memcpy(cValues, &someValues[0], someValues.size() * sizeof(int));
   int retVal = Add2ToValues_Internal(cValues, someValues.size());
   someValues.assign(std::begin(cValues), std::end(cValues));
   delete [] cValues;
   return retVal;
  }

private:
  int Add2ToValues_Internal(int* valuesOut, const int numValues);
};

//MyDLL.cpp

 int MyDLL::Add2ToValues_Internal(int* values, const int numValues)
 {
   for(int i = 0; i < numValues; ++i) {
     values[i] += 2;
   }

   return 0;
 }

我在执行这些包装器时遇到的一个问题是,您必须在头文件中分配和取消分配任何内存。由于头文件将由使用您的库的应用程序编译,因此它将使用 CRT 用于您用于构建应用程序的任何编译器。与 DLL 的所有交互都使用 C,因此您不会遇到任何运行时不匹配,并且所有内存都完全在 DLL 内或完全在应用程序内分配和释放,因此您也不会遇到任何跨 DLL 内存管理问题。在示例中,我在标题中分配和解除分配。如果您需要在 _Internal 函数中分配数据,您还需要添加一个允许您在 DLL 中释放该内存的函数。进入 _Internal 函数后,您可以随意使用 C++。

【讨论】:

  • 非常感谢您的示例代码和响应。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-02
  • 1970-01-01
  • 1970-01-01
  • 2013-04-03
  • 2011-01-15
  • 2012-01-20
  • 1970-01-01
相关资源
最近更新 更多