【发布时间】: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++ 代码只需填充您传递的数组。
-
OP 在他的问题中链接了相同的 URL,解释说它不适用于他的用例......
-
@HansPassant 看来我使用 SizeParamIndex 的方式不正确,所以我将从原始帖子中删除它。但是,您建议的替代方案仍然不能作为 C# 中
data成员的底层内存将不同于 C++ 对象(即仍然是一个副本,因此如果用户在 C# 对象中修改data[0]它不会反映在 C++ 对象的data[0]元素中)。 -
当然,C++ 数组与 C# 数组不同,因此需要复制。如果您想危险地生活,那么这很容易做到,只需在 pinvoke 声明中将返回类型声明为
float*。unsafe关键字是必需的。
标签: c# c++ marshalling