[英]Marshal struct with dynamic size array
我在整理具有动态大小数组的结构时遇到麻烦。 固定长度的数组很简单; 只需添加
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TheSizeOfTheArray)]
但是,当涉及到动态大小数组时,我很茫然。 为简单起见,我将忽略与代码无关的所有内容。
我正在发送此序列化结构的设备期望使用ushort告知数组的长度,然后是数组本身,最后是CRC。
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct MyNetworkMessage
{
public ushort Length { get; }
// This attribute does not work as I had hoped
[MarshalAs(UnmanagedType.ByValArray, SizeParamIndex = 1)]
private byte[] _messageData;
public ushort Crc { get; private set; }
public byte[] MessageData
{
get { return _data; }
private set { _data = value; }
}
public MyNetworkMessage(byte[] data)
{
MessageData = data;
Length = (ushort)data.Length;
Crc = Helper.CalcCrc(MessageData);
}
}
需要将该结构序列化为一个字节数组,该数组通过电线发送到另一个设备,其中前两个字节是数组的长度,后两个字节是MessageData的CRC:
Byte 0..1 Length of Data-field, N
Byte 2..N+2 Data
Byte N+3..N+4 CRC
我有很多这样的结构,需要序列化并以字节数组的形式通过电线发送,因此我需要一种通用的处理方法。 为这个示例创建一个正确的字节数组很简单,但是当每个结构都只包含简单数据时,我不想为每个结构编写序列化/反序列化。
我看到这里前面曾问过类似的问题,被标记为重复项,却没有令人满意的答案。
您可以编写自己的简单序列化逻辑。 您也可以编写自己的属性,以装饰要序列化的字段。
这是一个完整的可编译控制台应用程序,它演示了这个想法。
注意如何创建新的属性类NetworkSerialisationAttribute
,并使用它来装饰可序列化的字段。 它还使用反射来确定要序列化的字段。
此示例代码仅支持字节数组和原始类型,但足以让您入门。 (它还仅支持序列化字段,不支持属性,并且不进行反序列化-但从您所说的来看,我认为就足够了。)
这里的想法是避免写很多重复的序列化代码。 相反,您只需要使用[NetworkSerialisation]
属性来告诉它要序列化的内容。
注意,这里的大多数代码仅编写一次; 然后您可以将其放入库中,并用于所有数据传输类型。 例如,下面的代码中的MyNetworkMessage
和MyOtherNetworkMessage
代表数据传输类型。
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
namespace ConsoleApplication2
{
public enum SerialisationKind
{
Scalar,
Array
}
[MetadataAttribute]
public sealed class NetworkSerialisationAttribute: Attribute
{
public NetworkSerialisationAttribute(int ordinal, SerialisationKind kind = SerialisationKind.Scalar)
{
_ordinal = ordinal;
_kind = kind;
}
public SerialisationKind Kind // Array or scalar?
{
get
{
return _kind;
}
}
public int Ordinal // Defines the order in which fields should be serialized.
{
get
{
return _ordinal;
}
}
private readonly int _ordinal;
private readonly SerialisationKind _kind;
}
public static class NetworkSerialiser
{
public static byte[] Serialise<T>(T item)
{
using (var mem = new MemoryStream())
{
serialise(item, mem);
mem.Flush();
return mem.ToArray();
}
}
private static void serialise<T>(T item, Stream output)
{
var fields = item.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var orderedFields =
from field in fields
let attr = field.GetCustomAttribute<NetworkSerialisationAttribute>()
where attr != null
orderby attr.Ordinal
select new { field, attr.Kind };
foreach (var info in orderedFields)
{
if (info.Kind == SerialisationKind.Array)
serialiseArray(info.field.GetValue(item), output);
else
serialiseScalar(info.field.GetValue(item), output);
}
}
private static void serialiseArray(object value, Stream output)
{
var array = (byte[])value; // Only byte arrays are supported. This throws otherwise.
ushort length = (ushort) array.Length;
output.Write(BitConverter.GetBytes(length), 0, sizeof(ushort));
output.Write(array, 0, array.Length);
}
private static void serialiseScalar(object value, Stream output)
{
if (value is byte) // Byte is a special case; there is no BitConverter.GetBytes(byte value)
{
output.WriteByte((byte)value);
return;
}
// Hacky: Relies on the underlying type being a primitive type supported by one
// of the BitConverter.GetBytes() overloads.
var bytes = (byte[])
typeof (BitConverter)
.GetMethod("GetBytes", new [] {value.GetType()})
.Invoke(null, new[] {value});
output.Write(bytes, 0, bytes.Length);
}
}
// In this class, note the use of the [NetworkSerialization] attribute to indicate
// which fields should be serialised.
public sealed class MyNetworkMessage
{
public MyNetworkMessage(byte[] data)
{
_data = data;
_crc = 12345; // You should use Helper.CalcCrc(data);
}
public ushort Length
{
get
{
return (ushort)_data.Length;
}
}
public ushort Crc
{
get
{
return _crc;
}
}
public byte[] MessageData
{
get
{
return _data;
}
}
[NetworkSerialisation(0, SerialisationKind.Array)]
private readonly byte[] _data;
[NetworkSerialisation(1)]
private readonly ushort _crc;
}
// In this struct, note how the [NetworkSerialization] attribute is used to indicate the
// order in which the fields should be serialised.
public struct MyOtherNetworkMessage
{
[NetworkSerialisation(5)] public int Int1;
[NetworkSerialisation(6)] public int Int2;
[NetworkSerialisation(7)] public long Long1;
[NetworkSerialisation(8)] public long Long2;
[NetworkSerialisation(3)] public byte Byte1;
[NetworkSerialisation(4)] public byte Byte2;
[NetworkSerialisation(9)] public double Double1;
[NetworkSerialisation(10)] public double Double2;
[NetworkSerialisation(1)] public short Short1;
[NetworkSerialisation(2)] public short Short2;
public float ThisFieldWillNotBeSerialised;
public string AndNeitherWillThisOne;
}
class Program
{
private static void Main(string[] args)
{
var test1 = new MyNetworkMessage(new byte[10]);
var bytes1 = NetworkSerialiser.Serialise(test1);
Console.WriteLine(bytes1.Length + "\n");
var test2 = new MyOtherNetworkMessage
{
Short1 = 1,
Short2 = 2,
Byte1 = 3,
Byte2 = 4,
Int1 = 5,
Int2 = 6,
Long1 = 7,
Long2 = 8,
Double1 = 9,
Double2 = 10
};
var bytes2 = NetworkSerialiser.Serialise(test2);
Console.WriteLine(bytes2.Length);
foreach (byte b in bytes2)
{
Console.WriteLine(b);
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.