Маршалинг коллекции байтов с прямым порядком байтов в структуру для извлечения значений

Есть проницательный вопрос о чтении данных 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 #

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

Вклад Mono в решение проблемы

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


person Pat    schedule 19.03.2010    source источник
comment
Взгляните на это: stackoverflow.com/a/2624377/1254743 Он делает это еще более детально, что вы при необходимости легко поменять. И вам не нужно создавать свои структуры дважды (особенно хорошо, если у вас есть вложенные структуры).   -  person Onur    schedule 22.02.2013
comment
Я думаю, что библиотека PODCaster (zer7.com/software/podcaster и на NuGet) может быть нацелена в этой проблеме, но я, честно говоря, не могу сказать, как это предполагается использовать, даже по образцам.   -  person Pat    schedule 23.11.2013


Ответы (8)


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

Это настроено из решения Адама Робинсона здесь: 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;
    }

}
person Onur    schedule 22.02.2013
comment
Я подозреваю, что этот код в конечном итоге изменит порядок любых элементов внутри ByValArrays ... нужно будет добавить специальный случай, например, для перечислений, для работы с массивами, если это необходимо. - person user169771; 24.06.2021
comment
Ты пробовал это? - person Onur; 26.06.2021

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

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;
}
person Pat    schedule 18.05.2011
comment
Какие? Это не должно работать со строкой. - person Vlad; 03.01.2021

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

    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;
    }
person 500 - Internal Server Error    schedule 30.03.2010
comment
Ах да, размышление - я боялся, что кто-то ответит размышлением ;-). - person Pat; 01.04.2010
comment
Что такое __makeref (используется в части UInt16)? - person Pat; 29.01.2011
comment
SetValueDirect требует TypedReference (сочетание управляемого указателя и типа того, на что он указывает) для структуры данных, член которой мы устанавливаем. Это возвращает функция __makeref. - person 500 - Internal Server Error; 31.01.2011
comment
Хуже, чем отправить электронное письмо на почтовый сервер на Луне, а затем отправиться туда на автомобиле, запатентованном бензином, за содержимым. В 99% случаев сериализация должна быть эффективной, не используйте ее, если не используете 1%. - person Orestis P.; 07.10.2018

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

Хитрость заключается в том, чтобы распаковать и повторно упаковать байты с прямым порядком байтов, для чего требуется строка, описывающая, какие типы находятся в массиве байтов (см. Последний метод). Кроме того, выравнивание байтов сложно: вместо этого в структуре четыре байта одного, потому что маршалинг, кажется, полагается на 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);
}
person Pat    schedule 20.04.2010

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

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

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

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

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

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

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

person kriss    schedule 29.03.2010

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

person JaredPar    schedule 19.03.2010
comment
да. Это не сработает, потому что он имеет дело только с байтами, необходимыми для определенного значения, например превращение {0, 1} в ushort из 1, а не для целых структур. - person Pat; 19.03.2010

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

person weismat    schedule 19.03.2010
comment
См. Обновления моего вопроса, которые должны прояснить (даже лучше), что реверсирование массива не работает, потому что в структуре есть несколько значений. - person Pat; 19.03.2010
comment
Хорошо, но я сомневаюсь, что существует возможное универсальное решение, поскольку вам нужно знать размеры полей для реверсирования - вам нужно отменить GetBytes из класса BitConverter для каждого поля. - person weismat; 19.03.2010
comment
Верно, чем я сейчас и занимаюсь. Но решение для случая с прямым порядком байтов настолько элегантно, что я хочу, чтобы оно работало для моего случая с прямым порядком байтов! - person Pat; 19.03.2010
comment
Ну, очевидно, я не думал об этом до конца. Если байты полностью перевернуты, структура может принять формат данных в обратном порядке, тогда каждое поле будет правильно перевернуто! - person Pat; 19.05.2011

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

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

person Chromatix    schedule 31.03.2010