2010-04-12 7 views
15

Ho un sistema in cui un agente remoto invia strutture serializzate (da un sistema C incorporato) da leggere e archiviare tramite IP/UDP. In alcuni casi ho bisogno di inviare gli stessi tipi di struttura. Ho pensato di avere un buon setup usando Marshal.PtrToStructure (receive) e Marshal.StructureToPtr (send). Tuttavia, un piccolo trucchetto è che gli interi bigian della rete devono essere convertiti nel mio formato little endian x86 da utilizzare localmente. Quando li rimando, big endian è la strada da percorrere.Marshal.PtrToStructure (e viceversa) e soluzione generica per lo scambio di endianness

Qui ci sono le funzioni in questione:

private static T BytesToStruct<T>(ref byte[] rawData) where T: struct 
    { 
     T result = default(T); 
     GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); 
     try 
     { 
      IntPtr rawDataPtr = handle.AddrOfPinnedObject(); 
      result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); 
     } 
     finally 
     { 
      handle.Free(); 
     } 
     return result; 
    } 

    private static byte[] StructToBytes<T>(T data) 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(); 
     } 
     return rawData; 
    } 

e una struttura esempio veloce che potrebbe essere utilizzato in questo modo:

byte[] data = this.sock.Receive(ref this.ipep); 
Request request = BytesToStruct<Request>(ref data); 

Qualora la struttura in questione si presenta come:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 
private struct Request 
{ 
    public byte type; 
    public short sequence; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] 
    public byte[] address; 
} 

Quale modo (generico) è possibile scambiare l'endianness durante il marshalling delle strutture? Il mio bisogno è tale che la "richiesta.sequenza" memorizzata localmente in questo esempio dovrebbe essere little-endian per la visualizzazione all'utente. Non voglio dover scambiare l'endianness in un modo specifico della struttura poiché si tratta di un problema generico.

Il mio primo pensiero è stato usare Reflection, ma non ho molta familiarità con quella funzione. Inoltre, speravo che là fuori ci sarebbe stata una soluzione migliore a cui qualcuno potesse puntare. Grazie in anticipo :)

+0

Come ci sono voluti due anni perché qualcuno facesse notare che stiamo chiedendo la stessa qeuestion (http: // StackOverflow.com/domande/2480116/smistamento-a-big-endian byte-raccolta-in-a-struct-in-order-to-pull-out-Value) !? :-) – Pat

risposta

18

Il riflesso sembra l'unico vero modo per ottenere ciò che cerchi.

Ho messo insieme qualche codice qui sotto. Crea un attributo chiamato EndianAttribute che può essere applicato a livello di campo su una struttura. Ho incluso la definizione per questo attributo ed è associata enum, così come le modifiche al codice necessarie per usarlo.

Come nota a margine, non è stato necessario definire rawData come parametro ref.

Nota che questo richiede l'uso di C# 3.0/.NET 3.5, poiché sto utilizzando LINQ e tipi anonimi nella funzione che esegue il lavoro. Tuttavia, non sarebbe difficile riscrivere la funzione senza queste funzionalità.

[AttributeUsage(AttributeTargets.Field)] 
public class EndianAttribute : Attribute 
{ 
    public Endianness Endianness { get; private set; } 

    public EndianAttribute(Endianness endianness) 
    { 
     this.Endianness = endianness; 
    } 
} 

public enum Endianness 
{ 
    BigEndian, 
    LittleEndian 
} 

private static void RespectEndianness(Type type, byte[] data) 
{ 
    var fields = type.GetFields().Where(f => f.IsDefined(typeof(EndianAttribute), false)) 
     .Select(f => new 
     { 
      Field = f, 
      Attribute = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0], 
      Offset = Marshal.OffsetOf(type, f.Name).ToInt32() 
     }).ToList(); 

    foreach (var field in fields) 
    { 
     if ((field.Attribute.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) || 
      (field.Attribute.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian)) 
     { 
      Array.Reverse(data, field.Offset, Marshal.SizeOf(field.Field.FieldType)); 
     } 
    } 
} 

