今天我对关于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

说点什么
欢迎大家加入讨论(评论规则还在编写,咕咕咕)
由于博主学业繁忙,新评论可能需要很长时间才能过审,还请见谅
头像
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...