将大端字节集合编组到结构中以提取值

Posted

技术标签:

【中文标题】将大端字节集合编组到结构中以提取值【英文标题】:Marshalling a big-endian byte collection into a struct in order to pull out values 【发布时间】:2011-01-29 14:56:39 【问题描述】:

有一个关于 reading a C/C++ data structure in C# from a byte array 的有见地的问题,但我无法让代码适用于我的大端(网络字节顺序)字节集合。 (编辑:请注意,我真正的结构不止一个字段。)有没有办法将字节编组为结构的大端版本,然后提取框架字节序中的值(主机的字节序) ,通常是小端)?

(注意,反转字节数组将起作用 - 每个值的字节必须反转,这不会为您提供与反转所有字节相同的集合。)

这应该总结我在寻找什么(LE=LittleEndian,BE=BigEndian):

void Main()

    var leBytes = new byte[] 1, 0, 2, 0;
    var beBytes = new byte[] 0, 1, 0, 2;
    Foo fooLe = ByteArrayToStructure<Foo>(leBytes);
    Foo fooBe = ByteArrayToStructureBigEndian<Foo>(beBytes);
    Assert.AreEqual(fooLe, fooBe);


[StructLayout(LayoutKind.Explicit, Size=4)]
public struct Foo  
    [FieldOffset(0)] 
    public ushort firstUshort;
    [FieldOffset(2)] 
    public ushort secondUshort;


T ByteArrayToStructure<T>(byte[] bytes) where T: struct 

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
    handle.Free();
    return stuff;


T ByteArrayToStructureBigEndian<T>(byte[] bytes) where T: struct 

    ???

其他有用的链接:

Byte of a struct and onto endian concerns

A little more on bytes and endianness (byte order)

Read binary files more efficiently using C#

Unsafe and reading from files

Mono's contribution to the issue

Mastering C# structs

【问题讨论】:

看看这个:***.com/a/2624377/1254743 它做得更加细粒度,如果需要,您可以轻松更改。而且你不需要构建你的结构两次(如果你有嵌套的结构特别好)。 我认为 PODCaster 库(zer7.com/software/podcaster 和 NuGet 上)可能是针对这个问题的,但老实说,即使从示例中我也无法判断它应该如何使用。 【参考方案1】:

这是交换字节顺序的另一种解决方案。

这里从Adam Robinsons解决方案调整:https://***.com/a/2624377/1254743

它甚至可以处理嵌套结构。

public static class FooTest

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Foo2
    
        public byte b1;
        public short s;
        public ushort S;
        public int i;
        public uint I;
        public long l;
        public ulong L;
        public float f;
        public double d;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string MyString;
    

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Foo
    
        public byte b1;
        public short s;
        public ushort S;
        public int i;
        public uint I;
        public long l;
        public ulong L;
        public float f;
        public double d;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string MyString;
        public Foo2 foo2;
    

    public static void test()
    
        Foo2 sample2 = new Foo2()
        
            b1 = 0x01,
            s = 0x0203,
            S = 0x0405,
            i = 0x06070809,
            I = 0x0a0b0c0d,
            l = 0xe0f101112131415,
            L = 0x161718191a1b1c,
            f = 1.234f,
            d = 4.56789,
            MyString = @"123456789", // null terminated => only 9 characters!
        ;

        Foo sample = new Foo()
        
            b1 = 0x01,
            s = 0x0203,
            S = 0x0405,
            i = 0x06070809,
            I = 0x0a0b0c0d,
            l = 0xe0f101112131415,
            L = 0x161718191a1b1c,
            f = 1.234f,
            d = 4.56789,
            MyString = @"123456789", // null terminated => only 9 characters!
            foo2 = sample2,
        ;

        var bytes_LE = Dummy.StructToBytes(sample, Endianness.LittleEndian);
        var restoredLEAsLE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.LittleEndian);
        var restoredLEAsBE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.BigEndian);

        var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian);
        var restoredBEAsLE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.LittleEndian);
        var restoredBEAsBE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.BigEndian);

        Debug.Assert(sample.Equals(restoredLEAsLE));
        Debug.Assert(sample.Equals(restoredBEAsBE));
        Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE));
    

    public enum Endianness
    
        BigEndian,
        LittleEndian
    

    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);

            // 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, Marshal.SizeOf(fieldType));
            
            else
            
                // recurse
                MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset);
            
        
    

    internal static T BytesToStruct<T>(byte[] rawData, Endianness endianness) where T : struct
    
        T result = default(T);

        MaybeAdjustEndianness(typeof(T), rawData, endianness);

        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);

        try
        
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
        
        finally
        
            handle.Free();
        

        return result;
    

    internal static byte[] StructToBytes<T>(T data, Endianness endianness) where T : struct
    
        byte[] rawData = new byte[Marshal.SizeOf(data)];
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(data, rawDataPtr, false);
        
        finally
        
            handle.Free();
        

        MaybeAdjustEndianness(typeof(T), rawData, endianness);

        return rawData;
    


【讨论】:

