C#中結構體定義並轉換字節數組


       最近的項目在做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;
    }

        上面的代碼需要注意的是string類型實際為Char[6]長度的數組,實際使用中只能有效的使用前5個字符,因為char[6]最后一位默認\0;

        三、結構體與字節數組的互轉

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



//// <summary> /// 結構體轉byte數組 /// </summary> /// <param name="structObj">要轉換的結構體</param> /// <returns>轉換后的byte數組</returns> public static byte[] StructToBytes(object structObj) { //得到結構體的大小 int size = Marshal.SizeOf(structObj); //創建byte數組 byte[] bytes = new byte[size]; //分配結構體大小的內存空間 IntPtr structPtr = Marshal.AllocHGlobal(size); //將結構體拷到分配好的內存空間 Marshal.StructureToPtr(structObj, structPtr, false); //從內存空間拷到byte數組 Marshal.Copy(structPtr, bytes, 0, size); //釋放內存空間 Marshal.FreeHGlobal(structPtr); //返回byte數組 return bytes; } /// <summary> /// byte數組轉結構體 /// </summary> /// <param name="bytes">byte數組</param> /// <param name="type">結構體類型</param> /// <returns>轉換后的結構體</returns> public static object BytesToStuct(byte[] bytes, Type type) { //得到結構體的大小 int size = Marshal.SizeOf(type); //byte數組長度小於結構體的大小 if (size > bytes.Length) { //返回空 return null; } //分配結構體大小的內存空間 IntPtr structPtr = Marshal.AllocHGlobal(size); //將byte數組拷到分配好的內存空間 Marshal.Copy(bytes, 0, structPtr, size); //將內存空間轉換為目標結構體 object obj = Marshal.PtrToStructure(structPtr, type); //釋放內存空間 Marshal.FreeHGlobal(structPtr); //返回結構體 return obj; }

 

 

 

歡迎轉載,轉載請注明原文出處(原博客地址),然后謝謝觀看。

 
        

如果覺得我的文章對您有幫助,請點擊推薦支持:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM