C#中結構體定義並轉換位元組數組

来源:http://www.cnblogs.com/dafanjoy/archive/2017/11/11/7818126.html
-Advertisement-
Play Games

最近的項目在做socket通信報文解析的時候,用到了結構體與位元組數組的轉換;由於客戶端採用C++開發,服務端採用C#開發,所以雙方必須保證各自定義結構體成員類型和長度一致才能保證報文解析的正確性,這一點非常重要。 首先是結構體定義,一些基本的數據類型,C#與C++都是可以匹配的: 一、首先是 [St ...


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

 

 

 

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


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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System. ...
  • 一般在介紹一樣新技術之前,我們都要大致講講它的歷史、主要原理等等。當然,這些東西很枯燥,很容易誘發我們的瞌睡蟲。但是不說,又不能讓人理解。好在不是太多。 如果您已經瞭解重構的定義、原理以及如何重構,那麼請跳過本小節。好了,書歸正傳。 返回總目錄 一、何謂重構(What) 視上下文的不同,重構有兩種定 ...
  • 一、入門簡介 在學習之前,要先瞭解ASP.NET Core是什麼?為什麼?很多人學習新技術功利心很重,恨不得立馬就學會了。 其實,那樣做很不好,馬馬虎虎,聯繫過程中又花費非常多的時間去解決所遇到的“問題”,是簡單的問題,對,就是簡單,就是因為覺得簡單被忽略的東西,恰恰這才是最重要的。 1、學習資料 ...
  • 【重構:改善既有代碼的設計】讀書筆記總目錄 1、重構原則 2、代碼的壞味道【1】 3、代碼的壞味道【2】 4、代碼的壞味道【3】 5、代碼的壞味道【4】 6、重構手法之Extrct Method(提煉函數)、Inline Method(內聯函數)、Inline Temp(內聯臨時變數) 7、重構手法 ...
  • 1.阿裡雲伺服器控制台,開啟1433埠(出入方向都要開)。自從微軟發佈linux版本後,控制台常用埠下拉列表也增加了1433. 2.如果你沒配置阿裡雲yum源,可參照配置一下。http://www.cnblogs.com/tdws/p/7183040.html 3.你必須 RHEL 7.3 或 ...
  • 之前寫了一個功能性的文件上傳asp.net core的小程式,加上點七七八八的東西,勉強能夠應付了,打算學習一下微軟的官方.NET CORE微服務示例 "https://github.com/dotnet architecture/eShopOnContainers" 。這個例子很全面地展現了微服務 ...
  • 一、值參數 未用ref或out修飾符聲明的參數為值參數。 使用值參數,通過將實參的值複製到形參的方式,把數據傳遞到方法。方法被調用時,系統做如下操作。 在棧中為形參分配空間。 複製實參到形參。 值參數的實參不一定是變數。它可以是任何能計算成相應數據類型的表達式。 看一個例子: 下麵來調用方法 在把變 ...
  • 一、莊贏會員管理系統是個什麼樣的系統? 莊贏會員管理系統主是幫助莊家管理會員。 二、現在莊家運營的模式是什麼? 莊家運營網站的程式和資料庫都在上家的伺服器上,程式和資料庫都是由上家進行維護的,莊家只有一個管理後臺,說白了玩家的所有數據包括網站的所有數據都不在莊家自己手裡,莊家自然是不放心了。 三、目 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...