采用Union实现上位机交互中的数据解析

发布时间 2024-01-09 13:59:40作者: wangzhiliang

在计算机科学中,联合体(英语:union)又名共用体,是一种具有多个类型或格式的值,或者把它定义为一种由具有这样的值的变量形成的数据结构。一些编程语言(如C语言)可以支持被称为“联合体”的特殊类型,来表示上述的变量。与枚举和结构体不同的是,一个联合体的长度等于其内部长度最大的成员的长度,并且它们都共享着同一段内存。

      结构体与联合体变量的内存分配方式如下图所示:

                                                                

 

     上位机软件开发采用的C#语言不支持联合体,但可以通过手动控制结构体每个元素的位置来实现类似的功能。这需要结合使用StructLayoutAttribute、LayoutKind以及FieldOffsetAttribute。使用它们的时候必须引用System.Runtime.InteropServices。具体实现方式如下:

using DuiHelpers;
using System;
using System.Runtime.InteropServices;

namespace ConsoleApp
{
    /// <summary>
    /// C#上位机软件中采用结构体方式进行数据解析实现
    /// Added by wzl 2021/01/09
    /// 方法优势:
    /// 1、相比以前的字节拆分的方式,省去数据解析时的大量字节对比与赋值操作;
    /// 2、解析结果可靠,降低了编程的出错概率;
    /// </summary>
    internal unsafe class Program
    {
        static void Main(string[] args)
        {
            //模拟下位机返回的数据
            byte[] dataBuffer = new byte[100];
            for (int i = 0; i < 100; i++)
                dataBuffer[i] = (byte)(i + 100);

            //将接收到的数据(字节数组)转换成结构体
            net_type obj = (net_type)DuiHelperByte.BytesToStuct(dataBuffer, typeof(net_type));

            //从结构体对象中直接取值(不用再进行逐个字节对比和赋值)
            byte b1 = obj.get_param.ack_ip[0];
        }

        /// <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;
        }
    }

    public enum version_type : ushort
    {
        VER_BROADCAST = 0x5FAF, // 广播帧标识
        VER_NET = 0x01AF,       // 网络帧头
    }

    public enum cmd_type : ushort
    {
        CMD_USER_APP_COM_START = 0x8000, // 用户应用命令起始
        /*********自定义命令*********************************/
        CMD_GET_PARAM = 0x8001,          // 获取参数
        CMD_ACK_GET_PARAM,               // 返回获取参数
        CMD_SET_PARAM,                   // 设置参数
        CMD_ACK_SET_PARAM,               // 返回设置参数成功        
        /**************************************************/
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct net_type
    {
        [FieldOffset(0)]
        public version_type ver; // 消息头

        [FieldOffset(2)]
        public cmd_type cmd; //命令

        [FieldOffset(4)]
        public get_param_type get_param;       // 广播获取参数

        [FieldOffset(4)]
        public set_prarm_type set_prarm; // 广播设置IP地址
    }

    public enum sys_run_state_type : byte
    {
        SYS_BOOTLOADER = 0x01,   // Bootloader 运行状态
        SYS_APP,                          // APP 运行状态
    }

    // 获取系统参数,
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct get_param_type // size=6
    {
        public fixed byte ack_ip[4]; // 返回ip地址
        public ushort ack_port; // 返回端口号
    }

    // 设置系统参数
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct set_prarm_type //size=50+2
    {
        public fixed byte ack_ip[4];  // 返回ip地址
        public ushort ack_port;  // 返回端口号(*注意:struct按uint的长度进行了字节对齐,所以长度增加了2个字节)
        public fixed uint sn_id[3];  // MCU唯一序号(广播帧时作为设备ID,必须与本机相同才进行设置)
        public uint ip_cfg_mask;   // 结构体对应元素数据掩码: 0,无效数据,不做处理;1,有效数据;
    }
}

网上参考资料:

https://zhuanlan.zhihu.com/p/589867601
https://www.cnblogs.com/chihirosan/archive/2016/01/28/5166057.html