Прочитать структуру как массив байт

181
13 декабря 2018, 17:40

Не уверен что такое возможно, но есть ли хоть что-то что может помочь прочитать структуру данных как массив байт? Допустим в си/c++ проблем с таковым нет. Приводим указатель на структуру к типу const char* и читаем данные из памяти основываясь на размере.

Это необходимо чтобы отправить структуру в виде массива байт удалённому серверу.

Answer 1

Отталкиваясь от последней фразы, напишу ответ. Для передачи данных на удаленный сервер обычно используется сериализация. Этот механизм превращает объект в простой тип для передачи по сети. Есть несколько вариантов:

  1. Если вас устроит простой JSON в качестве сериализованной модели, можно пользоваться распространенной библиотечкой Newtonsoft.Json, которую можно достать через NuGet.
  2. Библиотечкой из предыдущего пункта можно также сериализовать в BSON, например.
  3. Если вас интересует продвинутый вариант сериализации - можно посмотреть на ProtoBuf от Google.

В дополнение хочется сказать что для оптимизации пересылки пакетов через сеть лучше их предварительно сжимать. Особенно, если пересылка идёт через HTTP протокол, он поддерживает сжатие c помощью заголовков Accept-Encoding и Content-Encoding.

Answer 2

В дополнении к ответу @Gordory могу сказать следующее: при отправке данных по сети Вам по-любому придется работать с массивом байт. Так почему бы не взять блок памяти, отведенный под Вашу структуру, не скопировать его в byte[] и не отправить на удаленный сервер, где позже также выгрузить в память?

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

// Пример пакета
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public unsafe struct Packet
{
    [FieldOffset(0)]
    public int ID;
    // Int32 занимает 4 байта
    [FieldOffset(4)]
    // Строка будет храниться внутри структуры в виде фиксированного массива указанной длины
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string Message;
    public Packet(int ID, string Message)
    {
        this.ID = ID;
        // В ANSI 1 символ занимает 1 байт
        this.Message = Message.Length < 129 ? Message : throw new ArgumentException("Message is too long! Max length is 128 characters!", nameof(Message));
    }
}

Расширение для демонстрации:

public static class StructExtensions
{
    public static byte[] Zip<T>(this T Obj) where T : struct
    {
        // Получим размер, занимаемый объектом
        int size = Marshal.SizeOf(Obj);
        // Сюда будем читать данные
        byte[] bytes = new byte[size];
        // Выделим память и получим указатель на выделенный блок
        IntPtr ptr = Marshal.AllocHGlobal(size);
        try
        {
            // Пишем данные структуры в неуправляемый блок памяти
            Marshal.StructureToPtr(Obj, ptr, false);
            // Копируем данные
            Marshal.Copy(ptr, bytes, 0, size);
        }
        catch (Exception exception)
        {
            // Даже в случае ошибки память следует освободить
            Marshal.FreeHGlobal(ptr);
            throw exception;
        }
        Marshal.FreeHGlobal(ptr);
        return bytes;
    }
    // Не надо создавать расширения для стандартных типов
    // Здесь я это делаю чисто ради наглядности примера
    public static T Unzip<T>(this byte[] Bytes) where T : struct
    {
        // Получаем дексриптор (указываем, что он закреплен, чтобы сборщик мусора не баловался)
        GCHandle handle = GCHandle.Alloc(Bytes, GCHandleType.Pinned);
        try
        {
            // Читаем структуру из блока памяти
            T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
            handle.Free();
            // Возвращаем ее
            return theStructure;
        }
        catch (Exception exception)
        {
            // Даже в случае ошибки память следует освободить
            handle.Free();
            throw exception;
        }
    }
}

И сам тест:

// Инициализируем объект
int uid = 10;
string msg = "Hello, world!";
Packet packet = new Packet(uid, msg);
// Сериализуем его и тут же десериализуем обратно
Packet gotPacket = packet.Zip().Unzip<Packet>(); // ID == 10, Message == "Hello, world!"

Заметьте, что метод .Zip() вернет нам массив байт длинной 132 (4 на int и 128 на string), то есть ровно столько, сколько мы и храним
Конечно, данные не сжимаются, но и никакого лишнего "мусора" не примешивается, как в случае с сериализацией с помощью System.Runtime.Serialization

Суть данного метода в том, что мы просто копируем участок памяти, занимаемый структурой, в массив байт. Потом этот массив мы можем точно так же записать в участок памяти, отведенный под аналогичную (хотя бы по размерам) структуру
Именно поэтому важно отказаться (в рамках данного метода) от ссылочных типов, ибо на их "месте" находятся указатели на иные блоки памяти, где и хранятся их данные. Тем самым в рамках одной и той же машины копирование структуры, которая включает в себя ссылочные типы (вроде массивов), пройдет успешно (если указатели на объекты не сменились), а вот при передаче такого блока на удаленную машину Вы получите результаты весьма неожиданные (точнее, неопределенные)

Плюсом (пусть и немного спорным) данного метода является то, что двум связанным сервисам не нужно иметь в своем арсенале одну и ту же структуру, взятую из одной и той же сборки! Им необходимо и достаточно знать лишь построение этой структуры, а далее можно хоть типы менять)!

К примеру, добавим к нашему прошлому тесту такую структуру:

// "Весит" те же 132 байта, что и Packet
public unsafe struct Packet2
{
    // sizeof(int) == sizeof(uint)
    public uint ID;
    // Размер ANSI-строки в 128 символов равен 128 байтам
    public fixed byte Message[128];
}

И дополним наш код считывания этими строчками:

Packet2 endDataPacket = packet.Zip().Unzip<Packet2>(); // ID == 10, Message == ['H', 'e', ..., '!', '\0', ..., '\0']
string test = new string((sbyte*)endDataPacket.Message); // Hello, world!

Как видите, все работает, пусть мы и описали не в точности такую структуру, какую использовали до этого)

Надеюсь, мой метод будет Вам полезен, а мой ответ помог Вам разобраться, каким образом средствами C# можно считать структуру как массив байт)

READ ALSO
C# AspNetCore WebApp. Настройка байндинга для POST запроса

C# AspNetCore WebApp. Настройка байндинга для POST запроса

Если у меня есть модель данных в которой есть св-во представленное абстрактным классомЕсли я возвращаю ответ GET запросом, то JSON сериализатор...

154
ReactiveUI синхронный вызов ReactiveCommand вызывает System.InvalidOperationException

ReactiveUI синхронный вызов ReactiveCommand вызывает System.InvalidOperationException

Предположим у меня есть Button с привязкой к команде ViewModelВо ViewModel я пишу:

155
Из Visual Studio не создается бд FireBird

Из Visual Studio не создается бд FireBird

Не получается из VS подключиться к бд

142
Подсказка внутри DataGrid wpf

Подсказка внутри DataGrid wpf

при создании новой строки я установил в ячейку подсказку с текстом с помощью XAML стиля FallbackValue=Name, цвет подсказки стандартный черный, возможно...

126