我怀疑这段代码最终会颠倒 ByValArrays 中任何元素的顺序...需要添加一个特殊情况,比如为枚举添加了一个,以便在需要时对数组进行操作。 为我工作! [StructLayout(LayoutKind.Sequential, Pack = 1)]【参考方案2】:

正如我在对@weismat 的回答的评论中提到的,有一种简单的方法可以实现大端结构。它涉及双重反转:将原始字节完全反转,然后结构本身就是原始(大端)数据格式的反转。

Main 中的 fooLefooBe 对于所有字段将具有相同的值。 (通常情况下,little-endian 结构和字节当然不会出现,但这清楚地表明了字节顺序之间的关系。)

注意:请参阅updated code,包括如何从结构中取回字节。

public void Main()

    var beBytes = new byte[] 
        0x80, 
        0x80,0, 
        0x80,0, 
        0x80,0,0,0, 
        0x80,0,0,0,
        0x80,0,0,0,0,0,0,0, 
        0x80,0,0,0,0,0,0,0, 
        0x3F,0X80,0,0, // float of 1 (see http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness)
        0x3F,0xF0,0,0,0,0,0,0, // double of 1
        0,0,0,0x67,0x6E,0x69,0x74,0x73,0x65,0x54 // Testing\0\0\0
    ;
    var leBytes = new byte[] 
        0x80, 
        0,0x80,
        0,0x80, 
        0,0,0,0x80,
        0,0,0,0x80, 
        0,0,0,0,0,0,0,0x80, 
        0,0,0,0,0,0,0,0x80, 
        0,0,0x80,0x3F, // float of 1
        0,0,0,0,0,0,0xF0,0x3F, // double of 1
        0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0
    ;
    Foo fooLe = ByteArrayToStructure<Foo>(leBytes).Dump("LE");
    FooReversed fooBe = ByteArrayToStructure<FooReversed>(beBytes.Reverse().ToArray()).Dump("BE");  


