Ошибка в небезопасном коде, чтение памяти с помощью указателей

У меня есть двоичный сериализованный объект в памяти, и я хочу прочитать его из памяти, используя указатели (небезопасный код) в С#. Пожалуйста, посмотрите на следующую функцию, которая читает из потока памяти.

static Results ReadUsingPointers(byte[] data)
{
    unsafe
    {
        fixed (byte* packet = &data[0])
        {
            return *(Results*)packet;
        }
    }
}

В этом операторе return *(Results*)packet; я получаю исключение времени компиляции "Невозможно получить адрес, получить размер или объявить указатель на результаты управляемого типа"

Вот моя структура

public struct Results
{
    public int Id;
    public int Score;
    public char[] Product;
}

Насколько я понимаю, все свойства моей структуры являются преобразовываемыми свойствами, тогда почему я получаю эту ошибку и что мне делать, если мне нужно использовать char[] в моей структуре?

EDIT-1 Позвольте мне объяснить подробнее (обратите внимание, что объекты являются макетами)...

Предыстория: у меня есть массив объектов Results, я сериализовал их с помощью двоичной сериализации. Теперь, на более поздних этапах моей программы, мне нужно как можно быстрее десериализовать свои данные в памяти, так как объем данных очень велик. Поэтому я пытался узнать, как небезопасный код может мне помочь.

Допустим, если моя структура не включает public char[] Product;, я получаю свои данные с достаточно хорошей скоростью. Но с char[] это дает мне ошибку (компилятор должен это сделать). Я искал решение, которое работает с char[] в этом контексте.


person ak1    schedule 21.04.2014    source источник
comment
Можете ли вы рассказать нам немного больше о данных, которые вы пытаетесь прочитать? В противном случае трудно сказать, как правильно обрабатывать данные массива.   -  person floele    schedule 21.04.2014


Ответы (2)


Вы не можете ожидать, что это сработает.

public struct Results
{
    public int Id;
    public int Score;
    public char[] Product;
}

Массив char[] Product является управляемым типом. Ваш код пытается использовать тип Results*. Это тип указателя. В документации указано, что вы можете объявить указатели на любое из следующего:

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

Теперь ваша структура явно не соответствует ни одному из первых трех пунктов. И не соответствует последнему маркеру, потому что массив является управляемым типом.

Насколько я понимаю, все свойства моей структуры являются преобразовываемыми свойствами.

Да, это верно, но не актуально. Вам нужно, чтобы члены структуры были более чем преобразовываемыми.


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

*(Results*)packet

Как компилятор мог превратить это во что-то, что создало бы новый массив и скопировало правильное количество элементов массива? Очевидно, что у компилятора нет никакой надежды сделать здесь что-то полезное, и, конечно же, именно поэтому язык отклоняет ваш код.

Я не думаю, что небезопасный код поможет вам здесь. Когда вы сериализуете свой массив, вам придется сериализовать длину, а затем содержимое массива. Чтобы десериализовать, вам нужно прочитать длину, создать новый массив этой длины, а затем прочитать содержимое. Небезопасный код не может помочь с этим. Простая копия в памяти статически определенного типа бесполезна, поскольку это означало бы, что длина массива была известна во время компиляции. Нет.


Что касается вашего обновления, вы сказали:

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

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

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

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

person David Heffernan    schedule 21.04.2014
comment
Я согласен с вами, но скажу, что у меня есть определенная длина, например. public char[256] Product; тогда есть ли шанс получить значения с помощью указателей или любым другим способом? - person ak1; 21.04.2014
comment
Я пробовал много других способов, но не получил желаемого прироста производительности, поэтому я ищу решение, которое могло бы сделать это быстрее, я думаю, что использование указателей - моя единственная надежда. - person ak1; 21.04.2014
comment
Думаю, я ответил на вопрос, который вы задали. - person David Heffernan; 21.04.2014
comment
Не могли бы вы поделиться, если у вас есть альтернатива, которая может хорошо работать, спасибо. - person ak1; 22.04.2014
comment
Я не знаю, каковы ваши требования к производительности. А сериализация вообще не моя специализация. Возможно, вам следует задать другой вопрос. То, что я пытался сделать здесь, было ответить на этот вопрос. - person David Heffernan; 22.04.2014

