2010-04-12 5 views
15

Ich habe ein System, wo ein Remote-Agent serialisierte Strukturen (von einem eingebetteten C-System) für mich über IP/UDP lesen und speichern sendet. In einigen Fällen muss ich die gleichen Strukturtypen zurücksenden. Ich dachte, ich hätte ein gutes Setup mit Marshal.PtrToStructure (receive) und Marshal.StructureToPtr (send). Ein kleiner Fehler ist jedoch, dass die Netzwerk-Big-Endian-Integer in mein x86-Little-Endian-Format konvertiert werden müssen, um lokal verwendet zu werden. Wenn ich sie wieder sende, ist Big Endian der richtige Weg.Marshal.PtrToStructure (und wieder zurück) und generische Lösung für Endianness Swapping

Hier sind die Funktionen in Frage:

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

Und ein kurzes Beispiel Struktur, die wie folgt verwendet werden könnten:

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

Ändert sich die Struktur in Frage wie folgt aussieht:

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

Was (generische) Weise kann ich die Endianness tauschen, wenn Sie die Strukturen marshallen? Mein Bedarf ist derart, dass die lokal gespeicherte "request.sequence" in diesem Beispiel Little-Endian sein sollte, um dem Benutzer angezeigt zu werden. Ich möchte die Endianz nicht strukturspezifisch austauschen müssen, da es sich um ein generisches Problem handelt.

Mein erster Gedanke war Reflection zu verwenden, aber ich kenne diese Funktion nicht sehr gut. Außerdem hoffte ich, dass es da draußen eine bessere Lösung geben würde, auf die mich jemand hinweisen könnte. Vielen Dank im Voraus :)

+0

Wie hat es zwei Jahre gedauert, bis jemand darauf hingewiesen hat, dass wir die gleiche Frage stellen (http: // stackoverflow.com/questions/2480116/Marshalling-a-Big-Endian-Byte-Sammlung-in-Struktur-in-Order-to-Pull-out-Wert)!? :-) – Pat

Antwort

18

Reflection scheint der einzige wirkliche Weg zu sein, um das zu erreichen, wonach Sie suchen.

Ich habe unten einen Code zusammengestellt. Es erstellt ein Attribut namens EndianAttribute, das auf Feldebene auf einer Struktur angewendet werden kann. Ich habe die Definition für dieses Attribut und seine zugeordnete Enumeration sowie die Änderungen an Ihrem Code, die für die Verwendung dieses Attributs erforderlich sind, hinzugefügt.

Als Randnotiz müssen Sie rawData nicht als ref Parameter definieren.

Beachten Sie, dass dies die Verwendung von C# 3.0/.NET 3.5 erfordert, da ich LINQ und anonyme Typen in der Funktion verwende, die die Arbeit erledigt. Es wäre jedoch nicht schwierig, die Funktion ohne diese Funktionen neu zu schreiben.

[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

Nur die glatte Art von Setup, die ich gesucht habe. Ich gebe das eine Chance. – cgyDeveloper

+0

Funktioniert bisher ohne weitere Modifikationen ... Ich mag diese Lösung sehr. – cgyDeveloper

+0

Schöne Lösung! – ParmesanCodice

2

Für diejenigen von uns, ohne Linq, ein Ersatz 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

Diese Frage war super und hat mir sehr geholfen! Ich musste jedoch auf den Endian-Wechsler erweitern, da er anscheinend keine Arrays oder Strukturen in Strukturen behandelt.

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

Beachten Sie, dass dieser Code nur an Strukturen getestet wurde, die aus int, float und double bestehen. Vermasselt wahrscheinlich, wenn Sie eine Schnur dort haben!

1

Hier ist meine Variante - sie behandelt verschachtelte Strukturen und Arrays mit der Annahme, dass Arrays eine feste Größe haben, zB markiert mit einem [MarshalAs (UnmanagedType.ByValArray, SizeConst = N)] Attribut.

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

Seit .NET 4.5.1 können Sie 'var size = Marshal.SizeOf ();' anstelle von 'var size = Marshal.SizeOf (structure);'. –

+0

Das Code-Snippet wurde aktualisiert, um Ihrem Vorschlag zu entsprechen. – DanB

+0

Ich bin gerade auf Ihren Code gestoßen und ich denke, es ist ziemlich gut, abgesehen von einem kleinen Fehler in Ihrer Rekursion, wo Sie mit Arrays umgehen. Ich habe "var arrayOffset = field.Offset + offSet;" und "für (int i = arrayOffset; i

Verwandte Themen