Effettuare il marshalling di una collezione di byte big-endian in una struttura per estrarre valori

C’è una domanda interessante sulla lettura di una struttura dati C / C ++ in C # da un array di byte , ma non riesco a far funzionare il codice per la mia collezione di byte big-endian (byte di rete). (EDIT: Si noti che la mia vera struttura ha più di un solo campo.) C’è un modo per eseguire il marshalling dei byte in una versione big-endian della struttura e quindi estrarre i valori nel endianness del framework (quello dell’host , che di solito è little-endian)?

(Nota, l’inversione della matrice di byte non funzionerà – i byte di ogni valore devono essere invertiti, il che non ti dà la stessa collezione di tutti i byte invertiti.)

Questo dovrebbe riassumere quello che sto cercando (LE = LittleEndian, BE = BigEndian):

void Main() { var leBytes = new byte[] {1, 0, 2, 0}; var beBytes = new byte[] {0, 1, 0, 2}; Foo fooLe = ByteArrayToStructure(leBytes); Foo fooBe = ByteArrayToStructureBigEndian(beBytes); Assert.AreEqual(fooLe, fooBe); } [StructLayout(LayoutKind.Explicit, Size=4)] public struct Foo { [FieldOffset(0)] public ushort firstUshort; [FieldOffset(2)] public ushort secondUshort; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } T ByteArrayToStructureBigEndian(byte[] bytes) where T: struct { ??? } 

Altri link utili:

Byte di una struttura e su preoccupazioni endian

Un po ‘di più su byte e endianness (ordine byte)

Leggi i file binari in modo più efficiente usando C #

Non sicuro e la lettura da file

Il contributo di Mono al problema

Padroneggiare strutture C #

Ecco un’altra soluzione per lo scambio di endianness.

È regolato dalla soluzione Adam Robinsons qui: https://stackoverflow.com/a/2624377/1254743

