为具有大量依赖关系的库创建包装器的正确方法

Correct way of creating wrappers for libraries with lots of dependencies

本文关键字:包装 创建 方法 依赖 关系      更新时间:2023-10-16

假设我想在一个有很多依赖关系的大型库中为一个特定函数创建一个c#包装器。为了便于讨论,假设我想为CGAL库中的convex_hull_3函数创建一个C#包装器。顾名思义,此函数为三维空间中的一组点创建凸包。正如您可能已经知道的,CGAL依赖于其他一些库,如BoostMPFRGMP以及其他一些库(它动态链接到这些库)
你将如何创建这样的包装器?包装函数的正确方式是什么,以便最终包装器最大限度地减少暴露/包装的其他库的数量?

我能想到的一个解决方案:

  1. 在C++中创建网关项目。这个网关项目依赖CGAL的DLL,以及CGAL工作所需的其他一切(boost DLL、MPFR的DLL等等)。该项目有两个文件:一个头文件和一个CPP文件。这些文件声明了一个函数(比如说)createConvexHull,它封装了原始的convex_hull_3。此外,createConvexHull函数被标记为__declspec(dllexport)
    这个函数的重点是它改变了CGAL处理的数据的低级表示。例如,我们不必将点云数据声明为CGAL的内部数据类型之一,而只需传递一个表示数据的3D矢量的double数组。因此,这个网关项目避免了包装CGAL内部数据结构的需要,并暴露了最少的额外依赖关系。

  2. 网关项目构建到DLL文件中(例如CGAL gateway.DLL)。

  3. 用C#创建一个项目。让我们将此项目称为包装器项目,因为所有P/Invoke操作都将在此项目内执行。添加CGAL gateway.dll作为此项目的参考,以及Boostdll、MPFRGMP的dll。在这个项目中写下所有P/Invoke的东西。

  4. 构建包装器项目,并将其添加为您希望具有创建凸包线功能的任何其他C#项目的引用。

潜在问题:

首先,我不知道这是否是做这种事情的标准方式。我一生中只为个人/教育目的创建了两个包装器,所以无论我知道什么,都是通过反复尝试和查看他人的代码学到的
我看到这种方法的一个问题是,将有一个额外的C++项目(我指的是网关项目),它相对无用,只能作为抽象层。这种方法的另一个问题是它创建了一个DLL地狱。要把我的C#包装交给其他人,我必须给他们BoostCGALMPFRGMPDLL。可能需要大量的DLL来运行我的包装器。老实说,我甚至不知道是否有办法绕过它。

我考虑的是静态链接而不是动态链接,但这里有两个考虑因素。首先,我以前从未尝试过静态链接。第二,我不认为可以静态链接到GPLv3库。因此,如果我决定将来在开源许可证下发布我的包装器,我将不得不坚持使用GPLv3许可证(这不是我想要的)。

我不知道我是否能够用一种可以理解的方式表达我的问题。如果这个问题需要澄清,请告诉我。

有几种方法可以创建C#本机库的包装器。以下是其中的一些:

1-创建一个混合模式程序集,将本机库中所需的功能封装在可直接在C#中使用的C++/CLR类中。这种方法的缺点是,您的C#项目现在将是特定于CPU架构的(免责声明:此链接指向我根据SO中的一些帖子以及我自己的经验编写的内容)。在您的C++/CLR包装中;机会;将本机C++类型(如std::string)转换为C#可以理解的类型(如System.string)

2-在C++中创建一个COM库,该库也可以直接在C#中使用。如果你可以在系统中全局注册库,那么你的C#应用程序就不需要特定于CPU架构。同样在这种情况下,来自.net框架的COM互操作引擎将为您执行大部分类型封送处理。

3-创建一个常规动态库,其中包含导出的函数,这些函数总结了您需要的功能,可以使用DllImport属性从C#调用,也可以使用Windows API中的LoadLibrary/FreeLibrary/GetProcAddress函数。我相信这就是您在问题中描述的方法,并且您添加了一个额外的C#包装层,我认为最好将其作为示例代码/文档提供。

没有一种方法是对的或错的,在每种方法中,您都可以动态或静态地链接其余的依赖项。

顺便说一句,如果您的依赖项被许可为GPL-v3,我认为您没有办法在遵守GPL-v3许可证的情况下分发这些依赖项。