2010-04-12 11 views
15

IP/UDP aracılığıyla okumak ve saklamak için uzak bir ajanın serileştirilmiş yapıları (gömülü C sisteminden) gönderdiği bir sistemim var. Bazı durumlarda aynı yapı tiplerini geri göndermem gerekiyor. Marshal.PtrToStructure (alma) ve Marshal.StructureToPtr (gönder) kullanarak güzel bir kurulum olduğunu düşündüm. Ancak, küçük bir çentik, ağın büyük endian tam sayılarının yerel olarak kullanılmak üzere x86 küçük endian formatına dönüştürülmesi gerektiğidir. Onları tekrar gönderdiğimde, büyük endian gitmek için yoldur.Marshal.PtrToStructure ve endianness takas için genel çözüm

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

Ve böyle kullanılabilecek hızlı bir örnek yapısı:

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

Söz konusu yapı gibi görünür:

[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; 
} 
İşte

söz konusu fonksiyonlar

Yapıları sıralarken ne (jenerik) bir şekilde endoteliteyi değiştirebilirim? İhtiyacım şudur, bu örnekte yerel olarak saklanan 'request.sequence', kullanıcıya gösterilmek üzere az-endian olmalıdır. Genel bir sorun olduğundan, enderliği yapıya özel bir şekilde değiştirmek istemiyorum.

İlk düşüncem Reflection kullanmaktı, ama bu özelliğe aşina değilim. Ayrıca, birilerinin bana doğru yönlendirebileceği daha iyi bir çözüm olacağını umuyorum. Şimdiden teşekkürler :)

+0

Birinin aynı sorgulamayı (http: // stackoverflow) istediğimizi belirtmesi iki yıl nasıl geçti?com/sorular/2480116/sıralanırken-a-büyük-endian bayt-toplama-içine-a-yapı-in-düzen-to-pull-out-valu) !? :-) – Pat

cevap

18

Yansıma, neyin peşinde olduğunuzu başarmanın tek gerçek yolu gibi görünüyor.

Aşağıda bazı kodları bir araya getirdim. Bir yapıda alan düzeyinde uygulanabilecek EndianAttribute adında bir öznitelik oluşturur. Bu öznitelik tanımını ve bununla ilişkili enum'un yanı sıra, kodunuzu kullanmak için gerekli olan değişiklikleri de ekledim.

Bir kenar notu olarak, parametresini ref parametresi olarak tanımlamanıza gerek yoktur.

Not: Bu işlem C# 3.0/.NET 3.5 kullanımını gerektirdiğinden, LINQ ve işlevi kullanan işlevin anonim türlerini kullanıyorum. Yine de, bu özellikler olmadan işlevi yeniden yazmak zor olmaz.

Linq olmadan bizler için
[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

Sadece aradığım kurulumun harika bir türü. Bunu bir atış yapacağım. – cgyDeveloper

+0

Şimdiye kadar başka bir değişiklik yapmadan çalışıyor ... Bu çözümü çok beğeniyorum. – cgyDeveloper

+0

Güzel çözüm! – ParmesanCodice

2

, bir yedek 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

Bu soru Başar ve bana çok yardım etti! Yapımlardaki dizileri veya yapıları ele almadığı için, endian değiştiricisinde genişlemem gerekiyordu.

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

Bu kodun yalnızca int, float, double biçimindeki yapılar üzerinde denendiğini unutmayın. Orada bir ipin varsa muhtemelen dağınık!

1

İşte benim varyasyonum - iç içe geçmiş yapıları ve dizileri, dizilerin sabit bir boyutta olduğu varsayımıyla, örneğin [MarshalAs (UnmanagedType.ByValArray, SizeConst = N)] özniteliği ile işaretlenmiş.

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

.NET 4.5.1 sürümünden 'var size = Marshal.SizeOf ();' vari size = Marshal.SizeOf (structure); 'yerine kullanabilirsiniz. –

+0

Önerinizi yansıtacak şekilde kod snippet'ini güncelleştirin. – DanB

+0

Sadece kodunuza rastladım ve dizileri hallettiğiniz yinelemede küçük bir hata dışında oldukça iyi olduğunu düşünüyorum. "Var arrayOffset = field.Offset + offSet;" ekledim ve "(int i = arrayOffset; i