将字节数组转换为托管结构

更新:这个问题的答案帮助我在GitHub上编写了开源项目AlicanC的Modern Warfare 2 Tool 。 您可以看到我如何在MW2Packets.cs中读取这些数据包以及我编写的扩展,以读取Extensions.cs中的大端数据。

我在我的C#应用​​程序中使用Pcap.Net捕获“ 使命召唤:现代战争2”的 UDP数据包。 我从库中收到一个byte[] 。 我试图像字符串一样解析它,但是效果不好。

我有的byte[]有一个通用包头,然后是另一个特定于包类型的头,然后是关于大厅中每个玩家的信息。

一个乐于助人的人为我检查了一些数据包并想出了这些结构:

 // Fields are big endian unless specified otherwise. struct packet_header { uint16_t magic; uint16_t packet_size; uint32_t unknown1; uint32_t unknown2; uint32_t unknown3; uint32_t unknown4; uint16_t unknown5; uint16_t unknown6; uint32_t unknown7; uint32_t unknown8; cstring_t packet_type; // \0 terminated string }; // Fields are little endian unless specified otherwise. struct header_partystate //Header for the "partystate" packet type { uint32_t unknown1; uint8_t unknown2; uint8_t player_entry_count; uint32_t unknown4; uint32_t unknown5; uint32_t unknown6; uint32_t unknown7; uint8_t unknown8; uint32_t unknown9; uint16_t unknown10; uint8_t unknown11; uint8_t unknown12[9]; uint32_t unknown13; uint32_t unknown14; uint16_t unknown15; uint16_t unknown16; uint32_t unknown17[10]; uint32_t unknown18; uint32_t unknown19; uint8_t unknown20; uint32_t unknown21; uint32_t unknown22; uint32_t unknown23; }; // Fields are little endian unless specified otherwise. struct player_entry { uint8_t player_id; // The following fields may not actually exist in the data if it's an empty entry. uint8_t unknown1[3]; cstring_t player_name; uint32_t unknown2; uint64_t steam_id; uint32_t internal_ip; uint32_t external_ip; uint16_t unknown3; uint16_t unknown4; uint32_t unknown5; uint32_t unknown6; uint32_t unknown7; uint32_t unknown8; uint32_t unknown9; uint32_t unknown10; uint32_t unknown11; uint32_t unknown12; uint16_t unknown13; uint8_t unknown14[???]; // Appears to be a bit mask, sometimes the length is zero, sometimes it's one. (First entry is always zero?) uint8_t unknown15; uint32_t unknown16; uint16_t unknown17; uint8_t unknown18[???]; // Most of the time this is 4 bytes, other times it is 3 bytes. }; 

我在我的C#应用​​程序中重新创建了包头结构,如下所示:

 [StructLayout(LayoutKind.Sequential, Pack=1)] struct PacketHeader { public UInt16 magic; public UInt16 packetSize; public UInt32 unknown1; public UInt32 unknown2; public UInt32 unknown3; public UInt32 unknown4; public UInt16 unknown5; public UInt16 unknown6; public UInt32 unknown7; public UInt32 unknown8; public String packetType; } 

然后我尝试为“partystate”标题创建一个结构,但我得到错误,说fixed关键字是不安全的:

 [StructLayout(LayoutKind.Sequential, Pack=1)] struct PartyStateHeader { UInt32 unknown1; Byte unknown2; Byte playerEntryCount; UInt32 unknown4; UInt32 unknown5; UInt32 unknown6; UInt32 unknown7; Byte unknown8; UInt32 unknown9; UInt16 unknown10; Byte unknown11; fixed Byte unknown12[9]; UInt32 unknown13; UInt32 unknown14; UInt16 unknown15; UInt16 unknown16; fixed UInt32 unknown17[10]; UInt32 unknown18; UInt32 unknown19; Byte unknown20; UInt32 unknown21; UInt32 unknown22; UInt32 unknown23; } 

由于unknown14unknown18大小不同,我无法为玩家条目做任何事情。 (玩家条目是最重要的。)

现在,不知何故,我必须将byte[]为这些PacketHeader结构。 遗憾的是,它(PacketHeader)bytes并不容易。 我试过这个我在互联网上找到的方法,但它AccessViolationException了一个AccessViolationException

 GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader)); 

我怎样才能做到这一点?

我将字节数组转换为内存流。 然后在该流上实例化二进制阅读器。 然后定义辅助函数,它接受二进制读取器并解析单个类。

内置的BinaryReader类总是使用little endian。

我在这里使用类而不是结构。

 class PacketHeader { uint16_t magic; uint16_t packet_size; uint32_t unknown1; uint32_t unknown2; uint32_t unknown3; uint32_t unknown4; uint16_t unknown5; uint16_t unknown6; uint32_t unknown7; uint32_t unknown8; string packet_type; // replaced with a real string }; PacketHeader ReadPacketHeader(BinaryReader reader) { var result=new PacketHeader(); result.magic = reader.ReadInt16(); ... result.packet_type=ReadCString();//Some helper function you might need to define yourself return result; } 

//我在以下url找到了这个: http : //code.cheesydesign.com/?p = 572 (我还没有测试过,但是//乍一看它会运行良好。)

  ///  /// Reads in a block from a file and converts it to the struct /// type specified by the template parameter ///  ///  ///  ///  private static T FromBinaryReader(BinaryReader reader) { // Read in a byte array byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); // Pin the managed memory while, copy it out the data, then unpin it GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); return theStructure; } 

这就是我做的:

 using System; using System.Runtime.InteropServices; public static object GetObjectFromBytes(byte[] buffer, Type objType) { object obj = null; if ((buffer != null) && (buffer.Length > 0)) { IntPtr ptrObj = IntPtr.Zero; try { int objSize = Marshal.SizeOf(objType); if (objSize > 0) { if (buffer.Length < objSize) throw new Exception(String.Format("Buffer smaller than needed for creation of object of type {0}", objType)); ptrObj = Marshal.AllocHGlobal(objSize); if (ptrObj != IntPtr.Zero) { Marshal.Copy(buffer, 0, ptrObj, objSize); obj = Marshal.PtrToStructure(ptrObj, objType); } else throw new Exception(String.Format("Couldn't allocate memory to create object of type {0}", objType)); } } finally { if (ptrObj != IntPtr.Zero) Marshal.FreeHGlobal(ptrObj); } } return obj; } 

在结构定义中,我没有使用任何fixed区域,而是如果标准编组不起作用,我使用了MarshalAs属性。 这是你需要的字符串。

你会像这样使用这个函数:

 PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader)); 

