C# 结构体定义 转换字节数组 z
客户端采用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 = 1)]
public struct PackTerminalSearch5001
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
/// <summary>
/// 终端编号
/// </summary>
public string stationCode; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
/// <summary>
/// 回复指令
/// </summary>
public Byte[] order;
}
/// <summary>
/// 终端信息数据
/// </summary> [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PackTerminalSearch3004
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
/// <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 = 8)]
public PackDiskInfo[] diskInfoArray;
} /// <summary>
/// 磁盘信息
/// </summary>
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
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, 0, 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, 0, structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}

C# 结构体定义 转换字节数组 z的更多相关文章
- C#中结构体定义并转换字节数组
最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换:由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一 ...
- 将c语言的结构体定义变成对应的golang语言的结构体定义,并将golang语言结构体变量的指针传递给c语言,cast C struct to Go struct
https://groups.google.com/forum/#!topic/golang-nuts/JkvR4dQy9t4 https://golang.org/misc/cgo/gmp/gmp. ...
- (原创)结构体自动化转为char数组的实现
结构体自动化转换为char数组这个需求,来自于一个最近开发的一个项目,在项目开发过程中遇到一个小问题,需要将各种结构体拷贝到char数组中,这对于一个简单的结构体来说是很简单的事情,比如下面这个只有整 ...
- JNA结构体参数传递,Java数组
JNA以结构体数组为参数进行调用: ////// C++ // student 结构体定义 typedef struct { int age; char name[20]; }Student; // ...
- ARM单片机的头文件如何用结构体定义地址
下面我们以ARM Cortex-M0内核单片机LPC1114的头文件lpc11xx.h文件进行说明. 1.先说两句 lpc11xx.h文件是lpc11xx系列单片机包含的头文件.这个文件的作用和51单 ...
- Swift类和结构体定义-备
Swift中的类和结构体定义的语法是非常相似的.类使用class关键词定义类,使用struct关键词定义结构体,它们的语法格式如下: class 类名 { 定义类的成员 } struct 结构体名 { ...
- C语言结构体定义的几种方法
什么是结构体? 在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类.结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据 ...
- #pragma pack 在BITMAP结构体定义中的使用
BITMAP位图文件主要分为如下3个部分: 块名称 对应Windows结构体定义 大小(Byte) 文件信息头 BITMAPFILEHEADER 14 位图信息头 BITMAPINFOHEADER 4 ...
- 读陈浩的《C语言结构体里的成员数组和指针》总结,零长度数组
原文链接:C语言结构体里的成员数组和指针 复制例如以下: 单看这文章的标题,你可能会认为好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Lar ...
随机推荐
- 使用mockito模拟静态方法
一.为什么要使用Mock工具 在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等). 而我们没法控制这些外部依赖的对象,为了解 ...
- bash常用快捷键和命令
在使用Linux的时候,最常见的终端解释器就是bash了.bash下有很多技巧,我知道这么几个: 0.关于按键模式bash默认的按键模式是emacs风格的.你也可以通过set -i vi设定为vi风格 ...
- NOIP2018初赛 解题报告(C++普及)
第24届全国青少年信息学奥林匹克联赛初赛 普及组C++语言试题 竞赛时间:2018 年 10 月 13 日 14:30~16:30 选手注意: 1.试题纸共有 7 页,答题纸共有 2 页,满分 100 ...
- echart 打开新世界的大门
实时折线图 option = { backgroundColor:'#2B2B2B', tooltip: { trigger: 'axis' }, legend: { data:['频率'], tex ...
- C++11线程池的实现
什么是线程池 处理大量并发任务,一个请求一个线程来处理请求任务,大量的线程创建和销毁将过多的消耗系统资源,还增加了线程上下文切换开销. 线程池通过在系统中预先创建一定数量的线程,当任务请求到来时从线程 ...
- bzoj 1232 [Usaco2008Nov]安慰奶牛cheer
思路:看出跟dfs的顺序有关就很好写了, 对于一棵树来说确定了起点那么访问点的顺序就是dfs序,每个点经过 其度数遍,每条边经过2边, 那么我们将边的权值×2加上两端点的权值跑最小生成树,最后加上一个 ...
- 跟厂长学PHP7内核(二):源码分析的环境与工具
本文主要介绍分析源码的方式,其中包含环境的搭建.分析工具的安装以及源码调试的基本操作. 一.工具清单 PHP7.0.12 GDB CLion 二.源码下载及安装 $ wget http://php.n ...
- GOF设计模式快速学习
这段时间,学习状态比较一般,空闲时基本都在打游戏,和研究如何打好游戏,终于通过戏命师烬制霸LOL,玩笑了.为了和"学习"之间的友谊小船不翻,决定对以往学习过的GOF设计模式做一个简 ...
- Java 中类的初始化过程
先来一张 JVM 中的内存模型 . 在Java 虚拟机原理这本书中介绍了类会被初始化的 5 种情况 . 1 遇到 new getstatic putstatic 和 invokestatic 这 4 ...
- date time insert
DATE=`date '+%m/%d/%Y'`TIME=`date '+%H:%M:%S'` sed -i '1i1***** start*****' test.kshsed -i '2i\ REPO ...