2010-04-12 3 views
15

필자는 원격 에이전트가 직렬화 된 구조 (임베디드 C 시스템에서)를 보내 IP/UDP를 통해 읽고 저장할 수있는 시스템을 가지고 있습니다. 어떤 경우에는 동일한 구조 유형을 다시 전송해야합니다. 나는 Marshal.PtrToStructure (receive)와 Marshal.StructureToPtr (send)를 사용하여 좋은 설정이 있다고 생각했다. 그러나 작은 문제는 네트워크 빅 엔디안 정수를 로컬에서 사용하기 위해 x86 리틀 엔디안 형식으로 변환해야한다는 것입니다. 내가 그들을 다시 보낼 때 빅 엔디안이 갈 길입니다.Marshal.PtrToStructure (그리고 다시) endianness swapping을위한 일반 솔루션

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

그리고 다음과 같이 사용될 수있는 간단한 구조 예 :

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

문제의 구조는 다음과 같습니다

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

는 문제의 함수이다

구조를 정렬 할 때 엔디안을 어떻게 교환 할 수 있습니까? 이 예제에서 로컬에 저장된 'request.sequence'는 사용자에게 표시하기 위해 리틀 엔디안이어야합니다. 나는 그것이 일반적인 문제이기 때문에 구조 고유의 방식으로 엔디안을 교환하고 싶지 않습니다.

내 첫 번째 생각은 리플렉션을 사용하는 것이었지만 그 기능에 익숙하지 않았습니다. 또한 누군가가 나를 향해 더 나은 해결책을 제시 할 수 있기를 바랬다. 미리 감사드립니다 :)

+0

누군가가 우리가 같은 qeuestion (http : // stackoverflow)을 요구하고 있음을 지적하는 데 어떻게 2 년이 걸렸습니까?com/questions/2480116/marshalling-a-big-endian-byte-collection-into-a-struct-in-order-to-out-value) !? :-) – Pat

답변

18

리플렉션은 실제 상황을 수행하는 유일한 방법처럼 보입니다.

아래 코드를 조합했습니다. 구조체의 필드 수준에 적용 할 수있는 EndianAttribute이라는 특성을 만듭니다. 이 속성의 정의와 관련 열거 형을 포함 시켰습니다. 또한이 속성을 사용하는 데 필요한 코드를 수정했습니다.

rawDataref 매개 변수로 정의 할 필요가 없었습니다.

작업을 수행하는 함수에 LINQ 및 익명 형식을 사용하고 있으므로 C# 3.0/.NET 3.5를 사용해야 함을 유의하십시오. 그러나 이러한 기능이 없으면 기능을 다시 작성하는 것이 어렵지 않습니다.

Linq에없는 우리의 사람들을 위해
[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

내가 찾고 있던 설정의 단지 매끄러운 종류. 나는 이것을 줄 것이다. – cgyDeveloper

+0

다른 수정없이 지금까지 작동 ... 나는이 솔루션을 많이 좋아한다. – cgyDeveloper

+0

좋은 해결책! – ParmesanCodice

2

, 대체 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

이 질문은 굉장했고 나에게 많은 도움이! 하지만 구조체 내에서 배열이나 구조체를 처리하지 않는 것처럼 엔디안 체인저를 확장해야했습니다.

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

참고이 코드는 int, float, double로 만든 구조체에서만 테스트되었습니다. 네가 거기에 끈이 있으면 아마도 엉망이 될거야!

1

내 변형 - 중첩 된 구조체 및 배열을 처리합니다. 배열은 고정 크기 (예 : [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

.NET 4.5.1부터'var size = Marshal.SizeOf (structure);'대신'var size = Marshal.SizeOf ();'을 사용할 수 있습니다. –

+0

제안 사항을 반영하도록 코드 스 니펫을 업데이트했습니다. – DanB

+0

방금 ​​코드를 보았고 배열을 처리하는 재귀의 작은 오류를 제외하고는 꽤 좋은 코드라고 생각합니다. "var arrayOffset = field.Offset + offSet;"을 추가했습니다. 및 "for (int i = arrayOffset; i