如何将包含数组的结构转换为字节数组?
How to convert a structure that contains an array to a byte array?
如何在 C# 中将包含数组的结构转换为字节数组?
有一个关于没有数组的结构的问题here。
但是如果结构包含这样的数组:
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
在字节中转换结构时导致访问冲突异常:
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
我的目标是使用 MSMQ 在消息队列中以字节为单位发送消息。
这里是编译和重现问题的完整代码。
using System;
//using System.IO;
//using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
internal class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
// Convert the struct in bytes
const int packetSize = 16;
var packetBytes = GetBytes(packet, packetSize);
// Create the message
/*
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
*/
}
}
}
问题在于关于结构在 C# 中的表示方式的错误假设
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
public DiObject[] Di
成员大小为 numDi * 4
的假设不正确。代替该字段的是指向结构数组的指针。数组是 .NET 中的 class,未包含在结构声明中。
要解决这个问题,可以使用固定数组。我知道设计背后的想法是获取可变长度数组,它在下一个代码清单中给出。
此代码在执行期间不会引发 AccessViolationException:
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public fixed byte Di[2 * 4];
}
internal unsafe class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
// packet.Di = new DiObject[packet.NumDi];
packet.Di[0] = 2;
packet.Di[1] = 3;
packet.Di[2] = 4;
packet.Di[3] = 5;
packet.Di[4] = 6;
packet.Di[5] = 7;
packet.Di[6] = 8;
packet.Di[7] = 9;
// Convert the struct in bytes
int packetSize = Marshal.SizeOf<MyPacket>();
var packetBytes = GetBytes(packet, packetSize);
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
}
}
}
下面的代码为具有可变内部数组大小的 MyPacket 结构提供了到字节数组和字节数组的高效转换。实现通过使用不安全的指针算法避免了类型转换和边界检查。
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
public byte[] ToBytes()
{
byte[] buffer = new byte[NumDi];
fixed(DiObject* pDi = Di)
fixed(byte* pBuff = buffer)
{
var pBuffDi = (DiObject*)pBuff;
var pDiPtr = pDi;
for (int i = 0; i < NumDi; i++)
*pBuffDi++ = *pDiPtr++;
}
return buffer;
}
public static MyPacket Create(byte[] buffer)
{
// argument checking code here
var packet = new MyPacket();
packet.ProtocolIdentifier = buffer[0];
packet.NumDi = buffer[1];
packet.Di = new DiObject[packet.NumDi];
fixed (byte* pBuf = buffer)
fixed (DiObject* pDi = packet.Di)
{
byte* pBufPtr = pBuf;
pBufPtr += 2;
var pBufDi = (DiObject*)pBufPtr;
var pDiPtr = pDi;
for (int i = 0; i < packet.NumDi; i++)
*pDiPtr++ = *pBufDi++;
}
return packet;
}
}
internal unsafe class Program
{
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 5;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
packet.Di[2].Command = 6;
packet.Di[2].ErrorClass = 7;
packet.Di[2].Flags = 8;
packet.Di[2].Reserved = 9;
packet.Di[3].Command = 6;
packet.Di[3].ErrorClass = 7;
packet.Di[3].Flags = 8;
packet.Di[3].Reserved = 9;
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packet.ToBytes());
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath);
// Send the message to the queue
q.Send(msg);
}
}
}
如何在 C# 中将包含数组的结构转换为字节数组?
有一个关于没有数组的结构的问题here。
但是如果结构包含这样的数组:
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
在字节中转换结构时导致访问冲突异常:
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
我的目标是使用 MSMQ 在消息队列中以字节为单位发送消息。
这里是编译和重现问题的完整代码。
using System;
//using System.IO;
//using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
internal class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
// Convert the struct in bytes
const int packetSize = 16;
var packetBytes = GetBytes(packet, packetSize);
// Create the message
/*
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
*/
}
}
}
问题在于关于结构在 C# 中的表示方式的错误假设
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
public DiObject[] Di
成员大小为 numDi * 4
的假设不正确。代替该字段的是指向结构数组的指针。数组是 .NET 中的 class,未包含在结构声明中。
要解决这个问题,可以使用固定数组。我知道设计背后的想法是获取可变长度数组,它在下一个代码清单中给出。
此代码在执行期间不会引发 AccessViolationException:
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public fixed byte Di[2 * 4];
}
internal unsafe class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
// packet.Di = new DiObject[packet.NumDi];
packet.Di[0] = 2;
packet.Di[1] = 3;
packet.Di[2] = 4;
packet.Di[3] = 5;
packet.Di[4] = 6;
packet.Di[5] = 7;
packet.Di[6] = 8;
packet.Di[7] = 9;
// Convert the struct in bytes
int packetSize = Marshal.SizeOf<MyPacket>();
var packetBytes = GetBytes(packet, packetSize);
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
}
}
}
下面的代码为具有可变内部数组大小的 MyPacket 结构提供了到字节数组和字节数组的高效转换。实现通过使用不安全的指针算法避免了类型转换和边界检查。
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
public byte[] ToBytes()
{
byte[] buffer = new byte[NumDi];
fixed(DiObject* pDi = Di)
fixed(byte* pBuff = buffer)
{
var pBuffDi = (DiObject*)pBuff;
var pDiPtr = pDi;
for (int i = 0; i < NumDi; i++)
*pBuffDi++ = *pDiPtr++;
}
return buffer;
}
public static MyPacket Create(byte[] buffer)
{
// argument checking code here
var packet = new MyPacket();
packet.ProtocolIdentifier = buffer[0];
packet.NumDi = buffer[1];
packet.Di = new DiObject[packet.NumDi];
fixed (byte* pBuf = buffer)
fixed (DiObject* pDi = packet.Di)
{
byte* pBufPtr = pBuf;
pBufPtr += 2;
var pBufDi = (DiObject*)pBufPtr;
var pDiPtr = pDi;
for (int i = 0; i < packet.NumDi; i++)
*pDiPtr++ = *pBufDi++;
}
return packet;
}
}
internal unsafe class Program
{
private static void Main(string[] args)
{
const string queuePath = @".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 5;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
packet.Di[2].Command = 6;
packet.Di[2].ErrorClass = 7;
packet.Di[2].Flags = 8;
packet.Di[2].Reserved = 9;
packet.Di[3].Command = 6;
packet.Di[3].ErrorClass = 7;
packet.Di[3].Flags = 8;
packet.Di[3].Reserved = 9;
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packet.ToBytes());
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath);
// Send the message to the queue
q.Send(msg);
}
}
}