C#中结构体定义并转换字节数组详解

2019-12-30 18:54:00刘景俊

最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。

       首先是结构体定义,一些基本的数据类型,C#与C++都是可以匹配的:


  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct Head
  {
    public ushort proMagic;     //包起始标记:固定0x7e7e
    public ushort proPackLen;    //包长度:包头 + 数据区 + 包尾长度,注意不要超过最大长度限制
    public long  proSrcAddr;    //源地址:不使用,填0
    public ushort proSrcPort;    //源地址端口:不使用,填0
    public long  proDstAddr;    //目的地址:不使用,填0
    public ushort proDstPort;    //目的端口:不使用,填0
    public ushort proCmdCode;    //命令码:参见以上命令码定义

    public ushort proVersion;    //版本号:不使用,填1
    public char  proSerial;     //报文序号:一条报文实例对应一个序号,不同报文叠加,0-255往复
    public ushort proPackSum;    //总包数:当包长超过最大长度限制时,需要拆包,大包拆小包总数,不拆默认1
    public ushort proPackId;     //当前包号:对应以上总包数的小包标识,不拆默认0

  }

       一、首先是 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)],这是C#引用非托管的C/C++的DLL的一种定义定义结构体的方式,主要是为了内存中排序,LayoutKind有两个属性Sequential和Explicit,Sequential表示顺序存储,结构体内数据在内存中都是顺序存放的,CharSet=CharSet.Ansi表示编码方式。这都是为了使用非托管的指针准备的,这两点大家记住就可以。

       需要注意的是 Pack = 1 这个特性,它代表了结构体的字节对齐方式,在实际开发中,C++开发环境开始默认是2字节对齐方式 ,拿上面报文包头结构体为例,char类型在虽然在内存中至占用一个字节,但在结构体转为字节数组时,系统会自动补齐两个字节,所以如果C#这面定义为Pack=1,C++默认为2字节对齐的话,双方结构体会出现长度不一致的情况,相互转换时必然会发生错位,所以需要大家都默认1字节对齐的方式,C#定义Pack=1,C++ 添加 #pragma pack 1,保证结构体中字节对齐方式一致。

       二、数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;


  /// <summary>
  /// 终端信息查询
  /// </summary>
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch5001
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
    /// <summary>
    /// 终端编号
    /// </summary>
    public string stationCode;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    /// <summary>
    /// 回复指令
    /// </summary>
    public Byte[] order;
  }
  /// <summary>
  /// 终端信息数据
  /// </summary>

  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch3004
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
    /// <summary>
    /// 终端编号
    /// </summary>
    public string stationCode;
    /// <summary>
    /// 终端IP
    /// </summary>
    public long terminalIP;
    /// <summary>
    /// 终端端口
    /// </summary>
    public ushort terminalPort;
    /// <summary>
    /// 中心IP
    /// </summary>
    public long serverIP;
    /// <summary>
    /// 测站端口
    /// </summary>
    public ushort serverPort;
    /// <summary>
    /// 磁盘信息数组
    /// </summary>
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public PackDiskInfo[] diskInfoArray;
  }

  /// <summary>
  /// 磁盘信息
  /// </summary>
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackDiskInfo
  {
    /// <summary>
    /// 盘符
    /// </summary>
    public char drive;
    /// <summary>
    /// 总空间
    /// </summary>
    public double totalSize;
    /// <summary>
    /// 可用空间
    /// </summary>
    public double usableSize;
  }