将 C++ 浮点数组成员直接编组到 c# 而无需复制
Marshalling a C++ float array member to c# directly without copying
我有一个矩阵 class,其中包含我想直接在 C# 包装器中公开的固定大小的浮点数组 class:
class Matrix
{
public:
float data[16];
...
};
通常,在 C# 中包装此结构时,我会将包装类型定义为 POD 结构,该结构将利用 [StructLayout(LayoutKind.Sequential)]
并允许我直接编组 class 与 C++/C# 之间的编组.但是,c++ class 公开了其他类型(通过成员或函数),这些类型不能以这种方式编组,所以我不得不使用 PIMPL 机制,其中我的 C# class 包含一个不透明的指针(IntPtr ) 到底层 C++ 结构。但是,我仍然想直接 将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.
感谢您的帮助。
除非 .NET 分配内存,否则您无法获得 float[]
。 StructLayout(Sequential)
也是如此——float[16]
等固定大小的缓冲区与 float[]
.
不兼容
不过,如果您比 float[]
更想要方便和高效,您可以做的是使用新的零拷贝缓冲类型。您可以从指向第一个元素的指针加上元素的数量构造一个 System.Span<float>
,并且下标该跨度将使您可以直接访问内存。需要进行一些重构,但由于 Span<T>
可以同样很好地访问 .NET 数组,一旦您重写代码以使用 Span
,它就可以存在于两个世界中(C# 和 C++ 分配的数据) .
我有一个矩阵 class,其中包含我想直接在 C# 包装器中公开的固定大小的浮点数组 class:
class Matrix
{
public:
float data[16];
...
};
通常,在 C# 中包装此结构时,我会将包装类型定义为 POD 结构,该结构将利用 [StructLayout(LayoutKind.Sequential)]
并允许我直接编组 class 与 C++/C# 之间的编组.但是,c++ class 公开了其他类型(通过成员或函数),这些类型不能以这种方式编组,所以我不得不使用 PIMPL 机制,其中我的 C# class 包含一个不透明的指针(IntPtr ) 到底层 C++ 结构。但是,我仍然想直接 将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.
感谢您的帮助。
除非 .NET 分配内存,否则您无法获得 float[]
。 StructLayout(Sequential)
也是如此——float[16]
等固定大小的缓冲区与 float[]
.
不过,如果您比 float[]
更想要方便和高效,您可以做的是使用新的零拷贝缓冲类型。您可以从指向第一个元素的指针加上元素的数量构造一个 System.Span<float>
,并且下标该跨度将使您可以直接访问内存。需要进行一些重构,但由于 Span<T>
可以同样很好地访问 .NET 数组,一旦您重写代码以使用 Span
,它就可以存在于两个世界中(C# 和 C++ 分配的数据) .