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
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
Flutter 设置全局字体的实现
用C语言实现五子棋小游戏
![](https://img.aspxhome.com/file/2023/9/117779_0s.jpg)
C# Winform实现导入和导出Excel文件
浅谈java中的重载和重写的区别
![](https://img.aspxhome.com/file/2023/6/75516_0s.png)
C#同步和异步调用方法实例
java Lock接口详解及实例代码
浅谈spring方法级参数校验(@Validated)
![](https://img.aspxhome.com/file/2023/7/90037_0s.png)
springboot+springsecurity如何实现动态url细粒度权限认证
![](https://img.aspxhome.com/file/2023/0/64130_0s.png)
c#实现最简洁的快速排序(你绝对可以看懂)
详解Android 多级联动控件实现思路讨论
![](https://img.aspxhome.com/file/2023/7/137577_0s.gif)
Java日常练习题,每天进步一点点(46)
![](https://img.aspxhome.com/file/2023/0/116190_0s.png)
java 中同步、异步、阻塞和非阻塞区别详解
Java中byte输出write到文件的实现方法讲解
Android 4.4.2 横屏应用隐藏状态栏和底部虚拟键的方法
java实现基于TCP协议网络socket编程(C/S通信)
![](https://img.aspxhome.com/file/2023/6/98406_0s.png)
springboot 如何解决yml没有spring的小叶子标志问题
![](https://img.aspxhome.com/file/2023/4/71644_0s.png)
C#如何生成唯一订单号
SpringBoot深入探究四种静态资源访问的方式
![](https://img.aspxhome.com/file/2023/2/94202_0s.png)