为什么我不能将一系列结构从C ++传递给C#

时间:2013-06-06 14:18:27

标签: pinvoke

我无法传递一系列结构,但我可以放弃一个。

我在本网站上看了几个答案,这些答案都是正确的。但它们都不适合我。在所有情况下,答案都将IntPtr作为PInvoke签名中的'out IntPtr someName',我总得到的值为零。如果IntPtr是返回值,我无法将其解析为struct指针数组。

这是我尝试使用IntPtr作为返回码: Unamanged C ++

extern "C"  EXPORTDLL_API sDeviceEndPoint **CallStartDiscovery(ZBTransport *zbTransport,     int *length, int *result)
{
    if(zbTransport != NULL)
    {
        std::list<sDeviceEndPoint *> deviceEndPoints;
        sDeviceEndPoint **devEndPoints;
        zbTransport->StartDiscovery(&deviceEndPoints, result);
        *length = deviceEndPoints.size();
        if(*length > 0)
        {
            devEndPoints = zbTransport->getDiscoveredEndPointPtrs();
            devEndPoints[*length] = devEndPoints[0]; // Test duplicate
            *length = *length + 1;
            return &devEndPoints[0];
        }
    }
    return NULL;
}

C#PInvoke签名:

    [DllImport("PInvokeBridge.dll", CharSet = CharSet.Unicode)]
    public static extern IntPtr CallStartDiscovery(IntPtr pZbTransportObject, ref int length, ref int result);

C#实施

    public Collection<DeviceEndPoint> DiscoverHCSensors()
    {
        Collection<DeviceEndPoint> discoveredZigBeeDevices = new Collection<DeviceEndPoint>();
        int length = 0;
        int result = 0;
        IntPtr arrayValue = CallStartDiscovery(_pZbTransportObject, ref length, ref result);

        if (ErrorCodes.ConvertResultCode(result) == ResultCode.rc_SUCCESS)
        {
            var deviceEndPointSize = Marshal.SizeOf(typeof(DeviceEndPoint));
            for (var i = 0; i < length; i++)
            {
                discoveredZigBeeDevices.Add((DeviceEndPoint)Marshal.PtrToStructure(arrayValue, typeof(DeviceEndPoint)));
                arrayValue = new IntPtr(arrayValue.ToInt32() + deviceEndPointSize);
            }
            return discoveredZigBeeDevices;
        }
        else
        {
            String error = "Error"; // Put something helpful in here
            TransportException transEx = new TransportException(error);
            transEx.Method = "DiscoverHCSensors";
            transEx.ErrorCode = result;
            transEx.TransportType = _transportString;
            throw transEx;
        }
    }

我知道在C#中定义的DeviceEndPoint结构的布局是正确的,因为如果我更改C ++代码只传递一个指向DeviceEndPoint结构的指针,我就会在Collection中正确加载单个结构。

另一种尝试是将值作为out参数传递,因此在C ++代码中我有一个参数sDeviceEndPoint ** devs。

我的C#签名是

    [DllImport("PInvokeBridge.dll", CharSet = CharSet.Unicode)]
    public static extern void CallStartDiscovery(IntPtr pZbTransportObject, out IntPtr devs, ref int length, ref int result);

我也尝试过IntPtr [] devs,'ref'而不是out,它们都以同样的方式失败了。问题出在C#实现

int result = 0;
IntPtr arrayValue = IntPTr.Zero;
CallStartDiscovery(_pZbTransportObject, out arrayValue, ref length, ref result);

arrayValue的值始终为null(0)。所以我在这一点之后所做的事情并不重要。至少使用返回值我得到一个非null值。

据我所知,我正在做其他人正在做的事情。我看不出我做错了什么。否则,这是在本论坛中多次“回答”的问题的重复。

2 个答案:

答案 0 :(得分:2)

大卫有正确的结果,他解释了我的方法不起作用的原因。我得出了相同的结论,但不同地解决了问题。我以为我会提供可读代码(注释不适用于代码)以防其他人使用它。我没有测试David的代码,但我使用了以下代码并且它有效:

IntPtr arrayValue = CallStartDiscovery(_pZbTransportObject, ref length, ref result);
if (result == 0) // If success
{
    if (length > 0 && arrayValue != IntPtr.Zero)
    {
        IntPtr[] devPtrs = new IntPtr[length];
        Marshal.Copy(arrayValue, devPtrs, 0, length); // De-reference once to get array of pointers
        for (var i = 0; i < length; i++)
        {
            // Now marshal the structures
            discoveredZigBeeDevices.Add((DeviceEndPoint)Marshal.PtrToStructure(devPtrs[i], typeof(DeviceEndPoint)));
        }
    }
    return discoveredZigBeeDevices;
}

我使用了返回IntPtr的方法,而不是参数(原帖中的案例1)。

答案 1 :(得分:1)

您缺少一个间接级别。您的本机代码返回一个双指针。但托管代码仅执行单个指针取消引用。添加第二层间接,你会没事的。

而不是

Marshal.PtrToStructure(arrayValue, typeof(DeviceEndPoint)

你需要

Marshal.PtrToStructure(Marshal.ReadIntPtr(arrayValue), typeof(DeviceEndPoint)

而不是

arrayValue = new IntPtr(arrayValue.ToInt32() + deviceEndPointSize);

你需要

arrayValue = new IntPtr(arrayValue.ToInt64() + Marshal.SizeOf(IntPtr));

说实话,这是一个相当复杂的界面。如果我面临创建互操作层,我会使用C ++ / CLI。