Вопрос

Недавно я столкнулся с ситуацией, когда мне нужно создать общий метод для чтения типа данных из массива байтов.

Я создал следующий класс:


public class DataStream
{
    public int Offset { get; set; }

    public byte[] Data { get; set; }

    public T Read<T>() where T : struct
    {
        unsafe
        {
            int dataLen = Marshal.SizeOf( typeof( T ) );
            IntPtr dataBlock = Marshal.AllocHGlobal( dataLen );


            Marshal.Copy( Data, Offset, dataBlock, dataLen );


            T type = *( ( T* )dataBlock.ToPointer() );

            Marshal.FreeHGlobal( dataBlock );

            Offset += dataLen;

            return type;
        }
    }
}

Теперь, если оставить в стороне проблемы с дераспределением, этот код не компилируется с таким сообщением:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')

Что кажется странным, потому что вы должны иметь возможность выполнять вышеуказанные операции на основе where T : struct ограничение на метод.

Если этот код ужасно неверен, есть ли какой-нибудь простой способ взять серию байтов и преобразовать их в 'T' тип?

Спасибо!

Это было полезно?

Решение

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

Другие советы

Поскольку ответ уже был дан, позвольте мне объяснить, почему ваш исходный код вам не помог:

Что кажется странным, потому что вы должны иметь возможность выполнять вышеуказанные операции в зависимости от того, где T :Ограничение структуры метода.

Не совсем.Вы можете иметь необработанные указатели на неуправляемые типы.В спецификации языка C# (18.2) это определяется следующим образом:

В отличие от ссылок (значений ссылочных типов), указатели не отслеживаются сборщиком мусора — сборщик мусора ничего не знает об указателях и данных, на которые они указывают.По этой причине указателю не разрешается указывать на ссылку или на структуру, содержащую ссылки, а референтный тип указателя должен быть неуправляемый тип.Ан неуправляемый тип — это любой тип, который не является ссылочным типом и не содержит полей ссылочного типа ни на каком уровне вложенности.Другими словами, неуправляемый тип является одним из следующих:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, или bool.
  • Любой тип перечисления.
  • Любой тип указателя.
  • Любой определяемый пользователем тип структуры который содержит поля неуправляемые типы только.

Таким образом, существует довольно много ограничений, и для универсального метода T:struct может соответствовать или не соответствовать им для какого-либо конкретного экземпляра, поэтому сконструируйте как T* является незаконным.Было бы неплохо иметь специальное ограничение параметра универсального типа для охвата неуправляемых типов, но в CLR его нет.

В какой-то момент я написал Эта статья объясняющий, как именно это сделать, но во много раз быстрее, чем Marshal.PtrToStructure.В примере кода используется динамическая генерация кода для копирования универсального типа T в битовый поток или из него.

Предполагая:

  • Последовательная или явная структура (в противном случае очень плохая идея)
  • Правильные размеры данных (следует предварительно проверить и кинуть)

unsafe TStruct BytesToStructure<TStruct>(byte[] data) where TStruct : struct
{
    fixed (byte* dataPtr = data)
        return (TStruct)Marshal.PtrToStructure(new IntPtr(dataPtr), typeof(TStruct));
}

unsafe byte[] StructureToBytes<TStruct>(TStruct st) where TStruct : struct
{
    var bytes = new byte[Marshal.SizeOf(st)];
    fixed (byte* ptr = bytes) Marshal.StructureToPtr(st, new IntPtr(ptr), true);
    return bytes;
}

Я написал это некоторое время назад, чтобы сделать то же самое: http://www.codeproject.com/Articles/33713/Generic-BinaryReader-and-BinaryWriter-Extensions

Однако вам придется добавить проект C++/CLI в ваше решение Visual Studio.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top