【发布时间】:2018-04-05 09:55:41
【问题描述】:
如何在 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);
*/
}
}
}
【问题讨论】:
-
只是好奇 - 为什么不让
BinaryMessageFormatter序列化呢?是为了让信息更小吗? MSMQ 消息的开销已经大于消息内容本身,所以对于这么小的东西,它不太可能产生太大的影响。 -
如果我使用 BinaryMessageFormatter,它将数据存储为 340 字节而不是 16 字节。我想要尽可能小的数据包。这里的代码显示了这一点: // 打开或创建消息队列 if (!MessageQueue.Exists(queuePath)) MessageQueue.Create(queuePath); // 打开队列 var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()}; var message = new Message(packet, new BinaryMessageFormatter()); q.Send(message);
-
简而言之,我想要尽可能小的数据包,因为我想要存储大量的数字和模拟点。这个例子很小,只是为了简单。
-
不回答这个问题,但我建议只使用更紧凑的序列化协议(但不是
Marshal的东西)。例如,您可以使用 protobuf 将带有一个DiObject的数据包序列化为 14 个字节,这还不错。 -
让 Google
Protocol Buffers为您完成繁重的工作。它是标准的、被广泛采用的,并且完全能够以最少的代码要求解决您的问题(例如,您只需定义一个 .proto 合同文件)。