【问题标题】:Pass a large buffer using P-Invoke使用 P-Invoke 传递一个大缓冲区
【发布时间】:2015-07-09 12:05:30
【问题描述】:

我需要将包含音频流的缓冲区从 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 短裤的 short* 缓冲区是否正确?

其次,size是预先未知的:我需要在SizeConst中设置最大大小(可能是几个MB)吗?

【问题讨论】:

    标签: c# interop pinvoke


    【解决方案1】:

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

    struct MyStruct
    {
        short arr[1000];
    };
    

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

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

    手动固定数组并传递其地址会更加清洁和高效。您还应该传递数组的长度,以便 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();
    }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-14
      • 1970-01-01
      • 2010-10-11
      • 2013-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多