使用 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();
}
如果您不强制将数组放入结构中,所有这些都会更容易。如果您将长度和数组作为参数传递,那么编组器会为您固定数组并使代码更简单。
我需要将包含音频流的缓冲区从 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();
}
如果您不强制将数组放入结构中,所有这些都会更容易。如果您将长度和数组作为参数传递,那么编组器会为您固定数组并使代码更简单。