Байт для байтовой сериализации структуры в 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 байт. Можно ли правильно сериализовать эти данные без ручной сериализации?


person joelr    schedule 10.03.2009    source источник


Ответы (4)


Просто используйте эти два метода:

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);
person JCH2k    schedule 02.01.2013
comment
Я использую этот ответ уже месяц, и это довольно круто. - person rocketsarefast; 01.05.2014
comment
Это должно быть круто! Посмотрите, кто написал второй ответ... Но Джон по-прежнему МакГайвер из StackOverflow. - person JCH2k; 03.07.2015

Как говорит Крис, вы можете используйте небезопасный код - в этом случае вам лучше убедиться, что вы явно указали макет. В этот момент, конечно, вы немного снижаете способность CLR к оптимизации - вы получите невыровненный доступ, потерю атомарности и т. д. Это может быть неактуально для вас, но об этом стоит помнить.

Лично я считаю это довольно хрупким способом сериализации/десериализации. Если что-то изменится, ваши данные станут нечитаемыми. Если вы попытаетесь запустить архитектуру с другим порядком байтов, вы обнаружите, что все ваши значения испорчены и т. д. влиять на ваш собственный дизайн типов, побуждая вас использовать структуры там, где вы в противном случае использовали бы классы.

Я предпочитаю явно читать и записывать значения (например, с помощью BinaryWriter или, что предпочтительнее, версия двоичного Writer, который позволяет установить порядок следования байтов), или использовать переносимую структуру сериализации, такую ​​как буферы протокола.

person Jon Skeet    schedule 10.03.2009
comment
Я пытаюсь определить структуры протокола для небольшого чипа, принимающего команды через UART (для тестирования). Протокол для меня в значительной степени высечен на камне, и я бы никогда не сохранил данные и не прочитал их позже. Я определенно последую вашему совету для серьезного ser/des. Спасибо! - person joelr; 10.03.2009

См. эту ссылку. При этом используется механизм Marshal для доступа к фактическим данным ваших структур и их копирования в Byte[]. Кроме того, как скопировать их обратно. В этих функциях хорошо то, что они являются универсальными, поэтому они будут работать со всеми вашими структурами (если только у них нет типов данных с переменными размерами, такими как строки).

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

person Community    schedule 14.07.2009
comment
@David: ссылка не работает - person Shantanu Gupta; 22.05.2015

Возможно, общие методы сериализации/десериализации:

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

Оригинал найден здесь.

person CmdrTallen    schedule 10.03.2009
comment
Извините, что копаюсь в прошлом, но этот код ужасен. XmlSerializer не является IDisposable, поэтому не может использоваться в инструкции. new MemoryStream() создается и удаляется, но никогда не используется. memoryStream присваивается дважды, что не будет компилироваться, поскольку является частью оператора using. UTF8ByteArrayToString() и StringToUTF8ByteArray() просто нигде не определены. Может быть, вы могли бы найти время, чтобы исправить это, теперь у вас больше опыта? - person Buh Buh; 18.05.2012