C#中結(jié)構(gòu)體定義并轉(zhuǎn)換字節(jié)數(shù)組詳解
最近的項(xiàng)目在做socket通信報(bào)文解析的時(shí)候,用到了結(jié)構(gòu)體與字節(jié)數(shù)組的轉(zhuǎn)換;由于客戶(hù)端采用C++開(kāi)發(fā),服務(wù)端采用C#開(kāi)發(fā),所以雙方必須保證各自定義結(jié)構(gòu)體成員類(lèi)型和長(zhǎng)度一致才能保證報(bào)文解析的正確性,這一點(diǎn)非常重要。
首先是結(jié)構(gòu)體定義,一些基本的數(shù)據(jù)類(lèi)型,C#與C++都是可以匹配的:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Head { public ushort proMagic; //包起始標(biāo)記:固定0x7e7e public ushort proPackLen; //包長(zhǎng)度:包頭 + 數(shù)據(jù)區(qū) + 包尾長(zhǎng)度,注意不要超過(guò)最大長(zhǎng)度限制 public long proSrcAddr; //源地址:不使用,填0 public ushort proSrcPort; //源地址端口:不使用,填0 public long proDstAddr; //目的地址:不使用,填0 public ushort proDstPort; //目的端口:不使用,填0 public ushort proCmdCode; //命令碼:參見(jiàn)以上命令碼定義 public ushort proVersion; //版本號(hào):不使用,填1 public char proSerial; //報(bào)文序號(hào):一條報(bào)文實(shí)例對(duì)應(yīng)一個(gè)序號(hào),不同報(bào)文疊加,0-255往復(fù) public ushort proPackSum; //總包數(shù):當(dāng)包長(zhǎng)超過(guò)最大長(zhǎng)度限制時(shí),需要拆包,大包拆小包總數(shù),不拆默認(rèn)1 public ushort proPackId; //當(dāng)前包號(hào):對(duì)應(yīng)以上總包數(shù)的小包標(biāo)識(shí),不拆默認(rèn)0 }
一、首先是 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)],這是C#引用非托管的C/C++的DLL的一種定義定義結(jié)構(gòu)體的方式,主要是為了內(nèi)存中排序,LayoutKind有兩個(gè)屬性Sequential和Explicit,Sequential表示順序存儲(chǔ),結(jié)構(gòu)體內(nèi)數(shù)據(jù)在內(nèi)存中都是順序存放的,CharSet=CharSet.Ansi表示編碼方式。這都是為了使用非托管的指針準(zhǔn)備的,這兩點(diǎn)大家記住就可以。
需要注意的是 Pack = 1 這個(gè)特性,它代表了結(jié)構(gòu)體的字節(jié)對(duì)齊方式,在實(shí)際開(kāi)發(fā)中,C++開(kāi)發(fā)環(huán)境開(kāi)始默認(rèn)是2字節(jié)對(duì)齊方式 ,拿上面報(bào)文包頭結(jié)構(gòu)體為例,char類(lèi)型在雖然在內(nèi)存中至占用一個(gè)字節(jié),但在結(jié)構(gòu)體轉(zhuǎn)為字節(jié)數(shù)組時(shí),系統(tǒng)會(huì)自動(dòng)補(bǔ)齊兩個(gè)字節(jié),所以如果C#這面定義為Pack=1,C++默認(rèn)為2字節(jié)對(duì)齊的話,雙方結(jié)構(gòu)體會(huì)出現(xiàn)長(zhǎng)度不一致的情況,相互轉(zhuǎn)換時(shí)必然會(huì)發(fā)生錯(cuò)位,所以需要大家都默認(rèn)1字節(jié)對(duì)齊的方式,C#定義Pack=1,C++ 添加 #pragma pack 1,保證結(jié)構(gòu)體中字節(jié)對(duì)齊方式一致。
二、數(shù)組的定義,結(jié)構(gòu)體中每個(gè)成員的長(zhǎng)度都是需要明確的,因?yàn)閮?nèi)存需要根據(jù)這個(gè)分配空間,而C#結(jié)構(gòu)體中數(shù)組是無(wú)法進(jìn)行初始化的,這里我們需要在成員聲明時(shí)進(jìn)行定義;
/// <summary> /// 終端信息查詢(xún) /// </summary> [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct PackTerminalSearch5001 { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)] /// <summary> /// 終端編號(hào) /// </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 PackTerminalSearch3004 { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)] /// <summary> /// 終端編號(hào) /// </summary> public string stationCode; /// <summary> /// 終端IP /// </summary> public long terminalIP; /// <summary> /// 終端端口 /// </summary> public ushort terminalPort; /// <summary> /// 中心IP /// </summary> public long serverIP; /// <summary> /// 測(cè)站端口 /// </summary> public ushort serverPort; /// <summary> /// 磁盤(pán)信息數(shù)組 /// </summary> [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public PackDiskInfo[] diskInfoArray; } /// <summary> /// 磁盤(pán)信息 /// </summary> [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct PackDiskInfo { /// <summary> /// 盤(pán)符 /// </summary> public char drive; /// <summary> /// 總空間 /// </summary> public double totalSize; /// <summary> /// 可用空間 /// </summary> public double usableSize; }
上面的代碼需要注意的是string類(lèi)型實(shí)際為Char[6]長(zhǎng)度的數(shù)組,實(shí)際使用中只能有效的使用前5個(gè)字符,因?yàn)閏har[6]最后一位默認(rèn)\0;
三、結(jié)構(gòu)體與字節(jié)數(shù)組的互轉(zhuǎn)
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> /// 結(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)體類(lèi)型</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ù)組長(zhǎng)度小于結(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)與類(lèi)有著驚人的相似度,但在實(shí)際應(yīng)用中,會(huì)常常因?yàn)橐恍┨厥庵?lèi)而錯(cuò)誤的使用它,下面幾點(diǎn)內(nèi)容是筆者認(rèn)為應(yīng)該注意的:
對(duì)于結(jié)構(gòu)
1)可以有方法與屬性
2)是密封的,不能被繼承,或繼承其他結(jié)構(gòu)
3)結(jié)構(gòu)隱式地繼承自System.ValueType
4)結(jié)構(gòu)有默認(rèn)的無(wú)參數(shù)構(gòu)造函數(shù),可以將每個(gè)字段初始化為默認(rèn)值,但這個(gè)默認(rèn)的構(gòu)造函數(shù)不能被替換,即使重載了帶參數(shù)的構(gòu)造函數(shù)
5)結(jié)構(gòu)沒(méi)有析構(gòu)函數(shù)
6)除了const成員外,結(jié)構(gòu)的字段不能在聲明結(jié)構(gòu)時(shí)初始化
7)結(jié)構(gòu)是值類(lèi)型,在定義時(shí)(盡管也使用new運(yùn)算符)會(huì)分配堆??臻g,其值也存儲(chǔ)于堆棧
8)結(jié)構(gòu)主要用于小的數(shù)據(jù)結(jié)構(gòu),為了更好的性能,不要使用過(guò)于龐大的結(jié)構(gòu)
9)可以像類(lèi)那樣為結(jié)構(gòu)提供 Close() 或 Dispose() 方法
如果經(jīng)常做通信方面的程序,結(jié)構(gòu)體是非常有用的(為了更有效地組織數(shù)據(jù),建議使用結(jié)構(gòu)體)
欄 目:C#教程
本文標(biāo)題:C#中結(jié)構(gòu)體定義并轉(zhuǎn)換字節(jié)數(shù)組詳解
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/5420.html
您可能感興趣的文章
- 01-10C#通過(guò)反射獲取當(dāng)前工程中所有窗體并打開(kāi)的方法
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10C#實(shí)現(xiàn)Winform中打開(kāi)網(wǎng)頁(yè)頁(yè)面的方法
- 01-10winform實(shí)現(xiàn)創(chuàng)建最前端窗體的方法
- 01-10C#實(shí)現(xiàn)實(shí)體類(lèi)與字符串互相轉(zhuǎn)換的方法
- 01-10C#實(shí)現(xiàn)由四周向中心縮小的窗體退出特效
- 01-10Extjs4如何處理后臺(tái)json數(shù)據(jù)中日期和時(shí)間
- 01-10C#實(shí)現(xiàn)主窗體最小化后出現(xiàn)懸浮框及雙擊懸浮框恢復(fù)原窗體的方
- 01-10C#實(shí)現(xiàn)更改MDI窗體背景顏色的方法
- 01-10C#實(shí)現(xiàn)打開(kāi)畫(huà)圖的同時(shí)載入圖片、最大化顯示畫(huà)圖窗體的方法


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10C#通過(guò)反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁(yè)無(wú)法打開(kāi)的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實(shí)現(xiàn)txt定位指定行完整實(shí)例
- 01-10WinForm實(shí)現(xiàn)仿視頻播放器左下角滾動(dòng)新
- 01-10C#停止線程的方法
- 01-10C#實(shí)現(xiàn)清空回收站的方法
- 01-10C#通過(guò)重寫(xiě)Panel改變邊框顏色與寬度的
- 01-10C#實(shí)現(xiàn)讀取注冊(cè)表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機(jī)閱讀
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什