1

我无法传递一个结构数组,但我可以传递一个。

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

这是我使用 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 结构的指针,我会正确加载集合中的单个结构。

另一种尝试是将值作为输出参数传递,因此在 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 的值始终为空 (0)。所以我在这一点之后所做的事情是无关紧要的。至少使用返回值我得到一个非空值。

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

4

2 回答 2

2

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

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)。

于 2013-06-07T00:55:41.580 回答
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。

于 2013-06-06T22:15:30.620 回答