[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Foo  
    public byte b1;
    public short s;
    public ushort S;
    public int i;
    public uint I;
    public long l;
    public ulong L;
    public float f;
    public double d;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string MyString;


[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FooReversed  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string MyString;
    public double d;
    public float f;
    public ulong L;
    public long l;
    public uint I;
    public int i;
    public ushort S;
    public short s;
    public byte b1;


T ByteArrayToStructure<T>(byte[] bytes) where T: struct 

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
    handle.Free();
    return stuff;

【讨论】:

【参考方案3】:

似乎必须有一个更优雅的解决方案,但这至少应该让你继续前进:

    static T ByteArrayToStructureBigEndian<T>(byte[] bytes) where T : struct
    
        GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        System.Type t = stuff.GetType();
        FieldInfo[] fieldInfo = t.GetFields();
        foreach (FieldInfo fi in fieldInfo)
                         
            if (fi.FieldType == typeof(System.Int16))
            
                // TODO
            
            else if (fi.FieldType == typeof(System.Int32))
            
                // TODO
            
            else if (fi.FieldType == typeof(System.Int64))
            
                // TODO
            
            else if (fi.FieldType == typeof(System.UInt16))
            
                UInt16 i16 = (UInt16)fi.GetValue(stuff);
                byte[] b16 = BitConverter.GetBytes(i16);
                byte[] b16r = b16.Reverse().ToArray();
                fi.SetValueDirect(__makeref(stuff), BitConverter.ToUInt16(b16r, 0);
            
            else if (fi.FieldType == typeof(System.UInt32))
            
                // TODO
            
            else if (fi.FieldType == typeof(System.UInt64))
            
                // TODO
            
        
        return stuff;
    

【讨论】:

啊,是的,反思——我害怕有人会用反思来回答;-)。 什么是__makeref(用于UInt16部分)? SetValueDirect 需要一个 TypedReference(托管指针和指向的类型的组合)指向我们正在设置的成员的数据结构。 __makeref 函数返回 this。 性能比发送电子邮件到月球上的邮件服务器,然后通过奔驰专利汽车去那里获取内容更糟糕。 99% 的时间序列化需要高性能,除非你在 1% 上,否则不要使用它。【参考方案4】:

我终于找到了一种不涉及反射并且主要是用户友好的方法。它使用 Mono 的 DataConverter 类 (source),不幸的是,此时它相当有问题。 (例如,浮点数和双精度数似乎无法正常工作,字符串解析被破坏等)

诀窍是将字节解包并重新打包为 big-endian,这需要一个字符串来描述字节数组中的类型(参见最后一种方法)。此外,字节对齐很棘手:有四个字节struct 而不是一个,因为封送处理似乎依赖于 4 字节对齐(我仍然不太了解那部分)。 (编辑:我发现将Pack=1 添加到StructLayout attribute 通常可以解决字节对齐问题。)

注意,此示例代码用于 LINQPad - Dump 扩展方法仅打印有关对象的信息并返回对象(它很流畅)。

public void Main()

    var beBytes = new byte[] 
        0x80, 
        0x80, 
        0x80, 
        0x80, 
        0x80,0, 
        0x80,0, 
        0x80,0,0,0, 
        0x80,0,0,0,
        0x80,0,0,0,0,0,0,0, 
        0x80,0,0,0,0,0,0,0, 
//      0,0,0x80,0x3F, // float of 1
//      0,0,0,0,0,0,0xF0,0x3F, // double of 1
        0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0
    ;
    var leBytes = new byte[] 
        0x80, 
        0x80, 
        0x80, 
        0x80, 
        0,0x80,
        0,0x80, 
        0,0,0,0x80,
        0,0,0,0x80, 
        0,0,0,0,0,0,0,0x80, 
        0,0,0,0,0,0,0,0x80, 
//      0,0,0x80,0x3F, // float of 1
//      0,0,0,0,0,0,0xF0,0x3F, // double of 1
        0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0
    ;
    Foo fooLe = ByteArrayToStructure<Foo>(leBytes).Dump("LE");
    Foo fooBe = ByteArrayToStructureBigEndian<Foo>(beBytes, 
        "bbbbsSiIlL"
//      + "fd" // float, then double
        +"9bb").Dump("BE");
    Assert.AreEqual(fooLe, fooBe);


[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Foo  
    public byte b1;
    public byte b2;
    public byte b3;
    public byte b4;
    public short s;
    public ushort S;
    public int i;
    public uint I;
    public long l;
    public ulong L;
//  public float f;
//  public double d;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string MyString;


T ByteArrayToStructure<T>(byte[] bytes) where T: struct 

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
    handle.Free();
    return stuff;


T ByteArrayToStructureBigEndian<T>(byte[] bytes, string description) where T: struct 

    byte[] buffer = bytes;
    IList unpacked = DataConverter.Unpack("^"+description, buffer, 0).Dump("unpacked");
    buffer = DataConverter.PackEnumerable("!"+description, unpacked).Dump("packed");
    return ByteArrayToStructure<T>(buffer);

【讨论】:

【参考方案5】:

我同意@weismat 并相信没有解决方案。

您在示例中展示的是,您可以像访问任何其他结构一样访问原始字节缓冲区,而无需对其进行任何更改,无需复制或移动数据,什么都没有。只是固定它以避免它因为 GC 而四处移动。

这基本上是您通常在 C 中通过使用包含目标结构和相同大小的字节数组的联合类型来实现的。

好的一面是它真的很高效。

这有几个缺点,主要是您只能通过这种方式访问​​本机机器顺序(无论是 LE 还是 BE)中的数据。因此,您的 ByteArrayToStructure 并不是真正的 LE,只是因为下面的处理器是 LE。如果您在另一个恰好是 BE 的目标上编译相同的程序,它会以另一种方式工作并相信您的字节数组是 BE。

其他缺点是您必须对数据对齐非常谨慎,注意可能的填充等。当然,如果不移动字节数组中的数据,就无法将字节顺序从 LE 更改为 BE(如果您有一个 16 位整数数组,如您的示例所示,这只是每两个字节交换一次)。

我碰巧遇到了类似的问题,由于之前的缺点,我不想使用这个解决方案,并选择将我的输入结构隐藏在访问器后面,以隐藏对下面字节数组的访问。它可能没有那么优雅,但它很简单,也避免了以任何方式复制缓冲区或移动数据。

【讨论】:

【参考方案6】:

您尝试过 MiscUtil 吗?它有一个名为EndianBitConverter 的实用程序类,用于在大小端字节数组之间进行转换。

http://www.yoda.arachsys.com/csharp/miscutil/

【讨论】:

是的。那是行不通的,因为它只处理某个值所需的字节,例如将0, 1 变成ushort1,而不是整个结构。【参考方案7】:

从我的角度来看,您只需要在字节数组的转换之前添加一个 Array.Reverse() 即可。

【讨论】:

查看我的问题的更新,应该澄清(甚至更好)反转数组不起作用,因为结构中有多个值。 好的 - 但我怀疑是否存在通用解决方案,因为您需要知道反转的字段大小 - 您需要反转每个字段的 BitConverter 类中的 GetBytes。 对,这就是我目前正在做的。但是小端情况的解决方案非常优雅,我希望它适用于我的大端情况! 好吧,我显然并没有从头到尾考虑到这一点。如果字节完全反转,则结构可以以相反的顺序采用数据格式,然后每个字段都正确反转!【参考方案8】:

传统的解决方案是使用 ntohl() 和 ntohs()。

typedef struct 
  long foo;
  short bar, baz;
  char xyzzy;
 Data;

Data d;
memcpy(&d, buffer, sizeof(Data));

d.foo = ntohl(d.foo);
d.bar = ntohs(d.bar);
d.baz = ntohs(d.baz);
// don't need to change d.xyxxy

以上内容适用于任何具有 BSD 套接字的平台,无论是大端、小端,还是像 VAX 这样非常奇怪的东西。反向操作是使用 hton*() 完成的。

在大端平台上,函数通常是无操作的,因此应该是零成本。

【讨论】:

以上是关于将大端字节集合编组到结构中以提取值的主要内容,如果未能解决你的问题,请参考以下文章

内存对齐 大端字节,序小端字节序验证

小端大端

小端大端

大端小端的概念

大端字节序和小端字节序

大端字节序和小端字节序