Как взять набор байтов и вытащить из него типизированные значения?

Скажем, у меня есть набор байтов

var bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};

и я хочу извлечь определенное значение из байтов как управляемый тип, например а ushort. Каков простой способ определить, какие типы находятся в каком месте коллекции, и извлечь эти значения?

Один (уродливый) способ - использовать System.BitConverter и Queue или byte[] с индексом и просто перебирать, например:

int index = 0;
ushort first = System.BitConverter.ToUint16(bytes, index);
index += 2; // size of a ushort
int second = System.BitConverter.ToInt32(bytes, index);
index += 4;
...

Этот метод становится очень и очень утомительным, когда вы имеете дело с большим количеством этих структур!

Я знаю, что есть System.Runtime.InteropServices.StructLayoutAttribute, который позволяет мне определять расположение типов внутри структуры или класса, но, похоже, нет способа импортировать коллекцию байтов в эту структуру. Если бы я мог как-то наложить структуру на коллекцию байтов и вытащить значения, это было бы идеально. Например.

Foo foo = (Foo)bytes; // doesn't work because I'd need to implement the implicit operator
ushort first = foo.first;
int second = foo.second;
...
[StructLayout(LayoutKind.Explicit, Size=FOO_SIZE)]
public struct Foo  {
    [FieldOffset(0)] public ushort first;
    [FieldOffset(2)] public int second;
}

Есть мысли о том, как этого добиться?

[РЕДАКТИРОВАТЬ: См. Также мой вопрос о том, что делать с байтами, когда они имеют обратный порядок байтов.]


person Pat    schedule 18.03.2010    source источник
comment
Все ли интересующие вас типы целочисленные (или фиксированные массивы интегралов)?   -  person gooch    schedule 18.03.2010
comment
@gooch: Нет, но мы могли бы согласиться на это и написать вспомогательные методы, когда у нас есть строки.   -  person Pat    schedule 18.03.2010
comment
Хорошо, это неплохо работает для целочисленных типов. Это немного громоздко для массивов этих типов, поскольку они должны быть обозначены как фиксированные (что по сути означает, что это указатель на начало памяти). Мы не смогли добиться других типов, но есть способ указать размер для каждого поля, это быстро становится сложно.   -  person gooch    schedule 18.03.2010


Ответы (2)


Мы сделали это довольно много, поскольку мы общаемся напрямую с оборудованием через байты по последовательному каналу.

Учитывая определение структуры

[StructLayout(LayoutKind.Explicit, Size=FOO_SIZE)]
public struct Foo  {
    [FieldOffset(0)] public ushort first;
    [FieldOffset(2)] public int second;
}

Вы можете использовать такой класс для выполнения преобразования

public class ByteArrayToStruct<StructType>
{
    public StructType ConvertToStruct(int size, byte[] thebuffer)
    {
        try
        {
            int theSize = size;
            IntPtr ptr1 = Marshal.AllocHGlobal(theSize);
            Marshal.Copy(thebuffer, 0, ptr1, theSize);
            StructType theStruct = (StructType)Marshal.PtrToStructure(ptr1, typeof(StructType));
            Marshal.FreeHGlobal(ptr1);
            return theStruct;
        }
        catch (Exception)
        {
            return default(StructType);
        }
    }
}

И наоборот, вы также можете создать List из массива и сделать что-то вроде следующего:

ushort first = BitConverter.ToInt16(myList.ToArray(), 0);
myList.RemoveRange(0, sizeof(ushort));
[...]

По сути, это будет держать соответствующие данные в «голове» списка, поэтому вам не придется отслеживать позицию в массиве.

person gooch    schedule 18.03.2010
comment
Хороший! Это то, что я искал, по крайней мере, из моих предварительных тестов. - person Pat; 18.03.2010
comment
О нет, это работает только для байтов с прямым порядком байтов (так как моя хост-машина имеет прямой порядок байтов)! Есть ли у вас способ обхода байтов с прямым порядком байтов? Обратите внимание, недостаточно изменить весь массив байтов, так как это каждое значение, которое поменяло местами порядок следования байтов: например, из {1, 0, 2, 0, 0, 0} вы могли бы вытащить ushort из 1 и uint из 2, но изменение байтов даст вам ushort 0 и uint 0x00020001 или 131073. - person Pat; 19.03.2010
comment
По сути, мне нужно, чтобы структура при получении байтов действовала так, как если бы она была с прямым порядком байтов, а затем позволяла мне извлекать из нее значения, как если бы это был нормальный порядок байтов для управляемого кода .NET. - person Pat; 19.03.2010
comment
Я понимаю что ты имеешь ввиду. Если вы не против использования сторонней библиотеки, я нашел это сообщение SO: stackoverflow.com/questions/217980/) - person gooch; 19.03.2010
comment
Я не против использования библиотеки, но в коде Джона не говорится о том, как заставить структуру работать с прямым порядком байтов. Его код просто добавляет в System.BitConverter функции с прямым порядком байтов (что я уже сделал в моей собственной библиотеке [жаль, что раньше я не мог найти класс Джона MiscUtil!]). Тем не менее, спасибо за поиск. - person Pat; 19.03.2010
comment
Что ж, я отдаю вам должное, @gooch, поскольку вы ответили на мой вопрос для случая с прямым порядком байтов и предоставили ключевые слова, которые мне нужны для продолжения поиска. Спасибо! - person Pat; 19.03.2010
comment
Мне очень жаль, что решение не удовлетворило ваши потребности. Это относительно безболезненный способ разместить байты в структурах. К сожалению, мы не работали со сценарием Big-Endian - ›Little-Endian. Это звучит особенно сложно, так как каждое значение необходимо поменять местами (не может выполняться общий обмен). - person gooch; 23.03.2010

сделай это первым способом. определите класс буфера, который имеет байты данных и курсор. Затем определите такие методы, как getInt16, getInt32 и т.д.

  Buffer b(bytes);
  ushort a = b.getInt16();
  int x = b.getInt32();

Это у меня в сумке с утилитами. У меня также есть противоположное, чтобы сделать буфер из строк ints, ....

person pm100    schedule 18.03.2010