C#使用struct直接转换下位机数据的示例代码
作者:波多尔斯基 发布时间:2023-06-07 01:49:01
编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制。传统的方法,是将数据整体获取到byte数组中,然后逐字节对数据进行解析。这样操作工作量比较大,对于较长数据段更容易计算位置出错。
其实,对于下位机给出通讯的数据结构的情况下,可以直接使用C#的struct将数据直接转换。需要使用到Marshal
。
数据结构
假定下位机(C语言编写)给到我们的数据结构是这个,传输方式为小端方式
typedef struct {
unsigned long int time; // 4个字节
float tmpr[3]; // 4*3 个字节
float forces[6]; // 4*6个字节
float distance[6]; // 4*6个字节
} dataItem_t;
方法1
首先需要定义一个struct:
[StructLayout(LayoutKind.Sequential, Size = 64, Pack = 1)]
public struct HardwareData
{
//[FieldOffset(0)]
public UInt32 Time; // 4个字节
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
//[FieldOffset(4)]
public float[] Tmpr; // 3* 4个字节
//[FieldOffset(16)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public float[] Forces; // 6* 4个字节
//[FieldOffset(40)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public float[] Distance; // 6*4个字节
}
然后使用以下代码进行转换
// code from https://stackoverflow.com/questions/628843/byte-for-byte-serialization-of-a-struct-in-c-sharp/629120#629120
/// <summary>
/// converts byte[] to struct
/// </summary>
public static T RawDeserialize<T>(byte[] rawData, int position)
{
int rawsize = Marshal.SizeOf(typeof(T));
if (rawsize > rawData.Length - position)
throw new ArgumentException("Not enough data to fill struct. Array length from position: " + (rawData.Length - position) + ", Struct length: " + rawsize);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.Copy(rawData, position, buffer, rawsize);
T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T));
Marshal.FreeHGlobal(buffer);
return retobj;
}
/// <summary>
/// converts a struct to byte[]
/// </summary>
public static byte[] RawSerialize(object anything)
{
int rawSize = Marshal.SizeOf(anything);
IntPtr buffer = Marshal.AllocHGlobal(rawSize);
Marshal.StructureToPtr(anything, buffer, false);
byte[] rawDatas = new byte[rawSize];
Marshal.Copy(buffer, rawDatas, 0, rawSize);
Marshal.FreeHGlobal(buffer);
return rawDatas;
}
注意这里我使用的方式为LayoutKind.Sequential
,如果直接使用LayoutKind.Explicit
并设置FieldOffset
会弹出一个诡异的错误System.TypeLoadException:“Could not load type 'ConsoleApp3.DataItem' from assembly 'ConsoleApp3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.”。
方法2
提示是对齐的错误,这个和编译的时候使用的32bit和64位是相关的,详细数据封送对齐的操作我不就详细说了,贴下代码。
//强制指定x86编译
[StructLayout(LayoutKind.Explicit, Size = 64, Pack = 1)]
public struct DataItem
{
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(0)]
public UInt32 time; // 4个字节
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.R4)]
[FieldOffset(4)]
public float[] tmpr; // 3* 4个字节
[FieldOffset(16)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.R4)]
public float[] forces; // 6* 4个字节
[FieldOffset(40)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.R4)]
public float[] distance; // 6*4个字节
}
强制指定x64编译没有成功,因为数据对齐后和从下位机上来的数据长度是不符的。
方法3
微软不是很推荐使用LayoutKind.Explicit
,如果非要用并且不想指定平台的话,可以使用指针来操作,当然,这个需要unsafe
。
var item = RawDeserialize<DataItem>(tail.ToArray(), 0);
unsafe
{
float* p = &item.forces;
for (int i = 0; i < 6; i++)
{
Console.WriteLine(*p);
p++;
}
}
[StructLayout(LayoutKind.Explicit, Size = 64, Pack = 1)]
public struct DataItem
{
[FieldOffset(0)]
public UInt32 time; // 4个字节
[FieldOffset(4)]
public float tmpr; // 3* 4个字节
[FieldOffset(16)]
public float forces; // 6* 4个字节
[FieldOffset(40)]
public float distance; // 6*4个字节
}
方法4
感觉写起来还是很麻烦,既然用上了unsafe
,就干脆直接一点。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct DataItem
{
public UInt32 time; // 4个字节
public fixed float tmpr[3]; // 3* 4个字节
public fixed float forces[6]; // 6* 4个字节
public fixed float distance[6]; // 6*4个字节
}
这样,获得数组可以直接正常访问,不再需要unsafe
了。
来源:https://www.cnblogs.com/podolski/archive/2021/01/20/14295500.html
猜你喜欢
- 刚毕业的第一份工作是 java 开发,项目中需要用到 mybatis,特此记录学习过程,这只是一个简单 demo,mybatis 用法很多不
- 一、JDBC简介JDBC是连接java应用程序和数据库之间的桥梁。什么是JDBC?Java语言访问数据库的一种规范,是一套API。JDBC
- 1、继承Thread类方式这种方式适用于执行特定任务,并且需要获取处理后的数据的场景。举例:一个用于累加数组内数据的和的线程。public
- 目录前言Java Heap进程内存分配内存不足管理GC 垃圾回收内核交换守护进程低内存终止守护进程最后前言很高兴遇见你~内存优化一直是 An
- 1.流布局FlowLayout所有组件像流一样,一个一个排放,排满了一行之后排下一行,默认情况下,每个组件是居中排列的,但是也可以设置。流布
- C# 自带的HttpWebRequest效率太低,对于自组HTTP封包不好操作。在写超级SQL注入工具时,研究了很长一段时间如何使用Sock
- 前言最近在给熔断器组件增加一个降级策略(Hystrix好像没有这个配置),我们提供了如下几种策略:1、默认策略2、返回常量值3、抛出指定异常
- 本文实例总结了java判断字符串是否为数字的方法。分享给大家供大家参考,具体如下:方法一:用JAVA自带的函数public static b
- 1. dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们
- 什么是注解?对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安
- 按行读取文件package test; import java.io.*; import java.util.*; public class
- 1. matlab的lp2lp函数的作用去归一化 H(s) 的分母2. matlab的lp2lp函数的使用方法[z, p, k]=butta
- 约瑟夫环约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数
- springboottest测试依赖和使用<dependency> <groupId>or
- 一、定义委托delegate void StudentDelegate();//【1】定义一个委托二、定义一个调用和定义事件的类/// &l
- 前言最近项目需要和Oracle数据库进行交互,然后我从Maven中央仓库下载数据库驱动jar包,但怎么都下不下来,我到Oracle官网上一看
- 前言idea作为一个java开发的便利IDE工具,个人是比较喜欢的,今天来探索个小功能: 导出单个类文件为jar包!JAR文件的全称是Jav
- 这里我们以拨打电话申请权限来写个小例子,也就是CALL_PHONE,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限,在Andro
- 1. 基本数据类型(以int为例,其他类似):Controller代码:@RequestMapping("saysth.do&qu
- 如下所示:JSONArray jsonArray1 = jsonObject.getJSONArray("result"