直接将 C++ 浮点数组成员编组到 C#,无需复制

Posted

技术标签:

【中文标题】直接将 C++ 浮点数组成员编组到 C#,无需复制【英文标题】:Marshalling a C++ float array member to c# directly without copying 【发布时间】:2018-09-19 21:06:02 【问题描述】:

我有一个矩阵类,它包含一个固定大小的浮点数组,我想直接在 C# 包装类中公开它:

class Matrix

public:
    float data[16];
    ...
;

通常,当在 C# 中包装此结构时,我会将包装类型定义为使用 [StructLayout(LayoutKind.Sequential)] 的 POD 结构,并允许我直接将类编组到 C++/C#。但是,c++ 类公开了无法以这种方式编组的其他类型(通过成员或函数),因此我不得不使用 PIMPL 机制,其中我的 C# 类包含指向底层 C++ 结构的不透明指针 (IntPtr)。但是,我仍然希望直接将 c++ 结构的底层 data 成员公开给 C# 对象。直接,我的意思是在 C# 中对 data 元素的修改将直接反映在底层 C++ 结构中(即 C# 成员不能是 C++ 成员的副本)。我的想法是像这样在 C# 中公开data 成员:

public class Matrix

    protected HandleRef cptr; // opaque reference to equivalent C++ instance
    public float [] data
    
        get
        
            return Get_data(cptr);
        
    

    [DllImport(MY_CPP_DLL, EntryPoint = "CSharp_Get_data", CharSet = CHARSET)]
    [return: MarshalAs(UnmanagedType.LPArray, SizeConst = 16, ArraySubType = UnmanagedType.R4)]
    private static extern float [] Get_Data(HandleRef in_object);
;

我的 C++ dll 包含函数 CSharp_Get_data,如下所示:

DLLEXPORT float * STDCALL CSharp_Get_data(Matrix *obj)

    return obj->data;

这在 C++ 和 C# 中都可以正常编译,但是当我尝试在 c# 中执行类似的操作时:

Matrix asdf = new Matrix();
asdf.data[0] = 2.0f;

我收到一个 System.Runtime.InteropServices.MarshalDirectiveException,它声明 Additional information: Cannot marshal 'return value': Invalid managed/unmanaged type combination.

有没有办法可以使用这种方法但不会遇到此错误?我知道这是一个非常奇怪的用例,但我觉得这种方法似乎没有违反 MS 在此处描述的任何编组标准:https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-arrays。

感谢您的帮助。

【问题讨论】:

这段代码几乎不可能在 C++ 程序中正确使用,当你 pinvoke 时它并没有变得更好。数组非常原始,您可以获得指向它的指针,但您不知道它包含多少元素。 SizeParamIndex = 0 没有意义,参数 0 不返回大小。 pinvoke marshaller 被那个卡住了。当它试图释放数组的内存时,修复它会导致崩溃。考虑 int CSharp_Get_data(const Matrix* obj, float* data, size_t maxdata),现在 C++ 代码只需填充您传递的数组。 docs.microsoft.com/en-us/dotnet/framework/interop/… OP 在他的问题中链接了相同的 URL,解释说它不适用于他的用例...... @HansPassant 看来我使用 SizeParamIndex 的方式不正确,所以我将从原始帖子中删除它。但是,您建议的替代方案仍然不能作为 C# 中 data 成员的底层内存将不同于 C++ 对象(即仍然是一个副本,因此如果用户在 C# 对象中修改 data[0] 它不会反映在 C++ 对象的 data[0] 元素中)。 当然,C++ 数组与 C# 数组不同,因此需要复制。如果您想危险地生活,那么这很容易做到,只需在 pinvoke 声明中将返回类型声明为float*unsafe 关键字是必需的。 【参考方案1】:

除非 .NET 分配内存,否则您无法获得 float[]StructLayout(Sequential) 也是如此——float[16] 等固定大小的缓冲区与 float[] 不兼容。

不过,如果您比float[] 更需要便利和效率,您可以做的是使用新的零拷贝缓冲类型。您可以从指向第一个元素的指针加上元素的数量构造一个System.Span<float>,并且下标该跨度将使您可以直接访问内存。需要进行一些重构,但由于 Span<T> 可以同样良好地访问 .NET 数组,因此一旦您重写代码以使用 Span,它就可以同时存在于两个世界中(C# 和 C++ 分配的数据)。

【讨论】:

很有趣,这似乎正是我所需要的!可悲的是,我不能使用 C# 7+,但如果没有人来很长时间并发布更通用的解决方案,我会将这个答案标记为已接受。

以上是关于直接将 C++ 浮点数组成员编组到 C#,无需复制的主要内容,如果未能解决你的问题,请参考以下文章

将具有 int* 成员的 C++ 结构编组到 C#

将安全数组的安全数组从 C++ 中的 VARIANT 编组到 C#

如何将指向 char[256] 数组的指针从 C++ 编组到 C#

C++ <--> C# 修改编组的字节数组

将固定大小的数组编组为 C# 类的成员不起作用

如何通过 IntPtr 将 c++ 数组编组为 c#