将数组从c#(Mono)封送到c++

本文关键字:c++ Mono 数组 | 更新日期: 2023-09-27 18:21:43

我尝试将数组传递到我创建的dll,但成功率非常有限。我在c#中工作,并在c++中执行一些机器学习操作。这里的问题是,我使用的是Mono而不是.Net框架(用于Unity)。我已经关注了多个"操作指南"链接,比如这个和这个。

第一个链接实际上非常有用,我可以将一个数组从c#传递到具有已知边界的c++,而不会被垃圾收集器占用。但是,SAFEARRAY在Mono(仅在.Net)中不受支持

我的c#代码的相关部分是:

[DllImport("Classification.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void DebugStruct([In] IntPtr ptest_struct_with_arrays);
internal struct LabeledDataManaged
{
//[MarshalAs(UnmanagedType.ByValArray,SizeConst = 720)]
public float[] floatArray;
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 45)]
public uint[] uintArray;
}
static unsafe void TestMarshalMono(float[] testArr, uint[] labelsArr)
{
    LabeledDataManaged labledData = new LabeledDataManaged();
    labledData.floatArray = testArr;
    labledData.uintArray = labelsArr;

    int iSizeOfTestStructWithArrays = Marshal.SizeOf(labledData);
    IntPtr pStruct = Marshal.AllocHGlobal(iSizeOfTestStructWithArrays);
    Marshal.StructureToPtr(labledData, pStruct, false);
    DebugStruct(pStruct );
}

我的问题:调试dll时,我在c++端看到垃圾。我也尝试过从IntPtr切换到HandleRef,但我不明白c++方面需要做什么才能正常工作。顺便说一句,任何传递数组而不被GC打乱的方法都是可以的,无论它是否被封装在结构中。

我正在联系所有有这个恼人问题的Stackoverflow天才。

将数组从c#(Mono)封送到c++

我在c++端使用std::vector<float>std::vector<unsigned int>定义了相同的结构。

重要的细节,需要在问题中。但不,这永远不会奏效。C++语言类型没有互操作性,不同编译器实现之间的类实现细节差异太大。或者,在同一个编译器中差异太大。迭代器调试之类的标准辅助会更改C++标准库对象的布局。

Pinvoke互操作基于C语言类型。您的C#声明现在与匹配

typedef struct LabeledData {
    float* floatArray;
    unsigned int* uintArray;
}

毫无疑问,你可以看到这个结构的巨大问题。没有任何像样的方法可以真正使用这些数组。你根本不知道它们有多大。您可以使用UnmanagedType.ByValArray,但等效的C++声明需要看起来像:

typedef struct LabeledData {
    float floatArray[42];
    unsigned int uintArray[666];
}

毫无疑问,你会看到这种结构的大问题,数组长度是固定的。当您思考std::vector<>时那么你肯定不喜欢固定的数组长度。


这就是.NET喜欢COM互操作类型的原因。SafeArray是安全的,因为它还存储数组长度。并且有一种定义良好的方法来分配和销毁它们,这是原始数组的另一个大问题。没有它们,你能做的最好的事情就是:

typedef struct LabeledData {
    size_t floatArraySize;
    float* floatArray;
    size_t uintArraySize;
    unsigned int* uintArray;
}

在C#端匹配:

internal struct LabelData {
    public int floatArraySize;
    public IntPtr floatArray;
    public int uintArraySize;
    public IntPtr uintArray;
}

你需要Marshal.AllocHGlobal()来分配数组,你已经知道如何做到这一点了。并且担心谁以及何时调用Marshal.FreeHGlobal()。由于您可能仍然想保留std::vector,所以您应该复制数组,以便在pinvoke调用后释放内存。


当然,数组数据复制非常非常难看。完全不使用结构,而是使用四个函数来取得成功。如CreateLabeledData、DestroyLabeledData,SetLabeledDataFloats,SetLabeled DataIntegers。以及一个相应的C++类,该类具有实现这些函数的工厂方法。