private static T BytesToStruct<T>(byte[] rawData) where T : struct 
{ 
    T result = default(T); 

    RespectEndianness(typeof(T), rawData);  

    GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); 

    try 
    { 
     IntPtr rawDataPtr = handle.AddrOfPinnedObject(); 
     result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); 
    } 
    finally 
    { 
     handle.Free(); 
    }   

    return result; 
} 

private static byte[] StructToBytes<T>(T data) 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(); 
    } 

    RespectEndianness(typeof(T), rawData);  

    return rawData; 
} 
+0

Solo il tipo di installazione che stavo cercando. Darò uno scatto. – cgyDeveloper

+0

Funziona finora senza altre modifiche ... Mi piace molto questa soluzione. – cgyDeveloper

+0

Bella soluzione! – ParmesanCodice

2

Per quelli di noi senza LINQ, una sostituzione RespectEndianness():

private static void RespectEndianness(Type type, byte[] data) { 
    foreach (FieldInfo f in type.GetFields()) { 
     if (f.IsDefined(typeof(EndianAttribute), false)) { 
      EndianAttribute att = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0]; 
      int offset = Marshal.OffsetOf(type, f.Name).ToInt32(); 
      if ((att.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) || 
       (att.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian)) { 
       Array.Reverse(data, offset, Marshal.SizeOf(f.FieldType)); 
      } 
     } 
    } 
} 
0

Questa domanda era incredibile, e mi ha aiutato molto! Avevo bisogno di espandermi sull'endian changer anche se non sembra che gestisca matrici o strutture all'interno di struct.

public struct mytest 
    { 
     public int myint; 
     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] 
     public int[] ptime; 
    } 

    public static void SwapIt(Type type, byte[] recvbyte, int offset) 
    { 
     foreach (System.Reflection.FieldInfo fi in type.GetFields()) 
     { 
      int index = Marshal.OffsetOf(type, fi.Name).ToInt32() + offset; 
      if (fi.FieldType == typeof(int)) 
      { 
       Array.Reverse(recvbyte, index, sizeof(int)); 
      } 
      else if (fi.FieldType == typeof(float)) 
      { 
       Array.Reverse(recvbyte, index, sizeof(float)); 
      } 
      else if (fi.FieldType == typeof(double)) 
      { 
       Array.Reverse(recvbyte, index, sizeof(double)); 
      } 
      else 
      { 
       // Maybe we have an array 
       if (fi.FieldType.IsArray) 
       { 
        // Check for MarshalAs attribute to get array size 
        object[] ca = fi.GetCustomAttributes(false); 
        if (ca.Count() > 0 && ca[0] is MarshalAsAttribute) 
        { 
         int size = ((MarshalAsAttribute)ca[0]).SizeConst; 
         // Need to use GetElementType to see that int[] is made of ints 
         if (fi.FieldType.GetElementType() == typeof(int)) 
         { 
          for (int i = 0; i < size; i++) 
          { 
           Array.Reverse(recvbyte, index + (i * sizeof(int)), sizeof(int)); 
          } 
         } 
         else if (fi.FieldType.GetElementType() == typeof(float)) 
         { 
          for (int i = 0; i < size; i++) 
          { 
           Array.Reverse(recvbyte, index + (i * sizeof(float)), sizeof(float)); 
          } 
         } 
         else if (fi.FieldType.GetElementType() == typeof(double)) 
         { 
          for (int i = 0; i < size; i++) 
          { 
           Array.Reverse(recvbyte, index + (i * sizeof(double)), sizeof(double)); 
          } 
         } 
         else 
         { 
          // An array of something else? 
          Type t = fi.FieldType.GetElementType(); 
          int s = Marshal.SizeOf(t); 
          for (int i = 0; i < size; i++) 
          { 
           SwapIt(t, recvbyte, index + (i * s)); 
          } 
         } 
        } 
       } 
       else 
       { 
        SwapIt(fi.FieldType, recvbyte, index); 
       } 
      } 
     } 
    } 

