【问题标题】:Byte for byte serialization of a struct in C#C#中结构的字节序列化字节
【发布时间】:2009-03-10 03:24:31
【问题描述】:

我正在寻找 C# 中序列化的语言支持。我可以从 ISerializable 派生并通过在字节缓冲区中复制成员值来实现序列化。但是,我更喜欢一种更自动化的方式,就像在 C/C++ 中可以做的那样。

考虑以下代码:

using System;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace XBeeHelper
{
    class XBee
    {
        [Serializable()]
        public struct Frame<FrameType> where FrameType : struct
        {
            public Byte StartDelimiter;
            public UInt16 Lenght;
            public Byte APIIdentifier;
            public FrameType FrameData;
            public Byte Checksum;
        }

        [Serializable()]
        public struct ModemStatus
        {
            public Byte Status;
        }

        public Byte[] TestSerialization()
        {
            Frame<ModemStatus> frame = new Frame<ModemStatus>();
            frame.StartDelimiter = 1;
            frame.Lenght = 2;
            frame.APIIdentifier = 3;
            frame.FrameData.Status = 4;
            frame.Checksum = 5;

            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();
            formatter.Serialize(stream, frame);
            Byte[] buffer = stream.ToArray();
            return buffer;
        }
    }
}

我有一个通用的 Frame 结构,作为许多类型有效负载的包装器,用于串行传输。 ModemStatus 是此类有效负载的一个示例。

但是,运行 TestSerialization() 会返回一个长度为 382 字节的缓冲区(没有预期的内容)!它应该包含 6 个字节。是否可以在不手动序列化的情况下正确序列化这些数据?

【问题讨论】:

标签: c# serialization


【解决方案1】:

只需使用这两种方法:

public static class StructTools
{
    /// <summary>
    /// converts byte[] to struct
    /// </summary>
    public static T RawDeserialize<T>(byte[] rawData, int position)
    {
        int rawsize = Marshal.SizeOf(typeof(T));
        if (rawsize > rawData.Length - position)
            throw new ArgumentException("Not enough data to fill struct. Array length from position: "+(rawData.Length-position) + ", Struct length: "+rawsize);
        IntPtr buffer = Marshal.AllocHGlobal(rawsize);
        Marshal.Copy(rawData, position, buffer, rawsize);
        T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T));
        Marshal.FreeHGlobal(buffer);
        return retobj;
    }

    /// <summary>
    /// converts a struct to byte[]
    /// </summary>
    public static byte[] RawSerialize(object anything)
    {
        int rawSize = Marshal.SizeOf(anything);
        IntPtr buffer = Marshal.AllocHGlobal(rawSize);
        Marshal.StructureToPtr(anything, buffer, false);
        byte[] rawDatas = new byte[rawSize];
        Marshal.Copy(buffer, rawDatas, 0, rawSize);
        Marshal.FreeHGlobal(buffer);
        return rawDatas;
    }
}

并像这样指定您的结构(指定确切的大小并按一个字节打包(对齐)。默认为 8):

[StructLayout(LayoutKind.Explicit, Size = 11, Pack = 1)]
private struct MyStructType
{
    [FieldOffset(0)]
    public UInt16 Type;
    [FieldOffset(2)]
    public Byte DeviceNumber;
    [FieldOffset(3)]
    public UInt32 TableVersion;
    [FieldOffset(7)]
    public UInt32 SerialNumber;
}

现在您可以使用反序列化

StructTools.RawDeserialize<MyStructType>(byteArray, 0); // 0 is offset in byte[]

并使用序列化

StructTools.RawSerialize(myStruct);

【讨论】:

  • 我已经使用这个答案一个月了,它非常棒。
  • 一定很棒!看看谁写了第二个答案……但 Jon 仍然是 StackOverflow 的 MacGyver。
【解决方案2】:

作为Chris says,您可以使用不安全的代码——在这种情况下,您最好确保明确指定布局。当然,此时您会降低 CLR 的优化能力 - 您最终会遇到未对齐的访问、原子性丢失等问题。这可能与您无关,但值得牢记。

就我个人而言,我认为这是一种非常脆弱的序列化/反序列化方式。如果有任何变化,您的数据将无法读取。如果您尝试在使用不同字节序的架构上运行,您会发现所有值都搞砸了等等。此外,一旦您需要使用引用类型,使用内存布局就会失败——这很可能影响您自己的类型设计,鼓励您在原本会使用类的地方使用结构。

我更喜欢明确地读取和写入值(例如使用 BinaryWriter,或者最好使用 a version of binary writer which lets you set the endianness)或使用可移植的序列化框架,如 Protocol Buffers

【讨论】:

  • 我正在尝试为通过 UART 接受命令的小芯片定义协议结构(用于测试)。该协议对我来说几乎是一成不变的,我永远不会存储数据并稍后再读回来。不过,对于严肃的 ser/des,我肯定会听从你的建议。谢谢!
【解决方案3】:

查看此链接。这使用 Marshal 机制来获取结构的实际数据并将它们复制到 Byte[]。另外,如何将它们复制回来。这些函数的好处是它们是通用的,因此它适用于您的所有结构(除非它们具有可变大小的数据类型,如字符串)

http://dooba.net/2009/07/c-sharp-and-serializing-byte-arrays/

【讨论】:

  • @David:链接已损坏
【解决方案4】:

也许是通用的序列化/反序列化方法:

public static string SerializeObject<T>(T obj)
{
      string xmlString = null;
      using(MemoryStream memoryStream = new MemoryStream())
      {
        using(XmlSerializer xs = new XmlSerializer(typeof(T)))
        {
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            xs.Serialize(xmlTextWriter, obj);
            memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
            xmlString = UTF8ByteArrayToString(memoryStream.ToArray());      
        }
      }
      return xmlString;
}

public static T DeserializeObject<T>(string xml)
{
   XmlSerializer xs = new XmlSerializer(typeof(T));
   MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xml));
   XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
   return (T)xs.Deserialize(memoryStream);
}

原始发现here

【讨论】:

  • 抱歉挖掘了过去,但这段代码很糟糕。 XmlSerializer 不是 IDisposable 所以不能在 using 语句中。 new MemoryStream() 已创建和处置,但从未使用过。 memoryStream 被分配了两次,因为它是 using 语句的一部分,所以不会编译。 UTF8ByteArrayToString()StringToUTF8ByteArray() 只是没有在任何地方定义。也许你现在有更多经验可以花时间修复它?
猜你喜欢
  • 1970-01-01
  • 2012-10-16
  • 1970-01-01
  • 2010-10-05
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
相关资源
最近更新 更多