最近在用C#做一個項目的時候,Socket發送消息的時候遇到了服務端需要接收C++結構體的二進制數據流,這個時候就需要用C#仿照C++的結構體做出一個結構來,然后將其轉換成二進制流進行發送,之后將響應消息的二進制數據流轉換成C#結構。
1、仿照C++結構體寫出C#的結構
[Serializable] // 指示可序列化 [StructLayout(LayoutKind.Sequential, Pack = 1)] // 按1字節對齊 public struct Operator { public ushort id; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] // 聲明一個字符數組,大小為11 public char[] name; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] public char[] pass; public Operator(string user, string pass) // 初始化 { this.id = 4105; this.name = user.PadRight(11, '\0').ToCharArray(); this.pass = pass.PadRight(9, '\0').ToCharArray(); } }
2、注意C#與C++數據類型的對應關系
C++與C#的數據類型對應關系表
|
|||||
API數據類型 | 類型描述 | C#類型 | API數據類型 | 類型描述 | C#類型 |
WORD | 16位無符號整數 | ushort | CHAR | 字符 | char |
LONG | 32位無符號整數 | int | DWORDLONG | 64位長整數 | long |
DWORD | 32位無符號整數 | uint | HDC | 設備描述表句柄 | int |
HANDLE | 句柄,32位整數 | int | HGDIOBJ | GDI對象句柄 | int |
UINT | 32位無符號整數 | uint | HINSTANCE | 實例句柄 | int |
BOOL | 32位布爾型整數 | bool | HWM | 窗口句柄 | int |
LPSTR | 指向字符的32位指針 | string | HPARAM | 32位消息參數 | int |
LPCSTR | 指向常字符的32位指針 | String | LPARAM | 32位消息參數 | int |
BYTE | 字節 | byte | WPARAM | 32位消息參數 | int |
整個結構的字節數是22bytes。
對應的C++結構體是:
typedef struct { WORD id; CHAR name[11]; CHAR password[9]; }Operator;
3、發送的時候先要把結構轉換成字節數組
/// <summary> /// 將結構轉換為字節數組 /// </summary> /// <param name="obj">結構對象</param> /// <returns>字節數組</returns> public static byte[] StructToBytes(object obj) { //得到結構體的大小 int size = Marshal.SizeOf(obj); //創建byte數組 byte[] bytes = new byte[size]; //分配結構體大小的內存空間 IntPtr structPtr = Marshal.AllocHGlobal(size); //將結構體拷到分配好的內存空間 Marshal.StructureToPtr(obj, 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 BytesToStruct(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; }
4、實際操作:
using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; byte[] Message = StructToBytes(new Operator("user","pass")); // 將結構轉換成字節數組 TcpClient socket = new TcpClient(); socket.Connect(ip,port); NetworkStream ns = Socket.GetStream(); ns.Write(Message,0,Message.Length); // 發送 byte[] Recv = new byte[1024]; // 緩沖 int NumberOfRecv = 0; IList<byte> newRecv = new List<byte>(); ns.ReadTimeout = 3000; try { do { // 接收響應 NumberOfRecv = ns.Read(Recv, 0, Recv.Length); for (int i = 0; i < NumberOfRecv; i++) newRecv.Add(Recv[i]); } while (ns.DataAvailable); byte[] resultRecv = new byte[newRecv.Count]; newRecv.CopyTo(resultRecv, 0); Operator MyOper = new Operator(); MyOper = (Operator)BytesToStruct(resultRecv, MyOper.GetType()); // 將字節數組轉換成結構
在這里取值的時候可能會出現只能取到一個字段,剩余的取不到的問題,怎么回事我也搞不懂,反正我的解決辦法就是按照字節的順序從resultRecv里分別取出對應的字段的字節數組,然后解碼,例如:
Operator.name是11個字節,最后一位是0,Operator.id是2個字節,那么從第3位到第12位的字節就是Operator.name的內容,取出另存為一個數組MyOperName,Encoding.Default.GetString(MyOperName)就是MyOper.name的內容。
socket.Close();
ns.Close();