Nota questo codice è stato testato solo su strutture costituite da int, float, double. Probabilmente rovinerai se hai una stringa lì dentro!

1

Ecco la mia variazione: gestisce le strutture e gli array annidati, con l'ipotesi che gli array abbiano una dimensione fissa, ad esempio contrassegnati con un attributo [MarshalAs (UnmanagedType.ByValArray, SizeConst = N)].

public static class Serializer 
{ 
    public static byte[] GetBytes<T>(T structure, bool respectEndianness = true) where T : struct 
    { 
     var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1 
     var bytes = new byte[size]; 
     var ptr = Marshal.AllocHGlobal(size); 

     Marshal.StructureToPtr(structure, ptr, true); 
     Marshal.Copy(ptr, bytes, 0, size); 
     Marshal.FreeHGlobal(ptr); 

     if (respectEndianness) RespectEndianness(typeof(T), bytes); 

     return bytes; 
    } 

    public static T FromBytes<T>(byte[] bytes, bool respectEndianness = true) where T : struct 
    { 
     var structure = new T(); 

     if (respectEndianness) RespectEndianness(typeof(T), bytes);  

     int size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1 
     IntPtr ptr = Marshal.AllocHGlobal(size); 

     Marshal.Copy(bytes, 0, ptr, size); 

     structure = (T)Marshal.PtrToStructure(ptr, structure.GetType()); 
     Marshal.FreeHGlobal(ptr); 

     return structure; 
    } 

    private static void RespectEndianness(Type type, byte[] data, int offSet = 0) 
    { 
     var fields = type.GetFields() 
      .Select(f => new 
      { 
       Field = f, 
       Offset = Marshal.OffsetOf(type, f.Name).ToInt32(), 
      }).ToList(); 

     foreach (var field in fields) 
     { 
      if (field.Field.FieldType.IsArray) 
      { 
       //handle arrays, assuming fixed length 
       var attr = field.Field.GetCustomAttributes(typeof(MarshalAsAttribute), false).FirstOrDefault(); 
       var marshalAsAttribute = attr as MarshalAsAttribute; 
       if (marshalAsAttribute == null || marshalAsAttribute.SizeConst == 0) 
        throw new NotSupportedException(
         "Array fields must be decorated with a MarshalAsAttribute with SizeConst specified."); 

       var arrayLength = marshalAsAttribute.SizeConst; 
       var elementType = field.Field.FieldType.GetElementType(); 
       var elementSize = Marshal.SizeOf(elementType); 
       var arrayOffset = field.Offset + offSet; 

       for (int i = arrayOffset; i < arrayOffset + elementSize * arrayLength; i += elementSize)     { 
        RespectEndianness(elementType, data, i); 
       } 
      } 
      else if (!field.Field.FieldType.IsPrimitive) //or !field.Field.FiledType.GetFields().Length == 0 
      { 
       //handle nested structs 
       RespectEndianness(field.Field.FieldType, data, field.Offset); 
      } 
      else 
      { 
       //handle primitive types 
       Array.Reverse(data, offSet + field.Offset, Marshal.SizeOf(field.Field.FieldType)); 
      } 
     } 
    } 
} 
+0

Da .NET 4.5.1 è possibile utilizzare 'var size = Marshal.SizeOf ();' invece di 'var size = Marshal.SizeOf (struttura);'. –

+0

Aggiornato lo snippet di codice per riflettere il tuo suggerimento. – DanB

+0

Ho appena trovato il tuo codice e penso che sia abbastanza buono, tranne che per un piccolo errore nella ricorsione in cui gestisci gli array. Ho aggiunto "var arrayOffset = field.Offset + offSet;" e "for (int i = arrayOffset; i

Problemi correlati