溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#中結(jié)構(gòu)體定義并轉(zhuǎn)換字節(jié)數(shù)組詳解

發(fā)布時間:2020-09-28 07:16:20 來源:腳本之家 閱讀:241 作者:dafanjoy 欄目:編程語言

最近的項(xiàng)目在做socket通信報文解析的時候,用到了結(jié)構(gòu)體與字節(jié)數(shù)組的轉(zhuǎn)換;由于客戶端采用C++開發(fā),服務(wù)端采用C#開發(fā),所以雙方必須保證各自定義結(jié)構(gòu)體成員類型和長度一致才能保證報文解析的正確性,這一點(diǎn)非常重要。

       首先是結(jié)構(gòu)體定義,一些基本的數(shù)據(jù)類型,C#與C++都是可以匹配的:

  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct Head
  {
    public ushort proMagic;     //包起始標(biāo)記:固定0x7e7e
    public ushort proPackLen;    //包長度:包頭 + 數(shù)據(jù)區(qū) + 包尾長度,注意不要超過最大長度限制
    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;     //報文序號:一條報文實(shí)例對應(yīng)一個序號,不同報文疊加,0-255往復(fù)
    public ushort proPackSum;    //總包數(shù):當(dāng)包長超過最大長度限制時,需要拆包,大包拆小包總數(shù),不拆默認(rèn)1
    public ushort proPackId;     //當(dāng)前包號:對應(yīng)以上總包數(shù)的小包標(biāo)識,不拆默認(rèn)0

  }

       一、首先是 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)],這是C#引用非托管的C/C++的DLL的一種定義定義結(jié)構(gòu)體的方式,主要是為了內(nèi)存中排序,LayoutKind有兩個屬性Sequential和Explicit,Sequential表示順序存儲,結(jié)構(gòu)體內(nèi)數(shù)據(jù)在內(nèi)存中都是順序存放的,CharSet=CharSet.Ansi表示編碼方式。這都是為了使用非托管的指針準(zhǔn)備的,這兩點(diǎn)大家記住就可以。

       需要注意的是 Pack = 1 這個特性,它代表了結(jié)構(gòu)體的字節(jié)對齊方式,在實(shí)際開發(fā)中,C++開發(fā)環(huán)境開始默認(rèn)是2字節(jié)對齊方式 ,拿上面報文包頭結(jié)構(gòu)體為例,char類型在雖然在內(nèi)存中至占用一個字節(jié),但在結(jié)構(gòu)體轉(zhuǎn)為字節(jié)數(shù)組時,系統(tǒng)會自動補(bǔ)齊兩個字節(jié),所以如果C#這面定義為Pack=1,C++默認(rèn)為2字節(jié)對齊的話,雙方結(jié)構(gòu)體會出現(xiàn)長度不一致的情況,相互轉(zhuǎn)換時必然會發(fā)生錯位,所以需要大家都默認(rèn)1字節(jié)對齊的方式,C#定義Pack=1,C++ 添加 #pragma pack 1,保證結(jié)構(gòu)體中字節(jié)對齊方式一致。

       二、數(shù)組的定義,結(jié)構(gòu)體中每個成員的長度都是需要明確的,因?yàn)閮?nèi)存需要根據(jù)這個分配空間,而C#結(jié)構(gòu)體中數(shù)組是無法進(jìn)行初始化的,這里我們需要在成員聲明時進(jìn)行定義;

  /// <summary>
  /// 終端信息查詢
  /// </summary>
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch6001
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
    /// <summary>
    /// 終端編號
    /// </summary>
    public string stationCode;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    /// <summary>
    /// 回復(fù)指令
    /// </summary>
    public Byte[] order;
  }
  /// <summary>
  /// 終端信息數(shù)據(jù)
  /// </summary>

  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  public struct PackTerminalSearch4004
  {
    [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>
    /// 磁盤信息數(shù)組
    /// </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;
  }


 

        上面的代碼需要注意的是string類型實(shí)際為Char[6]長度的數(shù)組,實(shí)際使用中只能有效的使用前5個字符,因?yàn)閏har[6]最后一位默認(rèn)\0;

        三、結(jié)構(gòu)體與字節(jié)數(shù)組的互轉(zhuǎn)

    PackTerminalSearch6001 info;
    info.stationCode = "12345";
    info.order = new byte[6] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
    Byte[] recv = StructToBytes(info);

    object obj = BytesToStuct(recv, typeof(PackTerminalSearch6001));
    PackTerminalSearch6001 info5001 = (PackTerminalSearch6001)obj;
    byte[] order = info5001.order;


    //// <summary>
    /// 結(jié)構(gòu)體轉(zhuǎn)byte數(shù)組
    /// </summary>
    /// <param name="structObj">要轉(zhuǎn)換的結(jié)構(gòu)體</param>
    /// <returns>轉(zhuǎn)換后的byte數(shù)組</returns>
    public static byte[] StructToBytes(object structObj)
    {
      //得到結(jié)構(gòu)體的大小
      int size = Marshal.SizeOf(structObj);
      //創(chuàng)建byte數(shù)組
      byte[] bytes = new byte[size];
      //分配結(jié)構(gòu)體大小的內(nèi)存空間
      IntPtr structPtr = Marshal.AllocHGlobal(size);
      //將結(jié)構(gòu)體拷到分配好的內(nèi)存空間
      Marshal.StructureToPtr(structObj, structPtr, false);
      //從內(nèi)存空間拷到byte數(shù)組
      Marshal.Copy(structPtr, bytes, 0, size);
      //釋放內(nèi)存空間
      Marshal.FreeHGlobal(structPtr);
      //返回byte數(shù)組
      return bytes;
    }

    /// <summary>
    /// byte數(shù)組轉(zhuǎn)結(jié)構(gòu)體
    /// </summary>
    /// <param name="bytes">byte數(shù)組</param>
    /// <param name="type">結(jié)構(gòu)體類型</param>
    /// <returns>轉(zhuǎn)換后的結(jié)構(gòu)體</returns>
    public static object BytesToStuct(byte[] bytes, Type type)
    {
      //得到結(jié)構(gòu)體的大小
      int size = Marshal.SizeOf(type);
      //byte數(shù)組長度小于結(jié)構(gòu)體的大小
      if (size > bytes.Length)
      {
        //返回空
        return null;
      }
      //分配結(jié)構(gòu)體大小的內(nèi)存空間
      IntPtr structPtr = Marshal.AllocHGlobal(size);
      //將byte數(shù)組拷到分配好的內(nèi)存空間
      Marshal.Copy(bytes, 0, structPtr, size);
      //將內(nèi)存空間轉(zhuǎn)換為目標(biāo)結(jié)構(gòu)體
      object obj = Marshal.PtrToStructure(structPtr, type);
      //釋放內(nèi)存空間
      Marshal.FreeHGlobal(structPtr);
      //返回結(jié)構(gòu)體
      return obj;
    }

