文章摘要
今天我对关于C#与C++之间Socket的联动产生的一系列问题进行记录,方便以后使用。 问题的罗列以及解决措施按照时间顺序排布 C#套接字如何发送结构体 为了方便数据的分析,我写的C++服务器将数据封装成了512字节的数据包,并使用一个结构体存储,因此C#客户端在发送数据时也需要发送同样格式的结构体,以便于服务器识别客户端发送的数据。 C++服务器的结构体的定义如下: //用于存储客户端消息 st…
今天我对关于C#与C++之间Socket的联动产生的一系列问题进行记录,方便以后使用。 问题的罗列以及解决措施按照时间顺序排布 C#套接字如何发送结构体 为了方便数据的分析,我写的C++服务器将数据封装成了512字节的数据包,并使用一个结构体存储,因此C#客户端在发送数据时也需要发送同样格式的结构体,以便于服务器识别客户端发送的数据。 C++服务器的结构体的定义如下: //用于存储客户端消息 struct ClientMsg { int iFlag = 0;//待定,用于标记数据包类型,暂时不使用 short iOperation = 0;//客户端需要进行的操作,如获取服务器版本信息 char sMsg[506];//额外消息 }; C#的结构体定义如下 struct ClientMsg { public int iFlag; public short iOperation; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 506)] public char[] sMsg; }; 根据代码服务器中sMsg是一个定长字符数组,那么对应的客户端也应该是定长数组,可C#不支持在结构体内声明数据长度,也不能够提前实例化结构体内的属性或字段,这倒是和C++有一定的区别 除了这个问题之外,我还想将这个结构体通过C#的套接字发送出去,可C#只支持发送出字节数组,这的确是一个棘手的问题 解决方案 使用InteropServices库 导入这个库的方法 using System.Runtime.InteropServices; 我主要用到了Marsha类。 首先先解决结构体大小固定的问题 很简单,在要固定大小的那个数组上面输入 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 506)] 其中SizeConst就是数组大小 其次是结构体转换成字节数组的问题,我依旧适用Marsha类实现 我首先声明了一个ClientMsg结构体类型的数据cm 然后对它进行初始化(常规数据操作) ClientMsg cm = new ClientMsg(); cm.iFlag = 1; cm.iOperation = 2; cm.sMsg = new char[506]; 这里衍生出来一个问题,如何保持sMsg数组大小不变的情况下,为他赋值 已知C#数组初始化之后就只能下标赋值,难道我要一个一个元素赋过去,显然不太现实。 用Array.Copy可以解决这个问题,当然也可以自己写一个函数,怎么写这里就不赘述了 具体代码如下: string strmsg = "TEST001"; Array.Copy(strmsg.ToCharArray(), cm.sMsg, strmsg.Length); 好了,回到结构体转字节数组的问题,我先给出代码,然后再给出具体解释 byte[] buf = new byte[512];//字节数组 IntPtr p = Marshal.AllocHGlobal(512);//手动申请一块内存 Marshal.StructureToPtr(cm, p, false);//将结构体写入内存 Marshal.Copy(p, buf, 0, 512);//将内存内容复制到字节数组Marshal.FreeHGlobal(p);//释放内存 这里主要用到了四个函数(取自MSDN,后面有我的个人理解) Marshal.AllocHGlobal 方法 从进程的非托管内存中分配内存。 重载 AllocHGlobal(Int32) 通过使用指定的字节数,从进程的非托管内存中分配内存。 AllocHGlobal(IntPtr) 通过使用指向指定字节数的指针,从进程的非托管内存中分配内存。 Marshal.StructureToPtr 方法 将数据从托管对象封送到非托管内存块。 重载 StructureToPtr(Object, IntPtr, Boolean) 将数据从托管对象封送到非托管内存块。 StructureToPtr T (T, IntPtr, Boolean) [在 .NET Framework 4.5.1 和更高版本中受支持]将数据从指定类型的托管对象封送到非托管内存块。 这里有一个重难点 StructureToPtr(Object, IntPtr, Boolean) 函数形参列表中有一个bool类型,编译器给的解释是-- 实际上很好理解,我用举例子的方法来方便理解 若非托管内存块(也就是我们手动分配的内存)中已存在结构体,且结构体是这样子的 Struct data { Int i; } Int类型是C#自带的数据结构,回收什么的不需要管,系统会自动回收。既然会自动回收,那么我们在将新的结构体写入这块内存中的时候,就不需要手动解除这块内存,系统会帮我们自动回收的。所以这个时候的布尔值填false 而当非托管内存块(也就是我们手动分配的内存)中结构体是这个样子的: Struct data { Struct2* p; } 如果我们直接将新的结构体覆盖掉这块内存上原有的结构体,我想很容易就能想到p指向的数据再也没办法管理了,并且系统还不会自动回收,这部分内存就溢出了,所以要在写入数据之前Free掉这部分内存才不会导致内存溢出,这个时候的布尔值为true 总的来说这个形参的意义也不是那么难分析的 Marshal.Copy 方法 将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。 这个函数的重载比较多,我只讲我用的这个函数 Copy(IntPtr, Byte[], Int32, Int32) 备注:将数据从非托管内存指针复制到托管 8 位无符号整数数组。 参数 source IntPtr 从中进行复制的内存指针。 destination Byte [] 要复制到的数组。 startIndex Int32 目标数组中从零开始的索引,在此处开始复制。 length Int32 要复制的数组元素的数目。 因为我设置的结构体大小为512字节,并且将所有结构体数据复制到字节数组,所以我的代码这么写 Marshal.Copy(p, buf, 0, 512); 当结构体成功转为字节数组时,我们也就可以释放这块用Marshal手动申请的内存了 于是,我们调用Marshal.FreeHGlobal(p);释放掉内存 现在我们运行一下,看看结果是否如意 C++服务器: C#客户端: Yes,大获成功 --转载请申明九仞博客 --这篇文章少不了度娘的帮助,感谢下面给我提供解决思路的链接: https://bbs.csdn.net/topics/391896950?page=1#post-410627559 https://blog.csdn.net/bluishglc/article/details/6564971今天我对关于C#与C++之间Socket的联动产生的一系列问题进行记录,方便以后使用。
问题的罗列以及解决措施按照时间顺序排布
C#套接字如何发送结构体
为了方便数据的分析,我写的C++服务器将数据封装成了512字节的数据包,并使用一个结构体存储,因此C#客户端在发送数据时也需要发送同样格式的结构体,以便于服务器识别客户端发送的数据。
C++服务器的结构体的定义如下:
//用于存储客户端消息
struct ClientMsg
{
int iFlag = 0;//待定,用于标记数据包类型,暂时不使用
short iOperation = 0;//客户端需要进行的操作,如获取服务器版本信息
char sMsg[506];//额外消息
};C#的结构体定义如下
struct ClientMsg
{
public int iFlag;
public short iOperation;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 506)]
public char[] sMsg;
};根据代码服务器中sMsg是一个定长字符数组,那么对应的客户端也应该是定长数组,可C#不支持在结构体内声明数据长度,也不能够提前实例化结构体内的属性或字段,这倒是和C++有一定的区别
除了这个问题之外,我还想将这个结构体通过C#的套接字发送出去,可C#只支持发送出字节数组,这的确是一个棘手的问题
解决方案
使用InteropServices库
导入这个库的方法 using System.Runtime.InteropServices;
我主要用到了Marsha类。
首先先解决结构体大小固定的问题
很简单,在要固定大小的那个数组上面输入
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 506)]
其中SizeConst就是数组大小
其次是结构体转换成字节数组的问题,我依旧适用Marsha类实现
我首先声明了一个ClientMsg结构体类型的数据cm
然后对它进行初始化(常规数据操作)
ClientMsg cm = new ClientMsg(); cm.iFlag = 1; cm.iOperation = 2; cm.sMsg = new char[506];
这里衍生出来一个问题,如何保持sMsg数组大小不变的情况下,为他赋值
已知C#数组初始化之后就只能下标赋值,难道我要一个一个元素赋过去,显然不太现实。
用Array.Copy可以解决这个问题,当然也可以自己写一个函数,怎么写这里就不赘述了
具体代码如下:
string strmsg = "TEST001"; Array.Copy(strmsg.ToCharArray(), cm.sMsg, strmsg.Length);
好了,回到结构体转字节数组的问题,我先给出代码,然后再给出具体解释
byte[] buf = new byte[512];//字节数组 IntPtr p = Marshal.AllocHGlobal(512);//手动申请一块内存 Marshal.StructureToPtr(cm, p, false);//将结构体写入内存 Marshal.Copy(p, buf, 0, 512);//将内存内容复制到字节数组Marshal.FreeHGlobal(p);//释放内存
这里主要用到了四个函数(取自MSDN,后面有我的个人理解)
Marshal.AllocHGlobal 方法
从进程的非托管内存中分配内存。
重载
| AllocHGlobal(Int32) | 通过使用指定的字节数,从进程的非托管内存中分配内存。 |
| AllocHGlobal(IntPtr) | 通过使用指向指定字节数的指针,从进程的非托管内存中分配内存。 |
Marshal.StructureToPtr 方法
将数据从托管对象封送到非托管内存块。
重载
| StructureToPtr(Object, IntPtr, Boolean) | 将数据从托管对象封送到非托管内存块。 |
| StructureToPtr<T>(T, IntPtr, Boolean) | [在 .NET Framework 4.5.1 和更高版本中受支持]将数据从指定类型的托管对象封送到非托管内存块。 |
这里有一个重难点
StructureToPtr(Object, IntPtr, Boolean)函数形参列表中有一个bool类型,编译器给的解释是--
实际上很好理解,我用举例子的方法来方便理解
若非托管内存块(也就是我们手动分配的内存)中已存在结构体,且结构体是这样子的
Struct data
{
Int i;
} Int类型是C#自带的数据结构,回收什么的不需要管,系统会自动回收。既然会自动回收,那么我们在将新的结构体写入这块内存中的时候,就不需要手动解除这块内存,系统会帮我们自动回收的。所以这个时候的布尔值填false
而当非托管内存块(也就是我们手动分配的内存)中结构体是这个样子的:
Struct data
{
Struct2* p;
} 如果我们直接将新的结构体覆盖掉这块内存上原有的结构体,我想很容易就能想到p指向的数据再也没办法管理了,并且系统还不会自动回收,这部分内存就溢出了,所以要在写入数据之前Free掉这部分内存才不会导致内存溢出,这个时候的布尔值为true
总的来说这个形参的意义也不是那么难分析的
Marshal.Copy 方法
将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。
这个函数的重载比较多,我只讲我用的这个函数
Copy(IntPtr, Byte[], Int32, Int32)
备注:将数据从非托管内存指针复制到托管 8 位无符号整数数组。
参数
source
从中进行复制的内存指针。
destination
Byte[]
要复制到的数组。
startIndex
目标数组中从零开始的索引,在此处开始复制。
length
要复制的数组元素的数目。
因为我设置的结构体大小为512字节,并且将所有结构体数据复制到字节数组,所以我的代码这么写
Marshal.Copy(p, buf, 0, 512);
当结构体成功转为字节数组时,我们也就可以释放这块用Marshal手动申请的内存了
于是,我们调用Marshal.FreeHGlobal(p);释放掉内存
现在我们运行一下,看看结果是否如意
C++服务器:
C#客户端:
Yes,大获成功
--转载请申明九仞博客
--这篇文章少不了度娘的帮助,感谢下面给我提供解决思路的链接:
本文采用 CC BY-NC-SA 4.0 协议进行授权,如无注明均为原创,转载请注明出处。
标题:C# Socket的结构体传输问题作者:九仞之行链接:http://dev.styunlen.cn/archives/post-203.html







还没有评论呢 ~ 快来抢沙发吧 🛋️