【问题标题】:Marshalling a C++ float array member to c# directly without copying直接将 C++ 浮点数组成员编组到 C#,无需复制
【发布时间】: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


【解决方案1】:

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

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

【讨论】:

  • 很有趣,这似乎正是我所需要的!可悲的是,我不能使用 C# 7+,但如果没有人来很长时间并发布更通用的解决方案,我会将这个答案标记为已接受。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
相关资源
最近更新 更多