C#和C++結構體Socket通訊(2009-09-10 13:44:46)
轉載標籤: 雜談 分類: .net
C#和C++結構體Socket通訊
2008-10-27 05:59
最近在用C#做一個項目的時候,Socket發送訊息的時候遇到了服務端需要接收C++結構體的位元據流,這個時候就需要用C#仿照C++的結構體做出一個結構來,然後將其轉換成二進位流進行發送,之後將響應訊息的位元據流轉換成C#結構。
1、仿照C++結構體寫出C#的結構來
using System.Runtime.InteropServices;
[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 = 10000;
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、發送的時候先要把結構轉換成位元組數組
using System.Runtime.InteropServices;
/// <summary>
/// 將結構轉換為位元組數組
/// </summary>
/// <param name="obj">結構對象</param>
/// <returns>位元組數組</returns>
public 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 object BytesToStruct(byte[] bytes, Type type)
{
//得到結構的大小
int size = Marshal.SizeOf(type);
Log(size.ToString(), 1);
//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();