如何在C#中封送(Marshal)动态大小的结构体数组?
C#结构体包含动态数组的内存序列化问题
为提升网络数据传输效率,想要将多个testStruct结构体打包到包含动态大小数组的sendTestStruct结构体中发送,而非分开发送单个结构体数据包,编写了如下代码:
public struct sendTestStruct { public int numberOfStructs; public testStruct[] structs; } public struct testStruct { public float float1, float2; public int int1, int2; public byte byte1, byte2; } sendTestStruct send = new(); send.structs = new testStruct[5]; send.numberOfStructs = 5; for (var i = 0; i < 5; i++) { testStruct numbers = new(); numbers.float1 = Random.Range(0f, 10f); numbers.float2 = Random.Range(0f, 10f); numbers.int1 = Random.Range(0, 10); numbers.int2 = Random.Range(0, 10); numbers.byte1 = (byte)Random.Range(0, 10); numbers.byte2 = (byte)Random.Range(0, 10); send.structs[i] = numbers; } IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(send)); Marshal.StructureToPtr(send, ptr, false); sendTestStruct newstruct = Marshal.PtrToStructure<sendTestStruct>(ptr); Marshal.FreeHGlobal(ptr);
执行sendTestStruct newstruct = Marshal.PtrToStructure<sendTestStruct>(ptr);时出现错误:
Structure field of type testStruct[] can't be marshalled as LPArray
解决方案
方法一:添加Marshal特性并手动计算内存大小
问题根源是默认情况下,Marshal无法自动处理结构体中的动态数组,需要通过特性指定数组长度的来源,并手动计算总内存大小:
- 修改结构体定义,添加布局特性和数组序列化特性:
[StructLayout(LayoutKind.Sequential)] public struct sendTestStruct { public int numberOfStructs; // SizeParamIndex = 0 表示数组长度由第0个字段(numberOfStructs)指定 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] public testStruct[] structs; } [StructLayout(LayoutKind.Sequential)] public struct testStruct { public float float1, float2; public int int1, int2; public byte byte1, byte2; }
- 手动计算需要分配的内存总大小,替换原
Marshal.SizeOf(send)的调用:
sendTestStruct send = new(); send.structs = new testStruct[5]; send.numberOfStructs = 5; for (var i = 0; i < 5; i++) { testStruct numbers = new(); numbers.float1 = Random.Range(0f, 10f); numbers.float2 = Random.Range(0f, 10f); numbers.int1 = Random.Range(0, 10); numbers.int2 = Random.Range(0, 10); numbers.byte1 = (byte)Random.Range(0, 10); numbers.byte2 = (byte)Random.Range(0, 10); send.structs[i] = numbers; } // 手动计算总内存:结构体数量的大小 + 每个testStruct的大小 × 数量 int singleTestStructSize = Marshal.SizeOf<testStruct>(); int totalMemorySize = Marshal.SizeOf<int>() + send.numberOfStructs * singleTestStructSize; IntPtr ptr = Marshal.AllocHGlobal(totalMemorySize); Marshal.StructureToPtr(send, ptr, false); sendTestStruct newstruct = Marshal.PtrToStructure<sendTestStruct>(ptr); Marshal.FreeHGlobal(ptr);
方法二:手动序列化/反序列化为字节数组(更适合网络传输)
如果是用于网络传输,直接手动将数据转换为字节数组会更灵活,无需依赖Marshal对结构体数组的处理:
序列化代码(转为可发送的字节数组):
sendTestStruct send = new(); send.structs = new testStruct[5]; send.numberOfStructs = 5; // 填充数据... List<byte> byteBuffer = new List<byte>(); // 先写入结构体数量 byteBuffer.AddRange(BitConverter.GetBytes(send.numberOfStructs)); // 逐个序列化testStruct并加入缓冲区 int singleStructSize = Marshal.SizeOf<testStruct>(); foreach (var ts in send.structs) { IntPtr tempPtr = Marshal.AllocHGlobal(singleStructSize); Marshal.StructureToPtr(ts, tempPtr, false); byte[] structBytes = new byte[singleStructSize]; Marshal.Copy(tempPtr, structBytes, 0, singleStructSize); byteBuffer.AddRange(structBytes); Marshal.FreeHGlobal(tempPtr); } byte[] sendData = byteBuffer.ToArray(); // 此时sendData可直接通过网络发送
反序列化代码(从接收的字节数组恢复结构体):
byte[] receivedData = ...; // 从网络接收的字节数组 sendTestStruct receivedStruct = new(); int offset = 0; // 先读取结构体数量 receivedStruct.numberOfStructs = BitConverter.ToInt32(receivedData, offset); offset += sizeof(int); // 逐个恢复testStruct int singleStructSize = Marshal.SizeOf<testStruct>(); receivedStruct.structs = new testStruct[receivedStruct.numberOfStructs]; for (int i = 0; i < receivedStruct.numberOfStructs; i++) { IntPtr tempPtr = Marshal.AllocHGlobal(singleStructSize); Marshal.Copy(receivedData, offset, tempPtr, singleStructSize); receivedStruct.structs[i] = Marshal.PtrToStructure<testStruct>(tempPtr); Marshal.FreeHGlobal(tempPtr); offset += singleStructSize; }
内容的提问来源于stack exchange,提问作者theepic0
相关产品推荐
相关产品推荐

