最近在用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();









