Marshalled struct在C#中导致OutOfBoundsException

时间:2017-06-26 11:16:34

标签: c# exception struct marshalling outofrangeexception

所以我使用一个简单的DatagramSocket创建了一个我想发送的结构。

结构代码如下:

 public struct MsgData
{
    private readonly int _value;
    private readonly string _descr;
    public MsgData(string desc, int value)
    {
        _descr = desc;
        _value = value;
    }

    public int GetValue()
    {
        return _value;
    }

    public string GetDescr()
    {
        return _descr;
    }
}

我继续转换为像这样的字节数组:

 public static byte[] GetBytes(MsgData message)
    {
        var size = Marshal.SizeOf(message);
        var data = new byte[size];

        System.IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(message, ptr, true);
        Marshal.Copy(ptr, data, 0, size);
        Marshal.FreeHGlobal(ptr);

        return data;
    }

并将其返回到MsgData结构,如下所示:

public static MsgData GetMessage(byte[] bytes)
    {
        var data = new MsgData();

        var size = Marshal.SizeOf(data);
        var ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(bytes, 0, ptr, size);

        data = Marshal.PtrToStructure<MsgData>(ptr);
        Marshal.FreeHGlobal(ptr);

        return data;
    }

但是我得到了:

System.ArgumentOutOfRangeException: 'Requested range extends past the end of the array.'

尝试在线转换时:

Marshal.Copy(bytes, 0, ptr, size);

我现在要使用简单的Serialization instad,但我想知道为什么这不能按预期工作?

1 个答案:

答案 0 :(得分:2)

您的代码存在一些问题,因此请让我解释一下您需要做些什么才能解决问题。

首先,您的结构具有针对快速内存访问进行了优化的布局。当将其编组为字节数组时,默认情况下,您将复制该内存布局。

您的Marshal.SizeOf(...)来电反映了这一点。它总是返回16。怎么可能?你的字符串怎么能被编组到这16个字节内的东西,即使字符串远远超过16?

答案是它没有。相反,您将指向字符串对象的指针编组为字节。

16个字节是int的8个字节(4个用于int + 4用于填充以对齐8字节内存地址边界上的下一个值,我正在运行64位),然后8个字节用于字符串参考(地址)。

那么需要做什么?你需要用一些属性来装饰你的结构,告诉编组引擎如何处理它:

[StructLayout(LayoutKind.Sequential, Pack=0)]
public struct MsgData
{
    [MarshalAs(UnmanagedType.I4)]
    private readonly int _value;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
    private readonly string _descr;

    ... rest as you have it

完成此操作后,您将从Marshal.SizeOf(...)获得此尺寸:68。

68 = int为4字节,字符串为64。

请注意,在编组时,动态调整大小的结果并不是那么容易处理,因此设置字符串的上限是目前的方法。

但是,对于您的问题有一个更简单的解决方案,使用.NET中内置的二进制序列化。

以下是Get*方法的两个新版本,不需要您进行编组:

public static byte[] GetBytes(MsgData message)
{
    using (var stream = new MemoryStream())
    {
        new BinaryFormatter().Serialize(stream, message);
        return stream.ToArray();
    }
}

public static MsgData GetMessage(byte[] bytes)
{
    using (var stream = new MemoryStream(bytes))
    {
        return (MsgData)new BinaryFormatter().Deserialize(stream);
    }
}

请注意,您需要将SerializableAttribute应用于您的结构:

[Serializable]
public struct MsgData
{