C 到 C# 数组编组中的矛盾行为

Posted

技术标签:

【中文标题】C 到 C# 数组编组中的矛盾行为【英文标题】:Contradictory behavior in C to C# array marshalling 【发布时间】:2013-02-13 20:14:23 【问题描述】:

我有两个代码几乎相同的示例。我可以从 C# 中检索一个的数据,但我没有正确的另一个数据。他们在这里:

效果很好

C++部分:

__declspec(dllexport) void** enumerateDevices(int *dIsize)
    Array<DeviceInfo> dIArray;
    Framewoek::enumerateDevices(&dIArray);
    *dIsize = dIArray.getSize();
    DeviceInfo dP[255];
    for (int i = 0; i < dIArray.getSize(); i++)
        dP[i] = dIArray[i];
    void* p = dP;
    return &p;

C#部分:

    [DllImport("Wrapper.dll")]
    static extern IntPtr enumerateDevices(out int devicesSize);
    public static DeviceInfo[] EnumerateDevices()
    
        int devicesSize;
        IntPtr arrayPointer = enumerateDevices(out devicesSize);
        IntPtr[] array = new IntPtr[devicesSize];
        Marshal.Copy(arrayPointer, array, 0, devicesSize);
        DeviceInfo[] arrayObjects = new DeviceInfo[devicesSize];
        for (int i = 0; i < devicesSize; i++)
            arrayObjects[i] = new DeviceInfo(array[i]);
        return arrayObjects;
    

没有按预期工作

C++部分:

__declspec(dllexport) void** SensorInfo_getSupportedVideoModes(SensorInfo* si, int *dIsize)
    const Array<VideoMode>& dIArray = si->getSupportedVideoModes();
    *dIsize = dIArray.getSize();
    VideoMode dP[255];
    for (int i = 0; i < dIArray.getSize(); i++)
        dP[i] = dIArray[i];
    void* p = dP;
    return &p;

C#部分:

    [DllImport("Wrapper.dll")]
    static extern IntPtr SensorInfo_getSupportedVideoModes(IntPtr objectHandler, out int arraySize);
    public VideoMode[] getSupportedVideoModes()
    
        int arraySize;
        IntPtr arrayPointer = SensorInfo_getSupportedVideoModes(this.Handle, out arraySize);
        IntPtr[] array = new IntPtr[arraySize];
        Marshal.Copy(arrayPointer, array, 0, arraySize);
        VideoMode[] arrayObjects = new VideoMode[arraySize];
        for (int i = 0; i < arraySize; i++)
            arrayObjects[i] = new VideoMode(array[i]);
        return arrayObjects;
    

如您所见,我只想要对象的指针。但是第二个例子给了我无效的指针。我不知道为什么。这是一个大型库,我正在尝试创建一个 .net 包装器,除此功能外一切正常。

注意:Array 是一个模板类,包含两个字段,大小和数组以及几个方法。


*编辑:我知道它是一个局部变量(甚至 VS 都会给我警告),但是为什么第一个有效呢?!


*编辑2:

我使用@JefferyThomas 的答案解决了第二个问题。它现在看起来像这样,没有问题: C++端:

__declspec(dllexport) void* SensorInfo_getSupportedVideoModes(SensorInfo* si, int *dIsize)
    const Array<VideoMode>& dIArray = si->getSupportedVideoModes();
    *dIsize = dIArray.getSize();
    VideoMode** dP = new VideoMode*[255];
    for (int i = 0; i < dIArray.getSize(); i++)
        dP[i] = const_cast<VideoMode*>(&(dIArray[i]));
    return dP;

__declspec(dllexport) void SensorInfo_destroyVideoModesArray(VideoMode** dP)
    delete[] dP;

目前我在更改我的第一个函数时遇到问题,就像@JefferyThomas 建议的那样。让我们看一下代码,然后我会告诉你问题出在哪里。 C++ 端:

__declspec(dllexport) void** Framework_enumerateDevices(int *dIsize)
    Array<DeviceInfo> dIArray;
    Framework::enumerateDevices(&dIArray);
    *dIsize = dIArray.getSize();
    DeviceInfo* dP = new DeviceInfo[255];
    for (int i = 0; i < dIArray.getSize(); i++)
        dP[i] = dIArray[i];
    void** p = new void*;
    *p = dP;
    return p;

__declspec(dllexport) void Framework_destroyDevicesArray(void **p)
    DeviceInfo *dP = (DeviceInfo *)*p;
    //delete [] dP;
    delete p;

