您的位置:首页 > 编程语言 > C#

C#中结构体定义并转换字节数组

2017-11-11 12:42 363 查看
最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。

首先是结构体定义,一些基本的数据类型,C#与C++都是可以匹配的:

[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
publicstructHead
{
publicushortproMagic;//包起始标记:固定0x7e7e
publicushortproPackLen;//包长度:包头+数据区+包尾长度,注意不要超过最大长度限制
publiclongproSrcAddr;//源地址:不使用,填0
publicushortproSrcPort;//源地址端口:不使用,填0
publiclongproDstAddr;//目的地址:不使用,填0
publicushortproDstPort;//目的端口:不使用,填0
publicushortproCmdCode;//命令码:参见以上命令码定义

publicushortproVersion;//版本号:不使用,填1
publiccharproSerial;//报文序号:一条报文实例对应一个序号,不同报文叠加,0-255往复
publicushortproPackSum;//总包数:当包长超过最大长度限制时,需要拆包,大包拆小包总数,不拆默认1
publicushortproPackId;//当前包号:对应以上总包数的小包标识,不拆默认0

}


一、首先是[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)],这是C#引用非托管的C/C++的DLL的一种定义定义结构体的方式,主要是为了内存中排序,LayoutKind有两个属性Sequential和Explicit,Sequential表示顺序存储,结构体内数据在内存中都是顺序存放的,CharSet=CharSet.Ansi表示编码方式。这都是为了使用非托管的指针准备的,这两点大家记住就可以。

需要注意的是Pack=1这个特性,它代表了结构体的字节对齐方式,在实际开发中,C++开发环境开始默认是2字节对齐方式,拿上面报文包头结构体为例,char类型在虽然在内存中至占用一个字节,但在结构体转为字节数组时,系统会自动补齐两个字节,所以如果C#这面定义为Pack=1,C++默认为2字节对齐的话,双方结构体会出现长度不一致的情况,相互转换时必然会发生错位,所以需要大家都默认1字节对齐的方式,C#定义Pack=1,C++添加#pragmapack1,保证结构体中字节对齐方式一致。

二、数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;

///<summary>
///终端信息查询
///</summary>
[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
publicstructPackTerminalSearch5001
{
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=6)]
///<summary>
///终端编号
///</summary>
publicstringstationCode;

[MarshalAs(UnmanagedType.ByValArray,SizeConst=6)]
///<summary>
///回复指令
///</summary>
publicByte[]order;
}
///<summary>
///终端信息数据
///</summary>

[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
publicstructPackTerminalSearch3004
{
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=6)]
///<summary>
///终端编号
///</summary>
publicstringstationCode;
///<summary>
///终端IP
///</summary>
publiclongterminalIP;
///<summary>
///终端端口
///</summary>
publicushortterminalPort;
///<summary>
///中心IP
///</summary>
publiclongserverIP;
///<summary>
///测站端口
///</summary>
publicushortserverPort;
///<summary>
///磁盘信息数组
///</summary>
[MarshalAs(UnmanagedType.ByValArray,SizeConst=8)]
publicPackDiskInfo[]diskInfoArray;
}

///<summary>
///磁盘信息
///</summary>
[StructLayoutAttribute(LayoutKind.Sequential,CharSet=CharSet.Ansi,Pack=1)]
publicstructPackDiskInfo
{
///<summary>
///盘符
///</summary>
publicchardrive;
///<summary>
///总空间
///</summary>
publicdoubletotalSize;
///<summary>
///可用空间
///</summary>
publicdoubleusableSize;
}


上面的代码需要注意的是string类型实际为Char[6]长度的数组,实际使用中只能有效的使用前5个字符,因为char[6]最后一位默认\0;

三、结构体与字节数组的互转

  

PackTerminalSearch5001info;
info.stationCode="12345";
info.order=newbyte[6]{0x00,0x01,0x02,0x03,0x04,0x05};
Byte[]recv=StructToBytes(info);

objectobj=BytesToStuct(recv,typeof(PackTerminalSearch5001));
PackTerminalSearch5001info5001=(PackTerminalSearch5001)obj;
byte[]order=info5001.order;

////<summary>
///结构体转byte数组
///</summary>
///<paramname="structObj">要转换的结构体</param>
///<returns>转换后的byte数组</returns>
publicstaticbyte[]StructToBytes(objectstructObj)
{
//得到结构体的大小
intsize=Marshal.SizeOf(structObj);
//创建byte数组
byte[]bytes=newbyte[size];
//分配结构体大小的内存空间
IntPtrstructPtr=Marshal.AllocHGlobal(size);
//将结构体拷到分配好的内存空间
Marshal.StructureToPtr(structObj,structPtr,false);
//从内存空间拷到byte数组
Marshal.Copy(structPtr,bytes,0,size);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
returnbytes;
}

///<summary>
///byte数组转结构体
///</summary>
///<paramname="bytes">byte数组</param>
///<paramname="type">结构体类型</param>
///<returns>转换后的结构体</returns>
publicstaticobjectBytesToStuct(byte[]bytes,Typetype)
{
//得到结构体的大小
intsize=Marshal.SizeOf(type);
//byte数组长度小于结构体的大小
if(size>bytes.Length)
{
//返回空
returnnull;
}
//分配结构体大小的内存空间
IntPtrstructPtr=Marshal.AllocHGlobal(size);
//将byte数组拷到分配好的内存空间
Marshal.Copy(bytes,0,structPtr,size);
//将内存空间转换为目标结构体
objectobj=Marshal.PtrToStructure(structPtr,type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
returnobj;
}



欢迎转载,转载请注明原文出处(原博客地址),然后谢谢观看。


如果觉得我的文章对您有帮助,请点击推荐支持:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: