2010-03-19 13 views
10

C'è una domanda perspicace su reading a C/C++ data structure in C# from a byte array, ma non riesco a far funzionare il codice per la mia raccolta 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)?Marshalling di una raccolta di byte big-endian in una struttura per estrarre valori

(Nota, invertendo l'array di byte sarà non lavoro -. Byte di ogni valore deve essere invertita, che non ti dà la stessa collezione di invertire tutti i byte)

Questo dovrebbe riassumere quello che ho 'm alla ricerca di (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<Foo>(leBytes); 
    Foo fooBe = ByteArrayToStructureBigEndian<Foo>(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<T>(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<T>(byte[] bytes) where T: struct 
{ 
    ??? 
} 

Altri link utili:

Byte of a struct and onto endian concerns

A little more on bytes and endianness (byte order)

Read binary files more efficiently using C#

Unsafe and reading from files

Mono's contribution to the issue

Mastering C# structs

+0

Dai un'occhiata a questo: http://stackoverflow.com/a/2624377/1254743 Lo fa ancora più a grana fine, che puoi facilmente modificare se necessario. Non hai bisogno di costruire le tue strutture due volte (specialmente se hai strutture annidate). – Onur

+0

Penso che la libreria PODCaster (http://www.zer7.com/software/podcaster e su NuGet) potrebbe essere indirizzata a questo problema, ma onestamente non so dire come dovrebbe essere usato, anche dagli esempi. – Pat

risposta

2

Come accennato nel mio commento alla risposta di @ weismat, c'è un modo semplice per ottenere 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).

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

NOTA: Vedere updated code incluso come richiamare i byte dalla struttura.

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<Foo>(leBytes).Dump("LE"); 
    FooReversed fooBe = ByteArrayToStructure<FooReversed>(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<T>(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; 
} 
0

Hai provato MiscUtil? Ha una classe di utilità denominata EndianBitConverter per la conversione tra array di byte grandi e piccoli endian.

+2

Sì. Ciò non funzionerà perché riguarda solo i byte necessari per un determinato valore, ad es. trasformando '{0, 1}' in un 'ushort' di' 1', non per intere strutture. – Pat

-1

Dal mio punto di vista si solo bisogno di aggiungere un Array.reverse() prima della conversione della matrice di byte.

+1

Vedere gli aggiornamenti alla mia domanda che dovrebbero chiarire (ancora meglio) che l'inversione dell'array non funziona perché ci sono più valori nella struttura. – Pat

+0

Ok - ma dubito che sia disponibile una soluzione generica in quanto è necessario conoscere le dimensioni del campo per l'inversione: è necessario invertire quindi i GetByte dalla classe BitConverter per campo. – weismat

+0

Giusto, che è quello che sto facendo attualmente. Ma la soluzione per il caso little-endian è così elegante, voglio che funzioni per il mio caso big-endian! – Pat

1

Sono d'accordo con @weismat e credo che non ci sia 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 in C in genere utilizzando un tipo di unione contenente 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 è necessario essere molto cauto allineamento dei dati, essere a conoscenza di possibili imbottitura, ecc e, naturalmente, che non v'è alcun modo per modificare l'ordine dei byte da LE essere senza spostare i dati in byte array (se avere un array di soli 16 bit solo come nel tuo esempio, si tratta semplicemente di scambiare 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 nascondere le mie strutture di input dietro gli accessor per nascondere 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.

2

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

static T ByteArrayToStructureBigEndian<T>(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; 
    } 
+1

Ah sì, riflessione - Avevo paura che qualcuno rispondesse con la riflessione ;-). – Pat

+0

Che cosa è '__makeref' (utilizzato nella parte UInt16)? – Pat

+0

SetValueDirect richiede un valore TypedReference (una combinazione di un puntatore gestito e il tipo di cosa viene puntato) alla struttura dati che è il membro che stiamo impostando. La funzione __makeref restituisce questo. –

-1

La soluzione tradizionale è quella di utilizzare 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 

I lavori di cui sopra su qualsiasi piattaforma che ha BSD Sockets, non importa se si tratta di big-endian, little-endian, o qualcosa di assolutamente strano come un VAX. L'operazione inversa viene eseguita usando hton *().

Su piattaforme big-endian le funzioni sono in genere non operative e dovrebbero pertanto essere a costo zero.

1

Alla fine ho capito un modo che non implicava la riflessione ed è per lo più di facile utilizzo. Utilizza la classe DataConverter di Mono (source) che, sfortunatamente, è abbastanza bacata a questo punto. (Ad esempio, float e double non sembrano funzionare correttamente, l'analisi delle stringhe è interrotta, ecc.)

Il trucco è decomprimere e reimballare i byte come big-endian, che richiede una stringa che descrive quali tipi sono nell'array di byte (vedi l'ultimo metodo). Inoltre, l'allineamento dei byte è complicato: ci sono quattro byte nella struct invece 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 al StructLayout attribute di solito si occupa di questioni di byte-allineamento.)

nota, questo codice di esempio è stato utilizzato in LINQPad - il metodo di estensione Dump stampa solo informazioni circa l'oggetto e restituisce l'oggetto (è 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<Foo>(leBytes).Dump("LE"); 
    Foo fooBe = ByteArrayToStructureBigEndian<Foo>(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<T>(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<T>(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<T>(buffer); 
} 
9

Ecco un'altra soluzione per lo scambio di endianness.

E 'regolata da Adam Robinsons soluzione qui: https://stackoverflow.com/a/2624377/1254743

E' ancora 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<Foo>(bytes_LE, Endianness.LittleEndian); 
     var restoredLEAsBE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.BigEndian); 

     var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian); 
     var restoredBEAsLE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.LittleEndian); 
     var restoredBEAsBE = Dummy.BytesToStruct<Foo>(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<T>(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>(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; 
    } 

} 
Problemi correlati