c++封装dll到静态lib

C++ wrapper DLLs to static LIBs

本文关键字:静态 lib dll 封装 c++      更新时间:2023-10-16

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

理想情况下,我希望有一套动态链接库(dll)来包装静态库套件中的每个库;我读过/听说在Windows(例如,Visual Studio 2005/2008/2010)上这样做的方法是"创建一个包装器DLL",其中包含一些调用底层静态库函数的暴露函数。如果有人能给我一些详细的步骤,包括可能的一些片段,我将非常感激,如何在MS Visual Studio 2005/2008/2010中做到这一点。我相信你们中的一些人可能已经在每天这样做了;非常感谢您的经验。

编辑:

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

"将一个库转换为另一个库类型" 似乎很容易,但事实并非如此。没有直接的一步一步的方法来做到这一点,因为c++和DLL不能很好地一起工作,你的代码需要调整以支持DLL接口。

描述这个问题的一个简明的方法是:

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

因此,DLL的接口根本不支持c++,你需要聪明的使它工作-这就是为什么存在模棱两可的答案。

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

另一个解决方案是为你的c++库创建一个dll安全的C接口。这意味着基本上要创建一个winapi风格的接口,这可能也是不实际的,或者根本不符合使用库的目的。这是@David Heffernan的建议。但是他没有提到的是你必须如何修改你的代码来与dll兼容。

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

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

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

作为评论添加到十四的回复中有点大了…

如果您想在使用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++。

如果您根本不关心接口适配,那么您可以相当容易地将符号从静态.lib导出到.dll。诀窍是,你根本不使用Visual Studio GUI或项目,而只是使用链接器(link.exe)。

使用这个方法,C符号仍然是C符号,c++符号仍然是c++符号。如果你需要改变这一点,你需要编写包装代码(例如外部C接口)。此方法简单地将.lib中的. obbs中的现有符号表示为来自DLL的正式导出。

假设我们有一个从源代码编译的.lib。TestLib.c

#include <stdio.h>

void print(char* str)
{
    printf("%sn", str);
}
int add(int a, int b)
{
    return a + b;
}

我们将其编译成一个静态库TestLib.lib。现在我们希望转换TestLib。lib到TestLibDll.dll(基本名称不应该相同,否则你会得到链接输出的问题,因为链接器也创建DLL link .lib)。为此,我们在Visual Studio GUI外部使用link.exe。启动Visual Studio xx的"x64 Native Tools"命令提示符以获得一个CMD与工具链在路径。(如果需要32位版本,请使用x86 Native Tools代替)。切换到带有TestLib的文件夹。lib(例如x64Release)。然后运行:

link /DLL /EXPORT:add /EXPORT:print /OUT:TestLibDll.dll TestLib.lib

这将生成TestLibDll.dll。(链接器可能会抱怨没有.obj,但您可以忽略这一点。)导出为:

dumpbin /exports TestLibDll.dll
Microsoft (R) COFF/PE Dumper Version 14.29.30040.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file TestLibDll.dll
File Type: DLL
  Section contains the following exports for TestLibDll.dll
    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names
    ordinal hint RVA      name
          1    0 00001080 add
          2    1 00001070 print

我们已经成功导出了函数。

在有许多函数的情况下,使用/EXPORT很繁琐。而是创建一个.def文件。对于我们的例子,下面是TestLibDll.def:

LIBRARY TestLibDll
EXPORTS
    print  @1
    add    @2

链接器然后作为

运行
link /DLL /DEF:TestLibDll.def /OUT:TestLibDll.dll TestLib.lib

这个例子使用了x64 C符号,这使得它很简单。如果您有c++符号,则需要在/EXPORT参数或def file中提供该符号的修改版本。对于比单个静态库更复杂的情况,您可能需要在命令行上提供更多的链接库和/或/LIBPATH参数来指向链接库文件夹。

同样,此方法仅用于从静态库逐字导出符号。我个人使用它来创建一个DLL,以便在Python中加载一个闭源静态库的ctypes。这样做的好处是你根本不需要编写任何包装器代码或创建任何额外的VS项目。

注意:被接受的答案提供了一个关于c++ DLL接口的陷阱的很好的讨论,以及为什么C包装是一个好主意。我在这里不关注这个,只关注将符号导出到DLL的机制。如果可能的话,使用C接口访问DLL仍然是一个好建议。