盡管在C#中結(jié)構(gòu)與類有著驚人的相似度,但在實(shí)際應(yīng)用中,會常常因?yàn)橐恍┨厥庵惗e誤的使用它,下面幾點(diǎn)內(nèi)容是筆者認(rèn)為應(yīng)該注意的:

對于結(jié)構(gòu)

1)可以有方法與屬性
2)是密封的,不能被繼承,或繼承其他結(jié)構(gòu)
3)結(jié)構(gòu)隱式地繼承自System.ValueType
4)結(jié)構(gòu)有默認(rèn)的無參數(shù)構(gòu)造函數(shù),可以將每個字段初始化為默認(rèn)值,但這個默認(rèn)的構(gòu)造函數(shù)不能被替換,即使重載了帶參數(shù)的構(gòu)造函數(shù)
5)結(jié)構(gòu)沒有析構(gòu)函數(shù)
6)除了const成員外,結(jié)構(gòu)的字段不能在聲明結(jié)構(gòu)時初始化
7)結(jié)構(gòu)是值類型,在定義時(盡管也使用new運(yùn)算符)會分配堆??臻g,其值也存儲于堆棧
8)結(jié)構(gòu)主要用于小的數(shù)據(jù)結(jié)構(gòu),為了更好的性能,不要使用過于龐大的結(jié)構(gòu)
9)可以像類那樣為結(jié)構(gòu)提供 Close() 或 Dispose() 方法

如果經(jīng)常做通信方面的程序,結(jié)構(gòu)體是非常有用的(為了更有效地組織數(shù)據(jù),建議使用結(jié)構(gòu)體)

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI