c# Marshal 将字节数组转为结构 封装协议

解析网络协议如果使用依次读取字节的方式效率太低,可以直接通过结构体映射的方式来转换数据,如下

  1. 定义需要转换的结构体
    需要让结构体数据顺序排列并对齐
    依次定义每一个属性的长度即可,需要注意定义的数据类型的大小要与UnmanagedType类型定义的大小一直
    否则会报 “不能作为非托管结构进行封送处理;无法计算有意义的大小或偏移量”
 // [StructLayout(LayoutKind.Sequential, Pack = 1)] //顺序排列,并按1字节对齐 
    [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct SEG2
    {
        /// <summary>
        /// 文件描述符  0-1
        /// 若不是3A55H/553AH则 不是SEG2文件
        /// </summary>
//数组指定数量即可    
//    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
//    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12, ArraySubType = UnmanagedType.U1)] //也可以指定具体的数组内部类型
//     public byte[] Descriptor0;

        [MarshalAs(UnmanagedType.U1)]
        public byte Descriptor0;

        [MarshalAs(UnmanagedType.U1)]
        public byte Descriptor1;
}
  1. 将字节数组转为对象
var fileByte = File.ReadAllBytes(testFile);
//创建指定类型的指针(从进程的非托管内存中分配内存)
IntPtr paramPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SEG2)));
//将字节数组复制到指针
Marshal.Copy(fileByte, 0, paramPtr, Marshal.SizeOf(typeof(SEG2)));
////将指定的指针转为特定类型的对象(将数据从非托管内存块封送到新分配的指定类型的托管对象)
SEG2 obj = (SEG2)Marshal.PtrToStructure(paramPtr, typeof(SEG2));
  1. 将对象从托管对象复制到内存块
//将对象复制给指针所指内存
var obj=new object();
Marshal.StructureToPtr(obj, paramPtr, true);
  1. 释放内存,内存在非托管块需要手动释放

            Marshal.FreeHGlobal(paramPtr);
  1. 复杂对象
    如果为数组这类非固定位数的对象,可以通过多次调用的方式依次将托管对象与非托管对象互相转换
    形式上更像c/c++利用指针对内存进行操作

扩展

上面的逻辑中未处理字节序,继续进行扩展。通过获取字段的类型和数组类型的字段来自动获取大端、小端转换后的数据

准备类型类型

public enum Endianness
{
    BigEndian,
    LittleEndian
}

扩展方法如下,需要特殊注意对数组做了特殊处理,会读取MarshalAsAttribute来计算数组实际的长度后再重排数组

public static T GetResult<T>(string data, int startPos = 3)
{
    System.IntPtr blockPtr = new();
    bool mallocSuccess = false;
    try
    {
        byte[] byteData = DataFormatEx.GetByteArray(data);

        MaybeAdjustEndianness(typeof(T), byteData, Endianness.BigEndian, startPos);

        //根据数据长度自动截取
        blockPtr = MarshalUtility.GetPointer<T>(byteData, startPos);
        mallocSuccess = true;
        T block = MarshalUtility.GetObj<T>(blockPtr);
        blockPtr.Free();
        return block;
    }
    catch (System.Exception)
    {
        throw;
    }
}

/// <summary>
/// 根据BitConverter大小端方向和目标方向判断是否需要交换字节序并交换字节序
/// </summary>
/// <param name="type"></param>
/// <param name="data"></param>
/// <param name="endianness"></param>
/// <param name="startOffset"></param>
/// <exception cref="Exception"></exception>
private static void MaybeAdjustEndianness(Type type, byte[] data, Endianness endianness, int startOffset = 0)
{
    if ((BitConverter.IsLittleEndian) == (endianness == Endianness.LittleEndian))
    {
        // nothing to change => return
        return;
    }

    foreach (var field in type.GetFields())
    {
        var fieldType = field.FieldType;
        if (field.IsStatic)
            // don't process static fields
            continue;

        if (fieldType == typeof(string))
            // don't swap bytes for strings
            continue;

        var offset = Marshal.OffsetOf(type, field.Name).ToInt32();

        // handle enums
        if (fieldType.IsEnum)
            fieldType = Enum.GetUnderlyingType(fieldType);


        int marshSize = 0;
        if (fieldType.IsArray)
        {
            // 获取MarshalAs特性
            var marshalAs = (MarshalAsAttribute)Attribute.GetCustomAttribute(
                field,
                typeof(MarshalAsAttribute)
            );

            if (marshalAs == null)
            {
                throw new Exception("MarshalAsAttribute not found on field " + field.Name);
            }

            if (marshalAs.SizeConst <= 0)
            {
                throw new Exception($"MarshalAsAttribute size count({marshalAs.SizeConst}) err");
            }

            // 返回SizeConst值(未设置时返回-1)
            var arrayElementType = fieldType.GetElementType();
            var arraySubTypeSize = Marshal.SizeOf(arrayElementType);

            marshSize = arraySubTypeSize * marshalAs.SizeConst;
        }
        else
        {
            marshSize = Marshal.SizeOf(fieldType);
        }

        // check for sub-fields to recurse if necessary
        var subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray();

        var effectiveOffset = startOffset + offset;

        if (subFields.Length == 0)
        {
            Array.Reverse(data, effectiveOffset, marshSize);
        }
        else
        {
            // recurse
            MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset);
        }
    }
}

发送处理大端小端

发送时需要匹配对方大小端时在发送字节数据前将调用的数据经过MaybeAdjustEndianness处理即可

posted @ 2021-01-07 20:11  Hey,Coder!  阅读(2390)  评论(0)    收藏  举报