如何为非托管 c dll 创建 c++\cli 包装器

Posted

技术标签:

【中文标题】如何为非托管 c dll 创建 c++\\cli 包装器【英文标题】:how to create a c++\cli wrapper for unmanaged c dll如何为非托管 c dll 创建 c++\cli 包装器 【发布时间】:2014-04-11 13:35:10 【问题描述】:

我可能自己都搞糊涂了,但以前没有这样做过,一些指导会很有帮助。 我正在尝试从C# 应用程序中调用一些C 代码。我尝试过使用PInvoke,但发现它有点棘手。我想我会尝试做一个C++\CLI 包装器。

有一些复杂的结构,它们有可变长度的双数组,PInvoke 很难处理。 我已经阅读了一些关于这是如何完成的,但我无法弄清楚。我发现的大部分内容都与包装C++ 而不是C 有关。 C 代码已经在导出它的函数,这些函数已经在 J​​ava 应用程序及其 JNA 服务中工作。我有C 代码、标头、库和 dll,但我宁愿不对现有的任何内容进行更改,以免打扰其他消费应用程序。调用它的C# 应用程序将是 64 位的,大多数示例都是创建 win32 库,这有关系吗?

更新:在下面添加代码: 注意:这只是几个功能之一,可能是最简单的一个,但它们都非常相似。

C HEADER:
typedef struct myStruct_t

    double prefix[8];
    int length;
    double array[1];

myStruct;

C:
extern "C" __declspec( dllexport ) myStruct *doSomething(const myStruct *input, double a)

    myStruct *output;
    //doSomething
    return output;

【问题讨论】:

Win32 是 32 位和 64 位版本的 Windows 中 Windows API 的通用名称。如果 C# 应用程序是 64 位的,那么您使用的 DLL 必须是 64 位的。是吗? c dll是64位的,c++\cli wrapper dll的win32位让我觉得是32位 【参考方案1】:

封装 C 和 C++ 之间的区别真的很小。您需要创建一个 C++/CLI 类库。然后,您在封装本机代码的托管 C++ 引用类中编写函数。

例如,假设 DLL 导出这个函数:

int sqr(int x)

然后在你的类库中包含头文件:

#include <mynativelibrary.h>

您还需要将导入库提供给链接器。

然后你就可以公开这个函数了。最简单的方法是将函数包装为 ref 类的静态方法。例如:

public ref class Class1

public:
    static int sqr(int x)
    
        return ::sqr(x);
    
;

然后,您可以像使用任何其他程序集一样在 C# 代码中使用此程序集。

【讨论】:

谢谢我试试看。如果 c 函数接受一个结构并返回一个结构怎么办。这在 c++ 包装器中是如何处理的? 函数本身没有在头文件中定义。只有结构和宏等有区别吗? 显然你需要适应和泛化更复杂的类型。将非托管结构调整为托管 ref 结构。等等。我不相信您没有在任何地方声明导出的函数。我有点担心你似乎在要求我们在这里教你 C++/CLI。 我浏览了整个 c 项目,唯一找到该函数的地方是 c 文件中。我确实说过我以前从未这样做过:) 没关系。然后将 .c 文件添加到 C++/CLI 项目并将其编译到 C++/CLI 程序集中。这样您就不需要单独的非托管 DLL。在 Stack Overflow 问题中,我认为我们无法合理地将您从完全新手转变为成品。很抱歉,这对您没有帮助。【参考方案2】:

我在 VisualStudio 2012 中创建了一些项目,这些项目使用托管代码包装了旧的 MFC dll。我是这样做的:

    在 CLR 中创建一个类库。 将旧项目链接到这个新项目 在新项目中为函数和结构创建包装器并调用旧代码。 在 C# 代码中使用新对象。

请不要忘记为托管 C++ 代码创建单元测试。 (我总是忘记...:))

祝你好运。

【讨论】:

你能不能!详细说明这一点,我是 Visual C++ 的新手,也面临同样的问题 @Ka7lm1011 请看这个:c-sharpcorner.com/UploadFile/SamTomato/…【参考方案3】:

如果Java可以通过jna调用你的C代码,那么C#通过PInvoke应该没有问题。虽然 C++ 互操作(使用 C++/Cli)是一种 PInvoke(隐式 PInvoke),但使用 DllImport 是显式 PInvoke。

当您不需要指定函数参数的编组方式或在显式调用 DllImportAttribute 时可以指定的任何其他详细信息时,隐式 PInvoke 很有用,但您需要创建一个额外的 C++/CLI Dll。

在这两种方式中,您都必须处理将原生数据类型编组到管理的数据类型,这是不可避免且痛苦的。

在 C# 中,结构体可以声明为:

[StructLayout(LayoutKind.Sequential)]
    public struct myStruct 
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        double prefix[] intersects;

        public int length;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public double[] array;
    

但是对于函数,DLLImport 无法处理这种情况,因为 C# 无法删除函数返回的非托管指针的内存,您可以在 C 中创建另一个 wrap 函数,使其使用 out 参数返回结果,在本例中,C# 代码为:

[DllImport("...")]
    public static extern void doSomething([In, Out] myStruct[] results,  myStruct[] input,  int len);

或者你可以使用 C++/CLI 互操作,因为它可以处理原生类型和管理类型,所以调用顺序是:

    C# 代码使用托管数据类型调用此 C++/CLI 函数:

    ManagedmyStruct[] doSomething(ManagedmyStruct[] input, double a)

    在C++/CLI函数domSomething中,调用native函数,步骤如下:

    ManagedmyStruct[] doSomething( ManagedmyStruct[] 输入,双 a)

      //convert the ManagedmyStruct[] input to native type myStruct* input
      myStruct ret* =  doSomething(input, a);
      //convert ret to managed type ManagedmyStruct[] rets
      return rets;
    

【讨论】:

上面已经加了代码,主要是想知道如何处理包含变长数组的struct,size为1的会变长 长度为 1 的数组作为包含名称长度为 int var 的 struct 的最终成员非常清楚地表明了 C struct hack。因此,没有多少 pinvoke struct marshalling 可以完成这项工作。您需要手动 IntPtr 编组。

以上是关于如何为非托管 c dll 创建 c++\cli 包装器的主要内容,如果未能解决你的问题,请参考以下文章

非托管C++通过C++/CLI包装调用C# DLL

在Visual Studio 2010中将Native / C ++ DLL链接到托管C ++ / CLI包装器

如何使我的托管 NuGet 包支持 C++/CLI 项目?

将非托管 C++ dll 添加到托管 C++ dll

在托管 DLL 中加载 C++ DLL 时出现 EEFileLoadException

使用包含托管类的 /CLR 标志构建的本机 dll