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

Существует проницательный вопрос о чтении структуры данных 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(leBytes);
    Foo fooBe = ByteArrayToStructureBigEndian(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(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(byte[] bytes) where T: struct 
{
    ???
}

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

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

Немного больше на байтах и порядке байтов (порядок байтов)

Считайте двоичные файлы более эффективно с помощью C#

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

Вклад Mono в проблему

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

10
задан Community 23 May 2017 в 12:16
поделиться

6 ответов

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

-1
ответ дан 3 December 2019 в 23:49
поделиться

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

0
ответ дан 3 December 2019 в 23:49
поделиться

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

    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
ответ дан 3 December 2019 в 23:49
поделиться

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

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

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

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

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

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

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

1
ответ дан 3 December 2019 в 23:49
поделиться

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

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

-2
ответ дан 3 December 2019 в 23:49
поделиться

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

Хитрость заключается в том, чтобы распаковать и повторно упаковать байты с прямым порядком байтов, что требует наличия строки, описывающей, какие типы в массиве байтов (см. последний метод). Кроме того, с выравниванием байтов сложно: в структуре четыре байта вместо одного, потому что маршалинг, похоже, полагается на 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
ответ дан 3 December 2019 в 23:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: