首页 文章

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

提问于
浏览
10

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

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

这应该总结我正在寻找的东西(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

8 回答

  • 10

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

    它在这里从Adam Robinsons解决方案进行了调整:https://stackoverflow.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;
        }
    
    }
    
  • 2

    正如我在@ weismat的答案中所提到的那样,有一种简单的方法来实现大端结构 . 它涉及双反转:原始字节完全颠倒,然后结构本身是原始(大端)数据格式的逆转 .

    Main 中的 fooLefooBe 将对所有字段具有相同的值 . (通常,当然,小端结构和字节不会出现,但这清楚地显示了字节顺序之间的关系 . )

    注意:请参阅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;
    }
    
  • 1

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

    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;
        }
    
  • 0

    我终于找到了一种方法,但不幸的是,在这一点上,这种方式很不合适 . [2560419_] (例如,浮点数和双精度数似乎不正常,字符串解析被破坏等)

    诀窍是将字节解包并重新打包为big-endian,这需要一个字符串来描述字节数组中的类型(参见最后一个方法) . 另外,字节对齐很棘手:结构中有四个字节而不是一个因为编组似乎依赖于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);
    }
    
  • -2

    我同意@weismat,并认为没有解决方案 .

    您在示例中显示的是,您可以访问原始字节缓冲区,就像它是任何其他结构一样,不会更改任何内容,不会复制或移动数据,也不会 . 只需固定它以避免它因GC而移动 .

    这基本上是你通常使用包含目标结构和相同大小的字节数组的union类型在C中实现的 .

    好的一面是它真的很有效率 .

    这有几个缺点,主要的一个原因是你只能以这种方式访问原生机器顺序的数据(无论是LE还是BE) . 因此你的ByteArrayToStructure不是真正的LE,只是因为下面的处理器是LE . 如果你在另一个碰巧是BE的目标上编译同一个程序,它会以另一种方式运行并相信你的字节数组是BE .

    其他缺点是你必须非常小心数据对齐,注意可能的填充等,当然,没有办法将字节顺序从LE更改为BE而不移动数据以字节为单位(如果你有16位整数只有在你的例子中的数组,这只是交换每两个字节) .

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

  • 1

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

  • 1

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

  • -1

    传统的解决方案是使用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 *()完成反向操作 .

    在大端平台上,功能通常是无操作,因此应该是零成本 .

相关问题