简体   繁体   中英

Byte for byte serialization of a struct in C#

I'm looking for language support of serialization in C#. I could derive from ISerializable and implement the serialization by copying member values in a byte buffer. However, I would prefer a more automatic way like one could do in C/C++.

Consider the following code:

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;
        }
    }
}

I have a generic Frame struct acting as a wrapper for many types of payload, for serial transmission. ModemStatus is an example of such payload.

However, running TestSerialization() returns a buffer 382 bytes long (without the expected content). It should have contained 6 bytes? Is it possible to serialize this data correctly without manual serializing?

Just use this two methods:

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;
    }
}

And specify your struct like this (Specify the exact size and pack (align) by one byte. default is 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;
}

Now you can Deserialize using

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

and serialize using

StructTools.RawSerialize(myStruct);

As Chris says , you can use unsafe code - in which case you'd better make sure you specify the layout explicitly. At that point of course you're reducing the CLR's ability to optimise a bit - you'll end up with unaligned access, loss of atomicity etc. That may well not be relevant for you, but it's worth bearing in mind.

Personally, I regard this as being a pretty fragile way to serialize/deserialize. If anything changes, your data is unreadable. If you try to run on an architecture which uses a different endianness, you'll find all your values screwed up etc. In addition, using the in-memory layout will fail as soon as you need to use an reference types - which could well influence your own design of types, encouraging you to use structs where you would otherwise use classes.

I far prefer to either explicitly read and write the values (eg with BinaryWriter, or preferably a version of binary writer which lets you set the endianness ) or use a portable serialization framework like Protocol Buffers .

See this link. This uses the Marshal mechanism to get to the actaul data of your structs and copy them to a Byte[]. Also, how to copy them back. The nice thing about these functions are they are generic, so it will work with all your structs (unless they have data types that have variable sizes like strings)

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

Perhaps generic Serialize/Deserialize methods:

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);
}

Original found here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM