Marshal.PtrToStructure(以及返回)和字节顺序交换的通用解决方案

我有一个系统,远程代理发送序列化结构(来自嵌入式C系统)供我通过IP / UDP读取和存储。 在某些情况下,我需要发回相同的结构类型。 我以为我使用Marshal.PtrToStructure(接收)和Marshal.StructureToPtr(发送)进行了很好的设置。 但是,一个小问题是网络大端整数需要转换为我的x86小端格式才能在本地使用。 当我再次发送它们时,大端是可行的方式。

以下是有问题的function:

private static T BytesToStruct(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 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(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’应该是little-endian以便向用户显示。 我不想以结构特定的方式交换字节序,因为它是一般问题。

我的第一个想法是使用Reflection,但我对这个function并不是很熟悉。 此外,我希望有一个更好的解决方案,有人可以指出我。 提前致谢 :)

反思似乎是完成你所追求的唯一真正的方法。

我在下面汇总了一些代码。 它创建一个名为EndianAttribute的属性,可以在结构的字段级别应用。 我已经包含了这个属性的定义,它是关联的枚举,以及使用它所需的代码修改。

作为旁注,您不需要将rawData定义为ref参数。

请注意,这确实需要使用C#3.0 / .NET 3.5,因为我在执行工作的函数中使用LINQ和匿名类型。 但是,如果没有这些function,重写function并不困难。

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

对于我们这些没有Linq的人来说,替换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)); } } } } 

这是我的变体 – 它处理嵌套的结构和数组,假设数组具有固定的大小,例如用[MarshalAs(UnmanagedType.ByValArray,SizeConst = N)]属性标记。

 public static class Serializer { public static byte[] GetBytes(T structure, bool respectEndianness = true) where T : struct { var size = Marshal.SizeOf(structure); //or Marshal.SizeOf(); 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(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(); 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)); } } } } 

这个问题太棒了,给了我很多帮助! 我需要扩展endian更换器,因为它似乎不处理结构中的数组或结构。

  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构成的结构上进行测试。 如果你有一个字符串,可能会搞砸!