2009-03-10 10 views
9

Szukam obsługi serializacji w języku C#. Mogłem czerpać z ISerializable i wdrażać serializację, kopiując wartości członków w buforze bajtowym. Wolałbym jednak bardziej automatyczny sposób jak w C/C++.Byte dla serializacji bajtów struktury w C#

Rozważmy następujący kod:

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

Mam rodzajowe Rama struct działając jako otoczka do wielu rodzajów ładunku, dla transmisji szeregowej. ModemStatus jest przykładem takiego ładunku.

Jednak uruchomienie funkcji TestSerialization() zwraca bufor o długości około bajtów (bez oczekiwanej zawartości)! Powinien zawierać 6 bajtów. Czy możliwe jest prawidłowe serializowanie tych danych bez ręcznego serializowania?

Odpowiedz

0

Może ogólne metody Serializować/deserializowania:

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 Znaleziono here.

+0

Mogłeś napisane, że w ciągu pół kodu ... – leppie

+2

Niestety wykopać przeszłości, ale ten kod jest straszne. XmlSerializer nie jest IDisposable, więc nie może być w użyciu instrukcja.'new MemoryStream()' jest tworzony i usuwany, ale nigdy nie jest używany. 'memoryStream' jest przypisane dwa razy, co nie spowoduje kompilacji, ponieważ jest częścią instrukcji using. 'UTF8ByteArrayToString()' i 'StringToUTF8ByteArray()' po prostu nie są nigdzie zdefiniowane. Może mógłbyś trochę czasu, aby to naprawić, teraz masz więcej doświadczenia? –

8

Jako Chris says możesz użyć niebezpiecznego kodu - w takim przypadku lepiej upewnij się, że wyraźnie określasz układ. W tym momencie oczywiście zmniejszasz zdolność CLR do optymalizacji - skończysz z niewyrównanym dostępem, utratą atomowości itp. To może nie być dla ciebie istotne, ale warto o tym pamiętać.

Osobiście uważam, że jest to dość kruchy sposób serializowania/deserializacji. Jeśli coś się zmieni, twoje dane będą nieczytelne. Jeśli spróbujesz uruchomić architekturę, która ma inną endianię, wszystkie twoje wartości zostaną skradzione itd. Ponadto użycie układu in-memory zakończy się niepowodzeniem, gdy tylko będziesz potrzebował użyć typów referencyjnych - które mogą również wpływać na własny projekt typów, zachęcając do używania struktur, w których w przeciwnym razie używałbyś klas.

Zdecydowanie wolę jednoznacznie odczytać i zapisać wartości (np. Z BinaryWriter, lub najlepiej a version of binary writer which lets you set the endianness) lub użyć przenośnej struktury serializacji, takiej jak Protocol Buffers.

+0

Próbuję zdefiniować struktury protokołu dla małego chipu przyjmującego polecenia przez UART (do testowania). Protokół ten jest dla mnie bardzo poważny i nigdy nie przechowywałbym danych i nie czytałam ich później. Zdecydowanie podążałbym za twoją radą dla poważnego ser/des. Dzięki! – joelr

1

Zobacz ten link. Wykorzystuje to mechanizm Marshal, aby uzyskać dane actaul swoich struktur i skopiować je do Byte []. Również, jak je skopiować. Zaletą tych funkcji są one uniwersalne, więc będzie współpracować ze wszystkimi swoimi elemencie (chyba że mają typy danych, które mają zmienne rozmiary jak struny)

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

+0

@David: Link jest zepsuty –

13

Wystarczy użyć tego dwie metody:

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

i określ struct tak (. Określ dokładny rozmiar i paczka (Wyrównaj) przez jeden bajt domyślnym jest 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; 
} 

teraz Można deserializowania użyciu

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

i szeregować użyciu

StructTools.RawSerialize(myStruct); 
+1

Używam tej odpowiedzi już od miesiąca i jest całkiem niezły. – rocketsarefast

+0

To musi być niesamowite! Zobacz, kto napisał drugą odpowiedź ... Ale Jon wciąż jest MacGyverem w StackOverflow. – JCH2k