在写C#TCP通信程序时,发送数据时,只能发送byte数组,处理起来比较麻烦不说,如果是和VC6.0等写的程序通信的话,很多的都是传送结构体,在VC6.0中可以很方便的把一个char[]数组转换为一个结构体,而在C#却不能直接把byte数组转换为结构体,要在C#中发送结构体,可以按以下方法实现: (1)定义结构体: //命名空间
using System.Runtime.InteropServices; //注意这个属性不能少
[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)] struct TestStruct {
public int c;
//字符串,SizeConst为字符串的最大长度
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string str;
//int数组,SizeConst表示数组的个数,在转换成 //byte数组前必须先初始化数组,再使用,初始化
//的数组长度必须和SizeConst一致,例test = new int[6]; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] test; }
(2)结构体转byte数组: //// /// 结构体转byte数组 ///
/// 要转换的结构体 ///
public static byte[] StructToBytes(object structObj) {
//得到结构体的大小
int size = Marshal.SizeOf(structObj); //创建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;
}
(3)byte数组转结构体: /// /// byte数组转结构体 ///
/// byte数组 /// 结构体类型 ///
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#.NET和C++结构体Socket通信与数据转换
最近在用C#做一个项目的时候,Socket发送消息的时候遇到了服务端需要接收C++结构体的二进制数据流,这个时候就需要用C#仿照C++的结 构体做出一个结构来,然后将其转换成二进制流进行发送,之后将响应消息的二进制数据流转换成C#结构。 1、仿照C++结构体写出C#的结构来
1 using System.Runtime.InteropServices; 2
3 [Serializable] // 指示可序列化
4 [StructLayout(LayoutKind.Sequential, Pack = 1 )] // 按1字节对齐
5 public struct Operator 6 7 { 8 public ushort id; 9 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11 )] // 声明一个字符数组,大小为11 10 public char [] name; 11 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9 )] 12 public char [] pass; 13 14 public Operator( string user, string pass) // 初始化 15 { 16 this .id = 10000 ; 17 this .name = user.PadRight( 11 , ' /0 ' ).ToCharArray(); 18 this .pass = pass.PadRight( 9 , ' /0 ' ).ToCharArray(); 19 } 20 } 21 22 2、注意C#与C++数据类型的对应关系 C++与C#的数据类型对应关系表 API数据类型 类型描述 WORD LONG DWORD HANDLE UINT BOOL LPSTR LPCSTR BYTE 16位无符号整数 32位无符号整数 32位无符号整数 句柄,32位整数 32位无符号整数 32位布尔型整数 指向字符的32位指针 指向常字符的32位指针 字节 C#类型 API数据类型 ushort CHAR int uint int uint bool string String byte 类型描述 字符 C#类型 char long DWORDLONG 位长整数 HDC HGDIOBJ HINSTANCE HWM HPARAM LPARAM WPARAM 设备描述表句柄 int GDI对象句柄 实例句柄 窗口句柄 32位消息参数 32位消息参数 32位消息参数 int int int int int int 整个结构的字节数是22bytes。 对应的C++结构体是:
1 typedef struct 2 {
3 WORD id; 4 CHAR name[ 11 ]; 5 CHAR password[ 9 ]; 6 } Operator; 7 8
3、发送的时候先要把结构转换成字节数组
1 using System.Runtime.InteropServices; 2
3 ///
6 /// 结构对象 7 ///
10 // 得到结构体的大小
11 int size = Marshal.SizeOf(obj); 12 // 创建byte数组
13 byte [] bytes = new byte [size]; 14 // 分配结构体大小的内存空间
15 IntPtr structPtr = Marshal.AllocHGlobal(size); 16 // 将结构体拷到分配好的内存空间
17 Marshal.StructureToPtr(obj, structPtr, false ); 18 // 从内存空间拷到byte数组
19 Marshal.Copy(structPtr, bytes, 0 , size); 20 // 释放内存空间
21 Marshal.FreeHGlobal(structPtr);
22 // 返回byte数组 23 return bytes; 24 25 } 26 27
接收的时候需要把字节数组转换成结构
1 ///
4 /// byte数组 5 /// 结构类型 6 ///
7 public object BytesToStruct( byte [] bytes, Type type) 8 {
9 // 得到结构的大小
10 int size = Marshal.SizeOf(type); 11 Log(size.ToString(), 1 ); 12 // byte数组长度小于结构的大小 13 if (size > bytes.Length) 14 {
15 // 返回空 16 return null ; 17 }
18 // 分配结构大小的内存空间
19 IntPtr structPtr = Marshal.AllocHGlobal(size); 20 // 将byte数组拷到分配好的内存空间 21 Marshal.Copy(bytes, 0 , structPtr, size); 22 // 将内存空间转换为目标结构
23 object obj = Marshal.PtrToStructure(structPtr, type); 24 // 释放内存空间
25 Marshal.FreeHGlobal(structPtr); 26 // 返回结构
27 return obj; 28 } 29
4、实际操作:
1 using System.Collections; 2 using System.Collections.Generic; 3 using System.Net; 4 using System.Net.Sockets; 5
6 byte [] Message = StructToBytes( new Operator( \" user \" , \" pass \" )); // 将结构转换成字节数组 7
8 TcpClient socket = new TcpClient(); 9
10 socket.Connect(ip,port); 11
12 NetworkStream ns = Socket.GetStream(); 13
14 ns.Write(Message, 0 ,Message.Length); // 发送 15
16 byte [] Recv = new byte [ 1024 ]; // 缓冲 17
18 int NumberOfRecv = 0 ; 19
20 IList < byte > newRecv = new List < byte > (); 21 ns.ReadTimeout = 3000 ; 22 try 23 { 24 do 25 {
26 // 接收响应
27 NumberOfRecv = ns.Read(Recv, 0 , Recv.Length); 28 for ( int i = 0 ; i < NumberOfRecv; i ++ )
29 newRecv.Add(Recv[i]); 30 }
31 while (ns.DataAvailable);
32 byte [] resultRecv = new byte [newRecv.Count]; 33 newRecv.CopyTo(resultRecv, 0 ); 34
35 Operator MyOper = new Operator(); 36
37 MyOper = (Operator)BytesToStruct(resultRecv, MyOper.GetType()); // 将字节数组转换成结构 38
在这里取值的时候可能会出现只能取到一个字段,剩余的取不到的问题,怎么回事我也搞不懂,反正我的解决办法就是按照字节的顺序从 resultRecv里分别取出对应的字段的字节数组,然后解码,例如:
Operator.name是11个字节,最后一位是0,Operator.id是2个字节,那么从第3位到第12位的字节就是 Operator.name的内容,取出另存为一个数组 MyOperName,Encoding.Default.GetString(MyOperName)就是MyOper.name的内容。
1 socket.Close(); 2
3 ns.Close(); 4
在C++中,套接字发送和接收的类型为字符数组char[],而char[]与结构体struct可以直接进行显式转换就可以;
在C#中,Sockets类和NetworkStream类发送和接收的类型为字节数组byte[],而它与结构体的转换并不是非常方便,因此也就有了下文 知识储备:
(1) 需要用到Marshal类,该类提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
(2) 代码的输入和输出都是托管类型(疑问:结构是值类型,也是托管类型??) (3) 中间需要用非托管类型作为内容的中转; 下面是代码:
从结构体转到字节数组:
[csharp] view plaincopy
1. //byte[]是托管的,structObj是托管的,structPtr是非托管的 2. //从struct转换到byte[]
3. public static byte[] StructToBytes(object structObj) 4. {
5. //返回类的非托管大小(以字节为单位) 6. int size = Marshal.SizeOf(structObj); 7.
8. //分配大小
9. byte[] bytes = new byte[size]; 10.
11. //从进程的非托管堆中分配内存给structPtr
12. IntPtr structPtr = Marshal.AllocHGlobal(size); 13.
14. //将数据从托管对象structObj封送到非托管内存块structPtr 15. Marshal.StructureToPtr(structObj, structPtr, false); 16.
17. //Marshal.StructureToPtr(structObj, structPtr, true); 18. //将数据从非托管内存指针复制到托管 8 位无符号整数数组 19. Marshal.Copy(structPtr, bytes, 0, size); 20.
21. //释放以前使用 AllocHGlobal 从进程的非托管内存中分配的内存 22. Marshal.FreeHGlobal(structPtr); 23. return bytes; 24. }
从字节数组转到结构体(strType指明了所要转换的结构体类型):
[csharp] view plaincopy
1. //返回类型其实没什么用,从bytes转为strType类型的结构体 2. //从byte[]转换为struct
3. public static object BytesToStruct(byte[] bytes, Type strType) 4. {
5. //获取结构体的大小(以字节为单位)
6. int size = Marshal.SizeOf(strType); 7. //简单的判断(可以去掉) 8. if (size > bytes.Length) 9. {
10. return null; 11. } 12.
13. //从进程的非托管堆中分配内存给structPtr 14. IntPtr strPtr = Marshal.AllocHGlobal(size); 15.
16. //将数据从一维托管数组bytes复制到非托管内存指针strPtr 17. Marshal.Copy(bytes, 0, strPtr, size); 18.
19. //将数据从非托管内存块封送到新分配的指定类型的托管对象 20. //将内存空间转换为目标结构体
21. object obj = Marshal.PtrToStructure(strPtr, strType); 22.
23. //释放以前使用 AllocHGlobal 从进程的非托管内存中分配的内存 24. Marshal.FreeHGlobal(strPtr); 25. return obj; 26. }
从字节数组类型转为结构体类型(返回值从object直接转为了structType类型的对象)
[csharp] view plaincopy
1. //从字节数组转化为结构体
2. public StructType ConverBytesToStructure 4. // 检查长度 5. if (bytesBuffer.Length != Marshal.SizeOf(typeof(StructType))) 6. { 7. throw new ArgumentException(\"bytesBuffer参数和structObject参数字节长 度不一致。\"); 8. } 9. 10. //分配一个未托管类型变量 11. IntPtr bufferHandler = Marshal.AllocHGlobal(bytesBuffer.Length); 12. 13. //逐个复制,也可以直接用copy()方法 14. for (int index = 0; index < bytesBuffer.Length; index++) 15. { 16. Marshal.WriteByte(bufferHandler, index, bytesBuffer[index]); 17. } 18. 19. //从非托管类型转化为托管类型变量 20. StructType structObject = (StructType)Marshal.PtrToStructure(bufferHandl er, typeof(StructType)); 21. 22. //释放非托管类型变量 23. Marshal.FreeHGlobal(bufferHandler); 24. 25. return structObject; 26. }
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo9.cn 版权所有 赣ICP备2023008801号-1
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务