使用 P-Invoke 传递一个大缓冲区

Pass a large buffer using P-Invoke

我需要将包含音频流的缓冲区从 C# 传递到本机 dll。缓冲区驻留在结构中。缓冲区将通过接口而不是通过磁盘路径传递是可取的。我见过这个方法:

// native
struct MyStruct 
{
    short* buffer
}
void Foo(MyStruct *myStruct);

// managed
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=1000)]
    public short[] buffer;
}

[DllImport("My.dll")]
public static extern void Foo(ref MyStruct myStruct);

第一个问题是此代码是否适用于最大 1K shorts 的 short* 缓冲区?

其次,事先不知道大小:我需要在 SizeConst 中设置最大大小(可能是几 MB)吗?

首先,你问题中的两个结构不匹配。 C# 结构将匹配

struct MyStruct
{
    short arr[1000];
};

这就是 ByValArray 的意思——在结构中内联分配的数组。

如果大小是动态的(仅在运行时已知),那么您可能不应该期望让编组器为您处理。您当然不想每次都强制编组到一个恒定大小的缓冲区,因为那样效率很低。事实上,你真的想完全避免复制缓冲区。 p/invoke 编组器对准备编组的对象的大小有上限。

手动固定阵列并传递其地址会更加简洁和高效。并且你还应该传递数组的长度,以便 C++ 代码知道它需要读取多少。

在 C++ 方面:

struct BufferStruct
{
    int len;
    short* arr;
};

void Foo(const BufferStruct buffer);

在 C# 方面:

[StructLayout(LayoutKind.Sequential)]
public struct BufferStruct
{
    public int len;
    public IntPtr arr;
}

[DllImport("My.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Foo(BufferStruct buffer);

然后你像这样调用函数:

short[] arr = ...;
GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned);
try
{
    BufferStruct buffer;
    buffer.len = buffer.Length;
    buffer.arr = gch.AddrOfPinnedObject();
    Foo(buffer);
}
finally
{
    gch.Free();
}

如果您不强制将数组放入结构中,所有这些都会更容易。如果您将长度和数组作为参数传递,那么编组器会为您固定数组并使代码更简单。