MSDN говорит:

Любой из следующих типов может быть типом указателя:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal или bool.

  • Любой тип перечисления.

  • Любой тип указателя.

  • Любой определяемый пользователем тип структуры, содержащий поля только неуправляемых типов.

Таким образом, вы можете определить свою структуру следующим образом, чтобы исправить ошибку компилятора:

public struct Results
{
    public int Id;
    public int Score;
    // Don't actually do this though.
    public unsafe char* Product;
}

Таким образом, вы можете указать на первый элемент массива.

Однако, исходя из вашего отредактированного вопроса, вам нужен другой подход.

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

Обычно для этой цели вы использовали бы BinaryFormatter. Если это слишком медленно, вопрос скорее должен заключаться в том, можно ли вообще избежать сериализации.

person floele    schedule 21.04.2014
comment
Первая часть вашего ответа верна. А вот вторая часть наверняка содержит вредные советы. Никакая сериализация, с которой я сталкивался, не работает путем записи указателей на потоки байтов. - person David Heffernan; 21.04.2014
comment
@DavidHeffernan Действительно, сериализация этого была бы довольно необычной. Это просто решение для ошибки компилятора. Конечно, есть и другие способы правильной сериализации, но нам может понадобиться узнать немного больше о структуре данных, которая здесь читается. - person floele; 21.04.2014
comment
Моя проблема с вашим ответом заключается в том, что он, кажется, благословляет использование unsafe char*, когда это должно быть неправильным решением проблемы. Мне не нравится, что ваш ответ делает это. - person David Heffernan; 21.04.2014
comment
@DavidHeffernan Я немного скорректировал свой ответ, возможно, мы получим от автора более подробную информацию, чтобы предложить правильное решение. - person floele; 21.04.2014
comment
Мне все еще не нравится предложение использовать unsafe char*. Наивный читатель подумает, что это хорошая идея, хотя на самом деле это не так. Почему вы его сохраняете? - person David Heffernan; 21.04.2014
comment
@floele, public unsafe char* Product; видимо, кажется, что я указываю первый символ и нахожу остальные по длине массива, но, как правило, длины неизвестны, как я могу найти остальные символы? - person ak1; 21.04.2014
comment
@floele Видите ли, этот комментарий от asif иллюстрирует мою точку зрения. Вы дали ложную надежду, что unsafe char* является жизнеспособным решением. Теперь мы просто потеряем время в этом тупике. - person David Heffernan; 21.04.2014
comment
@asif Я немного отредактировал свой ответ. Вопрос, который сейчас приходит мне на ум, заключается в том, можно ли избежать сериализации или создать ситуацию, когда нужно десериализовать только часть данных. Есть ли возможность оптимизировать ваш процесс до того, как дело дойдет до оптимизации двоичной сериализации? - person floele; 21.04.2014
comment
@floele В моем случае бинарная сериализация является частью моих требований, и, кстати, сериализация не так важна, но десериализация важна, из-за характера моей проблемы мне это нужно как можно быстрее. - person ak1; 21.04.2014
comment
@asif: А как насчет простого использования BinaryFormatter? Как это работает? Можете ли вы выбрать метод сериализации? - person floele; 21.04.2014
comment
Я уже пробовал это, но не соответствует моим требованиям. Я думаю, что protobuf-net наиболее эффективен, но даже он не соответствует моим требованиям, так как в моем случае критична производительность. - person ak1; 21.04.2014
comment
@asif: но даже это не соответствует моим требованиям - ну, если методы, которые некоторые люди долго и упорно думали об оптимизации, недостаточно хороши для вас, я сомневаюсь, что вы сможете найти лучшее решение, если не будете искать оптимизацию кроме кода сериализации. В любом случае, если вы следуете подходу указателя, вам нужно дополнительно сохранить длину массива (как указал Дэвид), чтобы правильно десириализировать, потому что в противном случае у вас нет способа узнать, сколько памяти нужно прочитать. - person floele; 21.04.2014