C#中結構體定義并轉換字節(jié)數組詳解
最近的項目在做socket通信報文解析的時候,用到了結構體與字節(jié)數組的轉換;由于客戶端采用C++開發(fā),服務端采用C#開發(fā),所以雙方必須保證各自定義結構體成員類型和長度一致才能保證報文解析的正確性,這一點非常重要。
首先是結構體定義,一些基本的數據類型,C#與C++都是可以匹配的:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct Head
{
public ushort proMagic; //包起始標記:固定0x7e7e
public ushort proPackLen; //包長度:包頭 + 數據區(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; //報文序號:一條報文實例對應一個序號,不同報文疊加,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 這個特性,它代表了結構體的字節(jié)對齊方式,在實際開發(fā)中,C++開發(fā)環(huán)境開始默認是2字節(jié)對齊方式 ,拿上面報文包頭結構體為例,char類型在雖然在內存中至占用一個字節(jié),但在結構體轉為字節(jié)數組時,系統(tǒng)會自動補齊兩個字節(jié),所以如果C#這面定義為Pack=1,C++默認為2字節(jié)對齊的話,雙方結構體會出現長度不一致的情況,相互轉換時必然會發(fā)生錯位,所以需要大家都默認1字節(jié)對齊的方式,C#定義Pack=1,C++ 添加 #pragma pack 1,保證結構體中字節(jié)對齊方式一致。
二、數組的定義,結構體中每個成員的長度都是需要明確的,因為內存需要根據這個分配空間,而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;
三、結構體與字節(jié)數組的互轉
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);
//創(chuàng)建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;
}
盡管在C#中結構與類有著驚人的相似度,但在實際應用中,會常常因為一些特殊之類而錯誤的使用它,下面幾點內容是筆者認為應該注意的:
對于結構
1)可以有方法與屬性
2)是密封的,不能被繼承,或繼承其他結構
3)結構隱式地繼承自System.ValueType
4)結構有默認的無參數構造函數,可以將每個字段初始化為默認值,但這個默認的構造函數不能被替換,即使重載了帶參數的構造函數
5)結構沒有析構函數
6)除了const成員外,結構的字段不能在聲明結構時初始化
7)結構是值類型,在定義時(盡管也使用new運算符)會分配堆??臻g,其值也存儲于堆棧
8)結構主要用于小的數據結構,為了更好的性能,不要使用過于龐大的結構
9)可以像類那樣為結構提供 Close() 或 Dispose() 方法
如果經常做通信方面的程序,結構體是非常有用的(為了更有效地組織數據,建議使用結構體)
您可能感興趣的文章
- 01-10C#通過反射獲取當前工程中所有窗體并打開的方法
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10C#實現Winform中打開網頁頁面的方法
- 01-10winform實現創(chuàng)建最前端窗體的方法
- 01-10C#實現實體類與字符串互相轉換的方法
- 01-10C#實現由四周向中心縮小的窗體退出特效
- 01-10Extjs4如何處理后臺json數據中日期和時間
- 01-10C#實現主窗體最小化后出現懸浮框及雙擊懸浮框恢復原窗體的方
- 01-10C#實現更改MDI窗體背景顏色的方法
- 01-10C#實現打開畫圖的同時載入圖片、最大化顯示畫圖窗體的方法


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關于ASP網頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現txt定位指定行完整實例
- 01-10WinForm實現仿視頻播放器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現讀取注冊表監(jiān)控當前操作系統(tǒng)已
隨機閱讀
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10C#中split用法實例總結
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11ajax實現頁面的局部加載
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢)副欄目數量限制代碼修改
- 08-05DEDE織夢data目錄下的sessions文件夾有什