C#端:

    [DllImport("Wrapper.dll")]
    static extern IntPtr Framework_enumerateDevices(out int devicesSize);
    [DllImport("Wrapper.dll")]
    static extern IntPtr Framework_destroyDevicesArray(IntPtr arrayPointer);
    public static DeviceInfo[] EnumerateDevices()
    
        int devicesSize;
        IntPtr arrayPointer = Framework_enumerateDevices(out devicesSize);
        IntPtr[] array = new IntPtr[devicesSize];
        Marshal.Copy(arrayPointer, array, 0, devicesSize);
        DeviceInfo[] arrayObjects = new DeviceInfo[devicesSize];
        for (int i = 0; i < devicesSize; i++)
            arrayObjects[i] = new DeviceInfo(array[i]);
        Framework_destroyDevicesArray(arrayPointer);
        return arrayObjects;
    

还有问题:

    我无法在销毁函数中保留“删除 [] dP”。它不仅删除数组,还删除数据。 我只能正确地拥有第一个指针(数组的第一项)。其他指针均不正确。我不知道为什么。尝试将 DeviceInfo 直接更改为指针数组而不是对象数组(如第二个函数),但仍然没有成功。

目前我认为是因为 Array 类处理和销毁所有东西。


编辑 3:我自己找到了第一个问题的解决方案。感谢 Jeff 为我指明了正确的方向。

【问题讨论】:

除非 DeviceInfo dP[255] 被定义为 static,否则您将丢失指针。这不适用于任何语言... 你返回的是本地地址!!!从来都不是一件好事! 您是否在编辑中提出新问题? @DavidHeffernan,这是关于第一个问题。我告诉我没有使用你的建议。如果这是你让我失望的原因,我认为你最好在这样做之前阅读整个问题。 我没有提出任何建议。我只是想理解这个问题。我这是你的编辑是不公平的。它似乎提出了一个新问题。 【参考方案1】:

这里的问题是您正在返回一个堆栈变量。这是个坏主意。

VideoMode dP[255];DeviceInfo dP[255]; 在函数返回时都被销毁。正好第一种情况内存没有被覆盖,第二种情况你就没那么幸运了。

过去我从堆中分配并提供了第二个 releaseXXXX 方法来释放该内存。

__declspec(dllexport) void** enumerateDevices(int *dIsize)
    Array<DeviceInfo> dIArray;
    Framewoek::enumerateDevices(&dIArray);
    *dIsize = dIArray.getSize();
    DeviceInfo *dP = new DeviceInfo[255];
    for (int i = 0; i < dIArray.getSize(); i++)
        dP[i] = dIArray[i];
    void** p = new void*;
    *p = dP;
    return p;


__declspec(dllexport) void releaseEnumerateDevices(void **p)
    DeviceInfo *dP = (DeviceInfo *)*p;
    delete [] dP;
    delete p;

C#

    [DllImport("Wrapper.dll")]
    static extern IntPtr enumerateDevices(out int devicesSize);
    static extern void releaseEnumerateDevices(IntPtr arrayPointer);
    public static DeviceInfo[] EnumerateDevices()
    
        int devicesSize;
        IntPtr arrayPointer = enumerateDevices(out devicesSize);
        IntPtr[] array = new IntPtr[devicesSize];
        Marshal.Copy(arrayPointer, array, 0, devicesSize);
        DeviceInfo[] arrayObjects = new DeviceInfo[devicesSize];
        for (int i = 0; i < devicesSize; i++)
            arrayObjects[i] = new DeviceInfo(array[i]);
        releaseEnumerateDevices(arrayPointer);
        return arrayObjects;
    

【讨论】:

当 pinvoke marshaller 用自己的堆栈帧删除数组时,运气就用完了。 感谢杰夫,您的建议解决了我的第二个函数的问题,但破坏了我的第一个函数。检查第一篇文章的 edit2 部分。 @SoroushFalahati 看起来您需要更复杂的内存管理。我假设 C# DeviceInfo 从 C++ DeviceInfo 复制了所需的数据。如果 C# DeviceInfo 保留 C++ DeviceInfo 指针的副本,那么您将需要一种方法来分别保留和释放每个 C++ 指针。这在 C# 中并不容易。

以上是关于C 到 C# 数组编组中的矛盾行为的主要内容,如果未能解决你的问题,请参考以下文章

将安全数组的安全数组从 C++ 中的 VARIANT 编组到 C#

将浮点数组编组到 C#

如何将 C 指针编组到 C# 结构数组

Marshal Union(C) 与包含 C# 中的数组的结构

将固定大小的数组编组为 C# 类的成员不起作用

C# 中的 C++ int & long 编组