Выделение коллекции байтов с прямым порядком байтов в структуру для извлечения значений - PullRequest
10 голосов
/ 19 марта 2010

Есть проницательный вопрос о чтении структуры данных C / C ++ в C # из байтового массива , но я не могу заставить код работать для моей коллекции байтов с прямым порядком байтов (сетевой порядок байтов) , (РЕДАКТИРОВАТЬ: обратите внимание, что моя реальная структура имеет более чем одно поле.) Есть ли способ упорядочить байты в версию структуры с прямым порядком байтов, а затем извлечь значения в порядке байтов структуры (хоста) , который обычно имеет младший порядок)?

(Обратите внимание, что обращение массива байтов не сработает - байты каждого значения должны быть обращены, что не дает вам такой же набор, как и обращение всех байтов.)

Это должно обобщить то, что я ищу (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 
{
    ???
}

Другие полезные ссылки:

Байт структуры и порядковые номера

Еще немного о байтах и ​​порядке байтов (порядок байтов)

Более эффективное чтение двоичных файлов с использованием C #

Небезопасно и чтение из файлов

Вклад Моно в выпуск

Освоение структур C #

Ответы [ 8 ]

10 голосов
/ 22 февраля 2013

Вот еще одно решение для замены порядка байтов.

Он настроен на Адам Робинсонс решение здесь: 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 голосов
/ 19 мая 2011

Как уже упоминалось в моем комментарии к ответу @ weismat, существует простой способ достижения структуры с прямым порядком байтов. Он включает в себя двойное обращение: исходные байты полностью обращаются, затем сама структура является обращением к исходному (big-endian) формату данных.

fooLe и fooBe в Main будут иметь одинаковые значения для всех полей. (Обычно структура с прямым порядком байтов и байты, конечно, не присутствуют, но это ясно показывает связь между порядками байтов.)

ПРИМЕЧАНИЕ. См. обновленный код , включая инструкции по получению байтов из структуры.

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 голос
/ 21 апреля 2010

Я наконец-то нашел способ, который не подразумевал рефлексию и в основном удобен для пользователя. Он использует класс DataConverter Mono ( source ), который, к сожалению, на данный момент довольно глючный. (Например, значения типа float и double не работают должным образом, синтаксический анализ строки не работает и т. Д.)

Хитрость заключается в том, чтобы распаковать и повторно упаковать байты как big-endian, для чего требуется строка, описывающая, какие типы находятся в байтовом массиве (см. Последний метод). Также, выравнивание байтов является сложным: в байтах четыре структура вместо одного, потому что маршалинг, кажется, полагается на 4-байтовое выравнивание (я все еще не совсем понимаю эту часть). (РЕДАКТИРОВАТЬ: я обнаружил, что добавление Pack=1 к атрибуту StructLayout обычно решает проблемы с выравниванием байтов.)

Обратите внимание, этот пример кода использовался в 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);
}
1 голос
/ 31 марта 2010

Кажется, должно быть более элегантное решение, но это должно, по крайней мере, помочь вам:

    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;
    }
1 голос
/ 30 марта 2010

Я согласен с @weismat и считаю, что нет решения.

То, что вы показываете в своем примере, - это то, что вы можете получить доступ к необработанному байтовому буферу, как если бы это была какая-то ДРУГАЯ структура, ничего не изменяя, не копируя и не перемещая данные, ничего. Просто закрепите его, чтобы избежать перемещения из-за GC.

Это в основном то, чего вы обычно добиваетесь в C, используя тип объединения, содержащий как целевую структуру, так и байтовый массив одинакового размера.

Хорошая сторона в том, что он действительно эффективен.

Это имеет несколько недостатков, главным из которых является то, что вы можете получить доступ таким образом только к данным, которые находятся в собственном машинном порядке (будь то LE или BE). Следовательно, ваша ByteArrayToStructure на самом деле не LE, это только потому, что процессор под ней - LE. Если вы скомпилируете ту же программу на другой цели, которая оказывается BE, она будет работать по-другому, и вы считаете, что ваш байтовый массив - BE.

Другие недостатки заключаются в том, что вы должны быть очень осторожны с выравниванием данных, знать о возможном заполнении и т. Д. И, конечно, нет способа изменить порядок байтов с LE на BE без перемещения данных в массиве байтов (если у вас есть 16-битный массив, состоящий только из целых чисел, поскольку в вашем примере это просто замена каждые два байта).

У меня случилась похожая проблема, и я решил не использовать это решение из-за предыдущих недостатков и решил скрыть свои структуры ввода за аксессорами, чтобы скрыть доступ к массиву байтов внизу. Это может быть не так элегантно, но это просто и также позволяет избежать копирования буфера или перемещения данных любым способом.

0 голосов
/ 31 марта 2010

Традиционным решением является использование 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 * ().

На платформах с прямым порядком байтов функции обычно не используются и поэтому должны иметь нулевую стоимость.

0 голосов
/ 19 марта 2010

С моей точки зрения, вам просто нужно добавить Array.Reverse () перед преобразованием байтового массива.

0 голосов
/ 19 марта 2010

Вы пробовали MiscUtil? У него есть служебный класс с именем EndianBitConverter для преобразования между байтовыми массивами с большим и младшим байтами.

...