【问题标题】:Marshalled struct results in OutOfBoundsException in C#编组结构导致 C# 中的 OutOfBoundsException
【发布时间】:2017-06-26 11:16:33
【问题描述】:

所以我已经创建了一个结构,我想使用一个简单的 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);

我现在要进行简单的序列化,但我想知道为什么这不能按预期工作?

【问题讨论】:

  • 发生异常时bytes.Length是什么?
  • 您确定bytes 包含足够的字节吗?我会在某处添加if (bytes.Length != size) throw new ArgumentException();
  • 不清楚你打算用这段代码做什么。首先,您没有在结构上指定 StructLayout 属性,在这种情况下未指定打包(您可以根据 x86/x64 检测将使用哪种打包,但代码看起来很脆弱)。此外,您没有在字段上指定“MarshalAs”属性,在这种情况下,您的字符串不会真正被编组。最后,您将“true”传递给 StructureToPtr 的 fDeleteOld 参数,但您分配的内存默认情况下不应包含结构。
  • 请解释您想要完成的任务,而不仅仅是“此代码失败,为什么”
  • var size = Marshal.SizeOf(data);

标签: c# exception struct marshalling outofrangeexception


【解决方案1】:

你的代码有几个问题,所以让我解释一下你需要做什么才能修复它。

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

您的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 = 4 字节的 int + 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
{

【讨论】:

  • 最后一部分是我在这种情况下的“简单序列化”的意思。然而,我生活,我学习。感谢您提供详尽的教学解释。
猜你喜欢
  • 2020-04-25
  • 1970-01-01
  • 2012-10-16
  • 2022-01-08
  • 1970-01-01
  • 2014-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多