编辑:我没有在代码示例中看到您的BigEndian“限制”。 只有字节为LittleEndian时,此解决方案才有效。

编辑2:在您的示例的字符串中,您将使用以下内容装饰它:

 [MarshalAs(UnmanagedType.LPStr)] 

在数组中,对于n大小的数组,我会使用类似的东西:

 [MarshalAs(UnmanagedType.ByValArray, SizeConst = n)] 

嗯,你真的有两个任务。 首先是将byte []解释为struct本质上,其次是处理可能的不同字节序。

所以,他们有点分歧。 AFAIK如果你想使用封送处理 – 它只会将字节解释为管理结构。 因此,从一个端到另一个端的转换留给你。 这并不难,但它不会是自动的。

因此,要将byte []解释为struct,您必须具有类似的东西:

 [StructLayout(LayoutKind.Sequential)] internal struct X { public int IntValue; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)] public byte[] Array; } static void Main(string[] args) { byte[] data = {1, 0, 0, 0, 9, 8, 7}; // IntValue = 1, Array = {9,8,7} IntPtr ptPoit = Marshal.AllocHGlobal(data.Length); Marshal.Copy(data, 0, ptPoit, data.Length); var x = (X) Marshal.PtrToStructure(ptPoit, typeof (X)); Marshal.FreeHGlobal(ptPoit); Console.WriteLine("x.IntValue = {0}", x.IntValue); Console.WriteLine("x.Array = ({0}, {1}, {2})", x.Array[0], x.Array[1], x.Array[2]); } 

所以前4个字节转到IntValue(1,0,0,0) – > [little endian] – > 1接下来3个字节直接转到数组。

如果你想要BigEndian,你应该自己做:

 int LittleToBigEndian(int littleEndian) { byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray(); return BitConverter.ToInt32(buf, 0); } 

这有点混乱,所以可能你最好坚持使用自定义编写的解析器,它从源字节[]逐个获取字节并填充你的数据类而不使用StructLayout和其他本机互操作。

我的方法不同。 我不想复制任何字节。
我只是想使用它们,修改它们中的一些并在其他地方使用更改的byte []数组作为byte []。
在挖掘谷歌和stackoverflow后,我决定进入不安全/固定。
在玩代码我发现没有副本的快速代码。
这是DEBUG / TEST代码。 在调试模式下检查它。
请记住,这样您就不会复制并且正在处理原始byte []数据。
struct中的任何更改都将反映在byte []数组更改中,反之亦然。
++ TESTED ++ WORKS

 //FOR DEBUG/TEST ONLY using System.Runtime.InteropServices; namespace ByteStructCast1 { class Program { [StructLayout(LayoutKind.Sequential, Pack = 1)] unsafe struct StructTest//4B { [MarshalAs(UnmanagedType.U2)] public ushort item1;//2B public fixed byte item2[2];//2B =2x 1B } static void Main(string[] args) { //managed byte array byte[] DB1 = new byte[7];//7B more than we need. byte buffer usually is greater. DB1[0] = 2;//test data |> LITTLE ENDIAN DB1[1] = 0;//test data | DB1[2] = 3;//test data DB1[3] = 4;//test data unsafe //OK we are going to pin unmanaged struct to managed byte array { fixed(byte* db1 = DB1) //db1 is pinned pointer to DB1 byte[] array { //StructTest t1 = *(StructTest*)db1; //does not change DB1/db1 //t1.item1 = 11; //does not change DB1/db1 db1[0] = 22; //does CHANGE DB1/db1 DB1[0] = 33; //does CHANGE DB1/db1 StructTest* ptest = (StructTest*)db1; //does CHANGE DB1/db1 ptest->item1 = 44; //does CHANGE DB1/db1 ptest->item2[0]++; //does CHANGE DB1/db1 ptest->item2[1]--; //does CHANGE DB1/db1 } } } } } 

对于那些有权访问C#7.3function的人,我使用这段不安全的代码来“序列化”为字节:

 public static class Serializer { public static unsafe byte[] Serialize(T value) where T : unmanaged { byte[] buffer = new byte[sizeof(T)]; fixed (byte* bufferPtr = buffer) { Buffer.MemoryCopy(&value, bufferPtr, sizeof(T), sizeof(T)); } return buffer; } public static unsafe T Deserialize(byte[] buffer) where T : unmanaged { T result = new T(); fixed (byte* bufferPtr = buffer) { Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T)); } return result; } } 

unmanaged类型可以是结构(没有引用类型的简单结构,那些被认为是受管理的结构)或本机类型,如intshort等。

要将字节数组转换为字符串,请执行此操作;

 byte [] dBytes = ... string str; System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding(); str = enc.GetString(dBytes); 

并将字符串转换回字节数组

 public static byte[] StrToByteArray(string str) { System.Text.UTF8Encoding encoding=new System.Text.UTF8Encoding(); return encoding.GetBytes(str); } 

现在阅读你的字符串,看看你的数据是什么。