C#中结构体定义并转换字节数组
最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。
首先是结构体定义,一些基本的数据类型,C#与C++都是可以匹配的:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = )]
public struct Head
{
public ushort proMagic; //包起始标记:固定0x7e7e
public ushort proPackLen; //包长度:包头 + 数据区 + 包尾长度,注意不要超过最大长度限制
public long proSrcAddr; //源地址:不使用,填0
public ushort proSrcPort; //源地址端口:不使用,填0
public long proDstAddr; //目的地址:不使用,填0
public ushort proDstPort; //目的端口:不使用,填0
public ushort proCmdCode; //命令码:参见以上命令码定义 public ushort proVersion; //版本号:不使用,填1
public char proSerial; //报文序号:一条报文实例对应一个序号,不同报文叠加,0-255往复
public ushort proPackSum; //总包数:当包长超过最大长度限制时,需要拆包,大包拆小包总数,不拆默认1
public ushort proPackId; //当前包号:对应以上总包数的小包标识,不拆默认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++ 添加 #pragma pack 1,保证结构体中字节对齐方式一致。
二、数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;
/// <summary>
/// 终端信息查询
/// </summary>
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = )]
public struct PackTerminalSearch5001
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = )]
/// <summary>
/// 终端编号
/// </summary>
public string stationCode; [MarshalAs(UnmanagedType.ByValArray, SizeConst = )]
/// <summary>
/// 回复指令
/// </summary>
public Byte[] order;
}
/// <summary>
/// 终端信息数据
/// </summary> [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = )]
public struct PackTerminalSearch3004
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = )]
/// <summary>
/// 终端编号
/// </summary>
public string stationCode;
/// <summary>
/// 终端IP
/// </summary>
public long terminalIP;
/// <summary>
/// 终端端口
/// </summary>
public ushort terminalPort;
/// <summary>
/// 中心IP
/// </summary>
public long serverIP;
/// <summary>
/// 测站端口
/// </summary>
public ushort serverPort;
/// <summary>
/// 磁盘信息数组
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = )]
public PackDiskInfo[] diskInfoArray;
} /// <summary>
/// 磁盘信息
/// </summary>
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = )]
public struct PackDiskInfo
{
/// <summary>
/// 盘符
/// </summary>
public char drive;
/// <summary>
/// 总空间
/// </summary>
public double totalSize;
/// <summary>
/// 可用空间
/// </summary>
public double usableSize;
}
上面的代码需要注意的是string类型实际为Char[6]长度的数组,实际使用中只能有效的使用前5个字符,因为char[6]最后一位默认\0;
三、结构体与字节数组的互转
PackTerminalSearch5001 info;
info.stationCode = "12345";
info.order = new byte[6] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
Byte[] recv = StructToBytes(info); object obj = BytesToStuct(recv, typeof(PackTerminalSearch5001));
PackTerminalSearch5001 info5001 = (PackTerminalSearch5001)obj;
byte[] order = info5001.order;
//// <summary>
/// 结构体转byte数组
/// </summary>
/// <param name="structObj">要转换的结构体</param>
/// <returns>转换后的byte数组</returns>
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, , size);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
return bytes;
} /// <summary>
/// byte数组转结构体
/// </summary>
/// <param name="bytes">byte数组</param>
/// <param name="type">结构体类型</param>
/// <returns>转换后的结构体</returns>
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, , structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}
欢迎转载,转载请注明原文出处(原博客地址),然后谢谢观看。
如果觉得我的文章对您有帮助,请点击推荐支持:)
C#中结构体定义并转换字节数组的更多相关文章
- C语言中结构体定义
struct test { int a; }; /* 定义一个结构体,名字是test,这样就可以使用struct test 来定义变量.比如 struct test a; */ typedef str ...
- C语言中结构体定义实际上相当于变量入栈
struct context { int edi; int esi; int ebx; int ebp; int eip;}; 对应的入栈顺序是 pushl %esp pushl %eip pushl ...
- C# 结构体定义 转换字节数组 z
客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性. [StructLayoutAttribute(LayoutKind.Sequent ...
- #pragma pack 在BITMAP结构体定义中的使用
BITMAP位图文件主要分为如下3个部分: 块名称 对应Windows结构体定义 大小(Byte) 文件信息头 BITMAPFILEHEADER 14 位图信息头 BITMAPINFOHEADER 4 ...
- C /C ++中结构体的定义
c语言中结构体的定义: struct 结构体名{ 成员列表: ..... }结构体变量: 7.1.1 结构体类型变量的定义结构体类型变量的定义与其它类型的变量的定义是一样的,但由于结构体类型需要针对问 ...
- C语言中结构体赋值问题的讨论
今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...
- C语言中结构体对齐问题
C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...
- ARM单片机的头文件如何用结构体定义地址
下面我们以ARM Cortex-M0内核单片机LPC1114的头文件lpc11xx.h文件进行说明. 1.先说两句 lpc11xx.h文件是lpc11xx系列单片机包含的头文件.这个文件的作用和51单 ...
- C语言中结构体赋值问题的讨论(转载)
今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...
随机推荐
- python采用 多进程/多线程/协程 写爬虫以及性能对比,牛逼的分分钟就将一个网站爬下来!
首先我们来了解下python中的进程,线程以及协程! 从计算机硬件角度: 计算机的核心是CPU,承担了所有的计算任务.一个CPU,在一个时间切片里只能运行一个程序. 从操作系统的角度: 进程和线程,都 ...
- Docker入门之七Dockerfile
Dockerfile是一个文本格式的配置文本,可以使用它来创建自定义的镜像.首先我们可以先看一个dockerfile是什么样子.这里可以有一个网站不错:http://dockerfile.github ...
- 面向对象编程笔记--static
通过static方法,提供静态的不需要实例化即可访问的方法或属性.所有的调用者可以使用同一个类(不实例化)或对象(只实例化一次),可以应用的场景: 1)各个调用者共享数据,协同工作. 2)对象只可以实 ...
- JAVA提高三:反射总结
为前期学习过反射,再这里再次复习总结下:[转载请说明来源:http://www.cnblogs.com/pony1223/p/7659210.html ] 一.透彻分析反射的基础_Class类 Cla ...
- YYHS-手机信号
题目描述 输入 输出 样例输入 11 10000 query 5 construct 5 500 100 query 500 query 1000 construct 10 90 5 query 44 ...
- CoreData归纳使用
1.CoreData简介 2.CoreData数据模型 3.CoreData的主要对象 4.使用CoreData实现数据存储 一.CoreData简介 CoreData用做数据持久化,是数据持久化的最 ...
- 【转载】CSS3的calc()使用
文章转载自 w3cplus http://www.w3cplus.com/ 原文链接:http://www.w3cplus.com/css3/how-to-use-css3-calc-function ...
- GeoServer+PostgreSQL+PostGIS+pgRouting实现最短路径查询
一.软件安装 GeoServer下载地址: http://geoserver.org/download/ PostgreSQL下载地址: https://www.postgresql.org/down ...
- win10 uwp clone
clone 可以用MemberwiseClone来复制一个类 但这个复制是浅复制,创建一个新的object然后复制值字段,对于引用就直接复制引用,不复制引用的本身,指向同样引用 如果要复制引用,可以使 ...
- STM32外部中断线编程
#include "ExtiConfig.h" unsigned char key1Down = 0; unsigned char key2Down = 0; /********* ...