2015-03-03 78 views
1

我无法使用动态大小数组来封装结构。定长阵列很简单;只是增加了带有动态大小数组的数组结构

[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 

我有很多不同的结构像这样需要被序列化并通过导线作为字节数组发送,因此处理这种通用的方式是我所追求的。为这个例子创建一个正确的字节数组非常简单,但我不希望为每个结构都编写序列化/反序列化,因为它们都只包含简单的数据。

我在这里看到过类似的问题,标记为重复,但没有看到任何满意的答案。

+0

[查核'MarshalAsAttribute.SizeParamIndex'](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshalasattribute.sizeparamindex%28v=vs.100%29) – 2015-03-03 09:44:20

+1

@MatthewWatson属性如何帮助动态大小数组?在通信时应该知道你传递给属性的参数。 – Spo1ler 2015-03-03 09:55:37

+0

@ Spo1ler我在想他可以在方法调用中使用它(它用于指示一个参数来动态指定数组大小),但实际上它看起来像是在手动序列化它。 – 2015-03-03 10:44:13

回答

2

您可以编写自己的简单序列化逻辑。你也可以编写你自己的属性来修饰你想要序列化的字段。

这是一个完整的可编译控制台应用程序,它演示了这个想法。

请注意它是如何创建一个新的属性类NetworkSerialisationAttribute,并用它来装饰可串行化的字段。它还使用反射来确定要序列化的字段。

此示例代码仅支持字节数组和原始类型,但它应该足以让您开始。 (它也只支持序列化字段,而不是属性,并且它不会执行反序列化 - 但是从您所说的我认为这就足够了。)

这里的想法是避免必须编写大量重复的序列化代码。相反,您只需使用[NetworkSerialisation]属性来告诉它要串行化。

请注意,这里的大部分代码只写入一次;那么你可以把它放在一个库中,并用它来处理所有的数据传输类型。例如,下面代码中的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); 
      } 
     } 
    } 
}