C# 字节数组转结构体之字节顺序

在上一篇C# TCP通信中结构体与字节数组相互转换中我们完成了字节数据与结构体的转换,但是一个更严重的问题出现了在我们的面前,那就时数据字节顺序的问题也就是我们常说的大小端问题。

在西门子PLC S7-1200中不启用优化的数据块是以大端方式进行存储的,但是在C#中却是以小端方式存储数据的,这就造成了一个矛盾。从PLC中直接读取到的数据在C#内部是无法直接正常解析的,这就需要我们对数据添加新的处理方式以解决大小端问题。

我使用方式是自定义特性,公共语言运行时使你能够添加类似于关键字的描述性声明(称为特性),以便批注编程元素(如类型、字段、方法和属性),它是以方括号([])的形式附加到代码元素上,并可包含参数。特性的定义通常是通过创建一个继承自System.Attribute的自定义类来实现的。

创建Flip类为特性,其中包括字节翻转与字翻转两种翻转方式。

public enum FlipType
{
    ByteFlip,
    WordFlip
}
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class Flip : Attribute
{
    public FlipType Type { get; }

    public Flip(FlipType type)
    {
        Type = type;
    }
}

为接收数据的结构体每一个需要进行大小端转换的成员添加特性。


public struct realTimeData
{
    /// <summary>
    /// A总重量
    /// </summary>
    [MarshalAs(UnmanagedType.R4), Flip(FlipType.ByteFlip)]
    public float DrumAWeightTotal;
    /// <summary>
    /// A空重量
    /// </summary>
    [MarshalAs(UnmanagedType.R4)]
    public float DrumAEmptyWeight;
}

接下来修改字节数组转换结构体的函数,在查询到需要字节转换的成员时将该成员数据进行指定的翻转。

/// <summary>
/// 将字节数组转换为指定类型的结构体
/// </summary>
/// <param name="bytes">要转换的字节数组</param>
/// <param name="type">目标结构体的类型</param>
/// <returns>转换后的结构体对象</returns>
public static object BytesToStuct(byte[] bytes, Type type)
{
    // 获取结构体的所有公共实例字段
    FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);

    // 得到结构体的大小
    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);

    // 遍历结构体的字段
    foreach (FieldInfo field in fields)
    {
        // 获取成员的 Flip 属性值
        Flip flipAttribute = (Flip)Attribute.GetCustomAttribute(field, typeof(Flip));
        if (flipAttribute != null)
        {
            // 根据 FlipType 执行相应的翻转操作
            switch (flipAttribute.Type)
            {
                case FlipType.ByteFlip:
                    field.SetValue(obj, byteflip(field, field.GetValue(obj)));
                    break;
                case FlipType.WordFlip:
                    field.SetValue(obj, wordflip(field, field.GetValue(obj)));
                    break;
                default:
                    Console.WriteLine("Unsupported other flip type!");
                    break;
            }
        }
    }

    // 返回转换后的结构体对象
    return obj;
}

 

/// <summary>
/// 执行字节翻转操作的方法
/// </summary>
/// <param name="field">要进行翻转操作的字段信息</param>
/// <param name="value">字段的当前值</param>
/// <returns>执行字节翻转后的结果</returns>
private static object byteflip(FieldInfo field, object value)
{
    byte[] valueBytes;

    // 根据字段的类型执行相应的字节翻转操作
    switch (Type.GetTypeCode(field.FieldType))
    {
        case TypeCode.Int16:
            valueBytes = BitConverter.GetBytes(Convert.ToInt16(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToInt16(valueBytes, 0);
        case TypeCode.UInt16:
            valueBytes = BitConverter.GetBytes(Convert.ToUInt16(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToUInt16(valueBytes, 0);
        case TypeCode.Int32:
            valueBytes = BitConverter.GetBytes(Convert.ToInt32(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToInt32(valueBytes, 0);
        case TypeCode.UInt32:
            valueBytes = BitConverter.GetBytes(Convert.ToUInt32(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToUInt32(valueBytes, 0);
        case TypeCode.Int64:
            valueBytes = BitConverter.GetBytes(Convert.ToInt64(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToInt64(valueBytes, 0);
        case TypeCode.UInt64:
            valueBytes = BitConverter.GetBytes(Convert.ToUInt64(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToUInt64(valueBytes, 0);
        case TypeCode.Single:
            valueBytes = BitConverter.GetBytes(Convert.ToSingle(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToSingle(valueBytes, 0);
        case TypeCode.Double:
            valueBytes = BitConverter.GetBytes(Convert.ToDouble(value));
            Array.Reverse(valueBytes);
            return BitConverter.ToDouble(valueBytes, 0);
        default:
            // 记录不支持的数据类型进行字节翻转的日志信息
            LoggerSingleton.Instance.LogInfo($"不支持{field.FieldType}类型数据进行字节翻转");
            break;
    }

    // 如果数据类型不受支持,则返回空
    return null;
}

 

/// <summary>
/// 执行字翻转操作的方法
/// </summary>
/// <param name="field">要进行翻转操作的字段信息</param>
/// <param name="value">字段的当前值</param>
/// <returns>执行字翻转后的结果</returns>
private static object wordflip(FieldInfo field, object value)
{
    switch (Type.GetTypeCode(field.FieldType))
    {
        case TypeCode.Int32:
            {
                // 对 Int32 类型执行字翻转操作
                Int32 data = Convert.ToInt32(value);
                Int32 highBits = (int)((data & 0xFFFF0000) >> 16); // 获取高 16 位数据
                Int32 lowBits = (Convert.ToInt32(value) & 0x00FF) << 16; // 获取低 16 位数据
                return highBits | lowBits;
            }
        case TypeCode.UInt32:
            {
                // 对 UInt32 类型执行字翻转操作
                UInt32 data = Convert.ToUInt32(value);
                UInt32 highBits = (uint)((data & 0xFFFF0000) >> 16); // 获取高 16 位数据
                UInt32 lowBits = (Convert.ToUInt32(value) & 0x00FF) << 16; // 获取低 16 位数据
                return highBits | lowBits;
            }
        case TypeCode.Int64:
            // 记录不支持 Int64 类型数据进行字翻转的日志信息
            LoggerSingleton.Instance.LogInfo($"暂不支持{field.FieldType}类型数据进行字翻转");
            return null;
        case TypeCode.UInt64:
            // 记录不支持 UInt64 类型数据进行字翻转的日志信息
            LoggerSingleton.Instance.LogInfo($"暂不支持{field.FieldType}类型数据进行字翻转");
            return null;
        case TypeCode.Single:
            {
                // 对 Single 类型执行字翻转操作
                byte[] data = BitConverter.GetBytes(Convert.ToSingle(value));
                (data[0], data[1], data[2], data[3]) = (data[2], data[3], data[0], data[1]);
                return BitConverter.ToSingle(data, 0);
            }
        case TypeCode.Double:
            // 记录不支持 Double 类型数据进行字翻转的日志信息
            LoggerSingleton.Instance.LogInfo($"暂不支持{field.FieldType}类型数据进行字翻转");
            return null;
        default:
            // 记录不支持其他类型数据进行字翻转的日志信息
            LoggerSingleton.Instance.LogInfo($"不支持{field.FieldType}类型数据进行字翻转");
            break;
    }
    return null;
}

注意:代码中LoggerSingleton.Instance.LogInfo 是自定义的单例日志实例,可以改为Console.WriteLine();

阅读剩余
THE END