简体   繁体   English

具有动态大小数组的元帅结构

[英]Marshal struct with dynamic size array

I am having trouble marshalling a struct with a dynamic size array. 我在整理具有动态大小数组的结构时遇到麻烦。 The fixed length arrays are simple; 固定长度的数组很简单; just add 只需添加

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

However, I'm at a loss when it comes to dynamic size arrays. 但是,当涉及到动态大小数组时,我很茫然。 For simplicity, I will omit everything that's not relevant in my code. 为简单起见,我将忽略与代码无关的所有内容。

The device I am sending this serialized struct to expects an ushort to inform about the length of the array, followed by the array itself, and a CRC at the end. 我正在发送此序列化结构的设备期望使用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);
    }
}

This struct needs to be serialized to a byte array which is sent over the wire to another device, where the first two bytes is the length of the array, and the last two bytes is the CRC of the MessageData: 需要将该结构序列化为一个字节数组,该数组通过电线发送到另一个设备,其中前两个字节是数组的长度,后两个字节是MessageData的CRC:

Byte 0..1       Length of Data-field, N
Byte 2..N+2     Data
Byte N+3..N+4   CRC

I have many different structs like this that need to be serialized and sent over the wire as byte arrays, so a generic way of handling this is what I am after. 我有很多这样的结构,需要序列化并以字节数组的形式通过电线发送,因此我需要一种通用的处理方法。 Creating a correct byte array for this one example is simple enough, but I don't want to have to write serialization/deserialization for each and every struct, when they all only contain simple data. 为这个示例创建一个正确的字节数组很简单,但是当每个结构都只包含简单数据时,我不想为每个结构编写序列化/反序列化。

I have seen similar questions asked earlier here, marked as duplicates, without seeing any satisfactory answers. 我看到这里前面曾问过类似的问题,被标记为重复项,却没有令人满意的答案。

You could write your own simple serialization logic. 您可以编写自己的简单序列化逻辑。 You could also write your own attribute with which to decorate the fields that you want to be serialised. 您也可以编写自己的属性,以装饰要序列化的字段。

Here's a complete compilable console application which demonstrates this idea. 这是一个完整的可编译控制台应用程序,它演示了这个想法。

Note how it creates a new attribute class, NetworkSerialisationAttribute , and uses it to decorate the serialisable fields. 注意如何创建新的属性类NetworkSerialisationAttribute ,并使用它来装饰可序列化的字段。 It also uses reflection to determine what fields to serialise. 它还使用反射来确定要序列化的字段。

This sample code only supports byte arrays and primitive types, but it should be enough to get you started. 此示例代码仅支持字节数组和原始类型,但足以让您入门。 (It also only supports serialising fields, not properties, and it doesn't do deserialisation - but from what you said I think that would be enough.) (它还仅支持序列化字段,不支持属性,并且不进行反序列化-但从您所说的来看,我认为就足够了。)

The idea here is to avoid having to write lots of repetitive serialisation code. 这里的想法是避免写很多重复的序列化代码。 Instead, you just use the [NetworkSerialisation] attribute to tell it what to serialise. 相反,您只需要使用[NetworkSerialisation]属性来告诉它要序列化的内容。

Note that most of the code here is only written once; 注意,这里的大多数代码仅编写一次; then you can put it in a library and use it for all your data transfer types. 然后您可以将其放入库中,并用于所有数据传输类型。 For example, MyNetworkMessage and MyOtherNetworkMessage in the code below represent the data transfer types. 例如,下面的代码中的MyNetworkMessageMyOtherNetworkMessage代表数据传输类型。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM