c#调用C++库

c#调用C++库

声明导出的C++方法

[DllImport("c++库名", CallingConvention = CallingConvention.StdCall)]
private static extern void method_exsample([MarshalAs(UnmanagedType.LPStr)] string url, [MarshalAs(UnmanagedType.LPStr)] string str, IntPtr context, IntPtr bytes, int length);
  1. extern关键字

    修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 DllImport 属性一起使用;在这种情况下,该方法还必须声明为 static

  2. DllImport特性

    ​ [DllImport(“c++库名”, CallingConvention = CallingConvention.StdCall)]

    ​ CallingConvention 调用约定,决定了参数入栈的顺序,从左往右还是从右往左,出栈方式之类的。根据C++导出的设置来处理

  3. MarshalAs特性

    ​ [MarshalAs(UnmanagedType.LPStr)]

    ​ 在C++中 字符串或者某类型数组是char* 或者某类型的指针

    ​ 这个特性可以帮忙将指针转为C#类型 C++ char*->C# string

    ​ 如果不用这个特性,参数类型就写IntPtr,用到的时候Marshal.PtrToStringAnsi(ptr)可以转string,也是差不多的效果。

向C++传递C#对象

​ C#对象属于托管堆,有垃圾回收机制管理内存。C++的内存是非托管的,当C++使用C#对象时,有可能这个对象被GC回收了。为了避免在C++使用期间被回收,需要使用GCHandle将它“钉住”,让GC暂时不要回收。用完也要记得解除这个钉子。

GCHandle gcHandler = GCHandle.Alloc(C#对象, GCHandleType.Pinned);

gcHandler.Free();//C++结束使用后,释放它

除了基础数据类型,其他C#类型对象传递到C++,我们都以IntPtr的形式,这两种方式都可以获得IntPtr。

GCHandler.ToIntPtr(gcHandler);

gcHandler.AddrOfPinnedObject();

C++回调C#方法

C++有函数指针,对C#来说就是委托。

我们要将C#方法注册到C++,就定义一个跟C++函数指针相同的委托

C++注册C#函数的方法

[DllImport("c++库名", CallingConvention = CallingConvention.StdCall)]
private static extern void cpp_callback_register(csharp_delegate cb, Intptr context);

C#定义委托

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int csharp_delegate (IntPtr cpp_ptr, int size, IntPtr context);

C#被回调的方法

标记MonoPInvokeCallback特性,这个是Unity用到的

[AOT.MonoPInvokeCallback(typeof(csharp_delegate))]
static void CSharp_Callback_Method(IntPtr cpp_ptr,int size, IntPtr context)
{
    if(cpp_ptr != Intptr.Zero)//如果C++指针不为空
    {
        //...
    }
}

IntPtr转回C#对象

如果将C#对象作为C++回调的上下文,我们将C#对象转为IntPtr传入C++,回调的时候也是IntPtr

GCHandle.FromIntPtr(ptr).Target as C#对象

还有一些用法,根据具体使用的时候查阅文档就可了。

Marshal.Copy(IntPtr.Add(ptr, recvSize), bytes, 0, copySize);

GCHandle.ToIntPtr vs. GCHandle.AddrOfPinnedObject

–2022.7.5
最近又开始写一点C++的东西,在写到函数指针传递上下文的时候又一次踩进同一个坑,一定要记录下来

我在C++回调到C#的方法中,会传递一个上下文是ojbect类型。我用AddrOfPinnedObject获取IntPtr,在回调时用GCHandle.FromIntPtr,却在一开始AddrOfPinnedObject就报错了。

具体原因可以看这个文章

里面提到这两者都可以获得IntPtr,但是使用上有些不同。
AddrOfPinnedObject,要求类型有严格的布局。
如果你使用GCHandle.ToIntPtr,传入的GCHandler在Alloc的时候,使用了GCHandleType.Pinned,也会报错的。

 		object t = new object();
        GCHandle handler = GCHandle.Alloc(t, GCHandleType.Pinned);
        var ptr = GCHandle.ToIntPtr(handler);

上面这种写法,ptr无论是用ToIntPtr还是AddrOfPinnedObject,都会报错。
报错内容是:System.ArgumentException:“Object contains non-primitive or non-blittable data. ”
就是说,非基础类型或者非blittable类型,就无法使用Pinned的方式了。
什么是blittable
通过上面的文章,可以发现object,class都不是blittable类型。

所以修改一下,Alloc的时候不要时候用GCHandleType.Pinned,然后直接ToIntPtr

 		object t = new object();
        GCHandle handler = GCHandle.Alloc(t);
        var ptr = GCHandle.ToIntPtr(handler);

microsoft文档中也提到:The only thing you can do with this IntPtr is pass it back to GCHandle.FromIntPtr to get back the GC handle.
这种方式获取的IntPtr,后续能做的就只有通过FromIntPtr再取出GCHandle。
显然这很符合我处理上下文的需求。

这两个文章会更好理解GCHandleType的作用
GCHandle: when to use GCHandleType.Normal explicitly
GCHandle:什么时候显式使用GCHandleType.Normal?上面的问题的中文翻译

你可能感兴趣的:(c#,c#,c++,开发语言)