直接将 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#,无需复制的主要内容,如果未能解决你的问题,请参考以下文章
将安全数组的安全数组从 C++ 中的 VARIANT 编组到 C#