将数据从非托管代码(C)传递到托管代码(C#)

5
我有一个应用程序,它是用C#编写的,并使用C编写的DLL。通过一些委托(函数指针),我已经成功调用了一个C函数。这个函数预计会对一些数据进行大量处理,然后将处理后的二进制数据与数据大小一起返回给C#代码。
托管c#函数的原型为: private unsafe delegate void MyCallback (IntPtr cs_buf, Int32 cs_size); 我正在从我的C代码中调用它:
void* c_buf = NULL;
int c_size = 0;

.... some processing here to fill buf and size......

MyCallback (c_buf, c_size);

在托管的C#代码中,我需要从MyCallback调用一个原型为void foo (byte[] cs_buf, int cs_size)的函数。
现在cs_size值没有问题,但是从C代码传递二进制缓冲区到C#代码并将其用作byte[]的正确方式是什么?
如果我的操作是正确的,那么将接收到的IntPtr cs_buf转换为byte[]的推荐方法是什么?
谢谢, Vikram
2个回答

4
这就是微软开发C++/CLI(也称托管C++)的原因。你可以编写如下的函数:

void call_foo(array<Byte>^ bytes)
{
    pin_ptr<Byte> ptrBuffer = &bytes[bytes->GetLowerBound(0)];
    foo(ptrBuffer, bytes->Length);
}

这将使用C#字节数组,锁定其内存,然后将其作为C样式的字节数组传递给foo,并带有它的长度。不需要进行清理或释放任何内容。当pinned指针在call_foo结束时超出范围时,它将自动取消锁定。干净简单。


3

您应该使用Marshal.Copy方法。

Marshal.Copy Method (IntPtr, Byte[], Int32, Int32)

将数据从非托管内存指针复制到托管的8位无符号整数数组。

假设cs_size是以字节为单位的大小:

var array = new byte[cs_size];
Marshal.Copy(pointer, array, 0, cs_size);
foo(array, cs_size);

顺便提一下,在这种情况下,foo() 不需要传递一个 cs_size 参数,因为它可以使用数组的 .Length 属性。
此外,由于 C# 代码会复制数组,您可以在调用回调后使用 free() 释放 C 代码中的缓冲区。
否则,您将需要从 C 导出一个 mylib_free() 方法,或者使用已知的内存分配器(而不是 malloc),例如 Windows 下的 LocalAlloc(来自 C)和 Marshal.FreeHGlobal(来自 C#)。

如果您的项目中没有托管 C++ 代码,且只有这一个调用,那么这是最佳解决方案。 - Ed Bayiates
+1。这个方法可行。谢谢 :). 不过,我在想,这样是不是会增加额外的复制开销呢? - Vikram.exe
它确实可以,但如果您没有任何托管的C++,并且您没有很多这样的调用,并且性能不是非常重要,那么它仍然是最佳解决方案。我的答案可用于避免额外的复制,但您必须创建一个C++项目(可以编译为dot-net程序集)。 - Ed Bayiates
它可以这样做,你也可以使用不安全代码或Marshal.Copy + BitConverter方法之一直接访问值而无需复制。 - Julien Roncaglia
1
即使在托管C++中,您无法将指针转换为托管(byte[])数组而不进行复制。但是,您可以实现一个IList<T>或IEnumerable<T>,它包装了指向非托管数组的指针,并在大多数地方使用它。因此,在您的情况下,没有复制是无法做到的,除非更改foo的原型以接受参数为其他类型而不是数组。 - Julien Roncaglia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接