È persino in grado di gestire le strutture nidificate.

 public static class FooTest { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo2 { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; public Foo2 foo2; } public static void test() { Foo2 sample2 = new Foo2() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! }; Foo sample = new Foo() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! foo2 = sample2, }; var bytes_LE = Dummy.StructToBytes(sample, Endianness.LittleEndian); var restoredLEAsLE = Dummy.BytesToStruct(bytes_LE, Endianness.LittleEndian); var restoredLEAsBE = Dummy.BytesToStruct(bytes_LE, Endianness.BigEndian); var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian); var restoredBEAsLE = Dummy.BytesToStruct(bytes_BE, Endianness.LittleEndian); var restoredBEAsBE = Dummy.BytesToStruct(bytes_BE, Endianness.BigEndian); Debug.Assert(sample.Equals(restoredLEAsLE)); Debug.Assert(sample.Equals(restoredBEAsBE)); Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE)); } public enum Endianness { BigEndian, LittleEndian } private static void MaybeAdjustEndianness(Type type, byte[] data, Endianness endianness, int startOffset = 0) { if ((BitConverter.IsLittleEndian) == (endianness == Endianness.LittleEndian)) { // nothing to change => return return; } foreach (var field in type.GetFields()) { var fieldType = field.FieldType; if (field.IsStatic) // don't process static fields continue; if (fieldType == typeof(string)) // don't swap bytes for strings continue; var offset = Marshal.OffsetOf(type, field.Name).ToInt32(); // handle enums if (fieldType.IsEnum) fieldType = Enum.GetUnderlyingType(fieldType); // check for sub-fields to recurse if necessary var subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray(); var effectiveOffset = startOffset + offset; if (subFields.Length == 0) { Array.Reverse(data, effectiveOffset, Marshal.SizeOf(fieldType)); } else { // recurse MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset); } } } internal static T BytesToStruct(byte[] rawData, Endianness endianness) where T : struct { T result = default(T); MaybeAdjustEndianness(typeof(T), rawData, endianness); GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); } finally { handle.Free(); } return result; } internal static byte[] StructToBytes(T data, Endianness endianness) where T : struct { byte[] rawData = new byte[Marshal.SizeOf(data)]; GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); Marshal.StructureToPtr(data, rawDataPtr, false); } finally { handle.Free(); } MaybeAdjustEndianness(typeof(T), rawData, endianness); return rawData; } } 

Sembra che ci debba essere una soluzione più elegante, ma questo dovrebbe almeno farti andare:

  static T ByteArrayToStructureBigEndian(byte[] bytes) where T : struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); System.Type t = stuff.GetType(); FieldInfo[] fieldInfo = t.GetFields(); foreach (FieldInfo fi in fieldInfo) { if (fi.FieldType == typeof(System.Int16)) { // TODO } else if (fi.FieldType == typeof(System.Int32)) { // TODO } else if (fi.FieldType == typeof(System.Int64)) { // TODO } else if (fi.FieldType == typeof(System.UInt16)) { UInt16 i16 = (UInt16)fi.GetValue(stuff); byte[] b16 = BitConverter.GetBytes(i16); byte[] b16r = b16.Reverse().ToArray(); fi.SetValueDirect(__makeref(stuff), BitConverter.ToUInt16(b16r, 0); } else if (fi.FieldType == typeof(System.UInt32)) { // TODO } else if (fi.FieldType == typeof(System.UInt64)) { // TODO } } return stuff; } 

Come accennato nel mio commento sulla risposta di @ weismat, c’è un modo semplice per ottenere una strutturazione big-endian. Implica una doppia inversione: i byte originali sono invertiti interamente, quindi la struttura stessa è l’inversione del formato dei dati originale (big-endian).

Il fooLe e il fooBe in Main avranno gli stessi valori per tutti i campi. (Normalmente, la struttura e i byte little-endian non sarebbero presenti, naturalmente, ma questo mostra chiaramente la relazione tra gli ordini byte).

NOTA: vedere il codice aggiornato incluso come recuperare i byte dalla struct.

 public void Main() { var beBytes = new byte[] { 0x80, 0x80,0, 0x80,0, 0x80,0,0,0, 0x80,0,0,0, 0x80,0,0,0,0,0,0,0, 0x80,0,0,0,0,0,0,0, 0x3F,0X80,0,0, // float of 1 (see http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness) 0x3F,0xF0,0,0,0,0,0,0, // double of 1 0,0,0,0x67,0x6E,0x69,0x74,0x73,0x65,0x54 // Testing\0\0\0 }; var leBytes = new byte[] { 0x80, 0,0x80, 0,0x80, 0,0,0,0x80, 0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0x80,0x3F, // float of 1 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; Foo fooLe = ByteArrayToStructure(leBytes).Dump("LE"); FooReversed fooBe = ByteArrayToStructure(beBytes.Reverse().ToArray()).Dump("BE"); } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FooReversed { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; public double d; public float f; public ulong L; public long l; public uint I; public int i; public ushort S; public short s; public byte b1; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } 

Alla fine ho capito un modo che non comportava riflessioni ed è per lo più user-friendly. Usa la class DataConverter di Mono ( fonte ) che, sfortunatamente, è abbastanza buggata a questo punto. (Ad esempio, float e double non sembrano funzionare correttamente, l’analisi delle stringhe è rotta, ecc.)

Il trucco è decomprimere e reimballare i byte come big-endian, che richiede una stringa che descriva i tipi presenti nell’array di byte (vedi l’ultimo metodo). Inoltre, l’allineamento dei byte è complicato: ci sono quattro byte nella struct di uno perché il marshalling sembra fare affidamento sull’avere un allineamento di 4 byte (non riesco ancora a capire bene quella parte). (EDIT: ho trovato che l’aggiunta di Pack=1 all’attributo StructLayout solito si occupa dei problemi di allineamento dei byte).

Nota, questo codice di esempio è stato utilizzato in LINQPad: il metodo di estensione Dump stampa solo informazioni sull’object e restituisce l’object (è fluente).

 public void Main() { var beBytes = new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80,0, 0x80,0, 0x80,0,0,0, 0x80,0,0,0, 0x80,0,0,0,0,0,0,0, 0x80,0,0,0,0,0,0,0, // 0,0,0x80,0x3F, // float of 1 // 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; var leBytes = new byte[] { 0x80, 0x80, 0x80, 0x80, 0,0x80, 0,0x80, 0,0,0,0x80, 0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0,0,0,0,0,0x80, // 0,0,0x80,0x3F, // float of 1 // 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; Foo fooLe = ByteArrayToStructure(leBytes).Dump("LE"); Foo fooBe = ByteArrayToStructureBigEndian(beBytes, "bbbbsSiIlL" // + "fd" // float, then double +"9bb").Dump("BE"); Assert.AreEqual(fooLe, fooBe); } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public byte b2; public byte b3; public byte b4; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; // public float f; // public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } T ByteArrayToStructureBigEndian(byte[] bytes, string description) where T: struct { byte[] buffer = bytes; IList unpacked = DataConverter.Unpack("^"+description, buffer, 0).Dump("unpacked"); buffer = DataConverter.PackEnumerable("!"+description, unpacked).Dump("packed"); return ByteArrayToStructure(buffer); } 

Sono d’accordo con @weismat e credo che non ci sia alcuna soluzione.

Quello che mostri nel tuo esempio è che puoi accedere a un buffer di byte non formattato come se fosse una qualsiasi struttura OTHER senza cambiargli nulla, non copiare o spostare dati in giro, niente. Basta bloccarlo per evitare che si muova a causa di GC.

Questo è fondamentalmente ciò che si ottiene normalmente in C usando un tipo di unione che contiene sia la struttura di destinazione che un array di byte della stessa dimensione.

Il lato positivo è che è davvero efficiente.

Questo ha diversi inconvenienti, il principale è che si può accedere in questo modo solo ai dati che si trovano nell’ordine macchina nativo (sia esso LE o BE). Quindi il tuo ByteArrayToStructure non è veramente LE, è solo perché il processore sottostante è LE. Se si compila lo stesso programma su un altro target che si trova in BE, funziona in un altro modo e si ritiene che il proprio array di byte sia BE.

Altri svantaggi sono che devi essere molto cauto con l’allineamento dei dati, essere consapevole del ansible riempimento, ecc. E ovviamente non c’è modo di cambiare l’ordine dei byte da LE a BE senza spostare i dati in array di byte (se hai 16 bit interi solo array come nel tuo esempio questo è semplicemente lo scambio ogni due byte).

Mi è capitato di avere un problema simile e ho tentato di non usare questa soluzione a causa dei precedenti inconvenienti e ho scelto di hide le mie strutture di input dietro gli accessor per hide l’accesso alla matrice di byte sottostante. Potrebbe non essere elegante, ma è semplice ed evitare anche di copiare il buffer o spostare i dati in alcun modo.

Hai provato MiscUtil? Ha una class di utilità chiamata EndianBitConverter per convertire tra array di byte grandi e piccoli endian.

Dal mio punto di vista è sufficiente aggiungere un Array.Reverse () prima della conversione dell’array di byte.

La soluzione tradizionale è usare ntohl () e ntohs ().

 typedef struct { long foo; short bar, baz; char xyzzy; } Data; Data d; memcpy(&d, buffer, sizeof(Data)); d.foo = ntohl(d.foo); d.bar = ntohs(d.bar); d.baz = ntohs(d.baz); // don't need to change d.xyxxy 

Quanto sopra funziona su qualsiasi piattaforma che abbia socket BSD, non importa se sia big-endian, little-endian o qualcosa di assolutamente strano come un VAX. L’operazione inversa viene eseguita usando hton * ().

Sulle piattaforms big-endian le funzioni sono in genere non operative e dovrebbero quindi essere a costo zero.