软件编程
位置:首页>> 软件编程>> C#编程>> C#使用struct直接转换下位机数据的示例代码

C#使用struct直接转换下位机数据的示例代码

作者:波多尔斯基  发布时间:2023-06-07 01:49:01 

标签:C#,struct,下位机数据

编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制。传统的方法,是将数据整体获取到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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com