最近的项目在做插座通信报文解析的时候,用到了结构体与字节数组的转换,由于客户端采用c++开发,服务端采用c#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。
,,,,,,首先是结构体定义,一些基本的数据类型,c#与c++都是可以匹配的:
[StructLayoutAttribute LayoutKind。连续的,CharSet=字符集。Ansi,包=1) 公共结构头 { 公共ushort proMagic;//包起始标记:固定0 x7e7e 公共ushort proPackLen;//包长度:包头+数据区+包尾长度,注意不要超过最大长度限制 公共长proSrcAddr;//源地址:不使用,填0 公共ushort proSrcPort;//源地址端口:不使用,填0 公共长proDstAddr;//目的地址:不使用,填0 公共ushort proDstPort;//目的端口:不使用,填0 公共ushort proCmdCode;//命令码:参见以上命令码定义 公共ushort proVersion;//版本号:不使用,填1 公共字符proSerial;//报文序号:一条报文实例对应一个序号,不同报文叠加,0 - 255往复 公共ushort proPackSum;//总包数:当包长超过最大长度限制时,需要拆包,大包拆小包总数,不拆默认1 公共ushort proPackId;//当前包号:对应以上总包数的小包标识,不拆默认0 } >之前,,,,,,一,首先是[StructLayoutAttribute LayoutKind。连续的,CharSet=字符集。Ansi,包=1),这是c#引用非托管的C/c++的DLL的一种定义定义结构体的方式,主要是为了内存中排序,LayoutKind有两个属性顺序和明确的,顺序表示顺序存储,结构体内数据在内存中都是顺序存放的,CharSet=CharSet.Ansi表示编码方式。这都是为了使用非托管的指针准备的,这两点大家记住就可以。
,,,,,,需要注意的是包=1这个特性,它代表了结构体的字节对齐方式,在实际开发中,c++开发环境开始默认是2字节对齐方式,拿上面报文包头结构体为例,字符类型在虽然在内存中至占用一个字节,但在结构体转为字节数组时,系统会自动补齐两个字节,所以如果c#这面定义为包=1,c++默认为2字节对齐的话,双方结构体会出现长度不一致的情况,相互转换时必然会发生错位,所以需要大家都默认1字节对齐的方式,c#定义包=1,c++添加# pragma Pack 1,保证结构体中字节对齐方式一致。
,,,,,,二、数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而c#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;
///& lt; summary>///终端信息查询///& lt;/summary> [StructLayoutAttribute LayoutKind。连续的,CharSet=字符集。Ansi,包=1) 公共struct PackTerminalSearch6001 { [MarshalAs UnmanagedType。ByValTStr SizeConst=6)]///& lt; summary>///终端编的号///& lt;/summary> 公共字符串stationCode; [MarshalAs UnmanagedType。ByValArray SizeConst=6)]///& lt; summary>///回复指令///& lt;/summary> 公共Byte[]秩序; }///& lt; summary>///终端信息数据///& lt;/summary> [StructLayoutAttribute LayoutKind。连续的,CharSet=字符集。Ansi,包=1) 公共struct PackTerminalSearch4004 { [MarshalAs UnmanagedType。ByValTStr SizeConst=6)]///& lt; summary>///终端编的号///& lt;/summary> 公共字符串stationCode;///& lt; summary>///终端IP///& lt;/summary> 公共长terminalIP;///& lt; summary>///终端端口///& lt;/summary> 公共ushort terminalPort;///& lt; summary>///中心IP///& lt;/summary> 公共长serverIP;///& lt; summary>///测站端口///& lt;/summary> 公共ushort serverPort;///& lt; summary>///磁盘信息数组///& lt;/summary> [MarshalAs UnmanagedType。ByValArray SizeConst=8)) 公共PackDiskInfo [] diskInfoArray; }///& lt; summary>///磁盘信息///& lt;/summary> [StructLayoutAttribute LayoutKind。连续的,CharSet=字符集。Ansi,包=1) 公共struct PackDiskInfo {///& lt; summary>///盘符///& lt;/summary> 公共字符驱动;///& lt; summary>///总空间///& lt;/summary> 公共双totalSize;///& lt; summary>///可用空间///& lt;/summary> 公共双usableSize; } >之前
,,,,,,,,上面的代码需要注意的是字符串类型实际为Char[6]长度的数组,实际使用中只能有效的使用前5个字符,因为Char[6]最后一位默认\ 0;
,,,,,,,三、结构体与字节数组的互转
PackTerminalSearch6001信息; 信息。stationCode=" 12345 "; 信息。订单=new字节[6]{0 x00 0 x01 0 x02, 0 x03, 0 x04 0 x05}; Byte [] recv=StructToBytes(信息); 对象obj=BytesToStuct (recv typeof (PackTerminalSearch6001)); PackTerminalSearch6001 info5001=(PackTerminalSearch6001 obj); byte []=info5001.order顺序;////& lt; summary>///结构体转字节数组///& lt;/summary>///& lt;参数name=" structObj祝辞要转换的结构体& lt;/param>///& lt; returns>转换后的字节数组& lt;/returns> 公共静态byte [] StructToBytes structObj(对象) {//得到结构体的大小 int大?sizeof (structObj);//创建字节数组 byte[]字节=新字节(大小);//分配结构体大小的内存空间 IntPtr structPtr=Marshal.AllocHGlobal(大小);//将结构体拷到分配好的内存空间 元帅。StructureToPtr (structObj structPtr,假);//从内存空间拷到字节数组 元帅。复制(structPtr字节0、大小);//释放内存空间 Marshal.FreeHGlobal (structPtr);//返回字节数组 返回字节; }///& lt; summary>///字节数组转结构体///& lt;/summary>///& lt;参数name=白纸凇痹谧纸谑? lt;/param>///& lt;参数name=袄嘈汀痹诮峁固謇嘈? lt;/param>///& lt; returns>转换后的结构体& lt;/returns> 公共静态对象BytesToStuct (byte[]字节,类型类型) {//得到结构体的大小 int大?sizeof(类型);//字节数组长度小于结构体的大小 如果(大小比;bytes.Length) {//返回空 返回null; }//分配结构体大小的内存空间 IntPtr structPtr=Marshal.AllocHGlobal(大小);//将字节数组拷到分配好的内存空间 元帅。复制(字节0 structPtr、大小);//将内存空间转换为目标结构体 对象obj=元帅。PtrToStructure (structPtr、类型);//释放内存空间 Marshal.FreeHGlobal (structPtr);//返回结构体 返回obj; }c#中结构体定义并转换字节数组详解