Преобразование C # void * в байт [] - PullRequest
4 голосов
/ 03 апреля 2010

В C # мне нужно записать T [] в поток, в идеале без каких-либо дополнительных буферов. У меня есть динамический код, который преобразует T [] (где T - структура без объектов) в void * и исправляет его в памяти, и это прекрасно работает. Когда поток представлял собой файл, я мог использовать собственный Windows API для прямой передачи void *, но теперь мне нужно записать в общий объект Stream, который принимает байт [].

Вопрос: Может кто-нибудь предложить взломать способ создания фиктивного объекта массива, который фактически не имеет каких-либо распределений кучи, а скорее указывает на уже существующее (и фиксированное) расположение кучи?

Это тот псевдокод, который мне нужен:

void Write(Stream stream, T[] buffer)
{
    fixed( void* ptr = &buffer )    // done with dynamic code generation
    {
        int typeSize = sizeof(T);   // done as well

        byte[] dummy = (byte[]) ptr;   // <-- how do I create this fake array?

        stream.Write( dummy, 0, buffer.Length*typeSize );
    }
}  

Обновление: Я описал, как сделать fixed(void* ptr=&buffer) в этой статье . Я всегда мог создать байт [], исправить его в памяти и выполнить небезопасное байт-копирование из одного указателя в другой, а затем отправить этот массив в поток, но я надеялся избежать ненужного дополнительного выделения и копирования.

Невозможно Если подумать, в байте [] есть куча метаданных с размерами массива и типом элемента. Простая передача ссылки (указателя) на T [] как byte [] может не сработать, потому что метаданные блока все равно будут метаданными T []. И даже если структура метаданных идентична, длина T [] будет намного меньше байта [], поэтому любой последующий доступ к байту [] с помощью управляемого кода приведет к неверным результатам.

Запрошенная функция @ Microsoft Connect Пожалуйста, проголосуйте за этот запрос , надеюсь MS послушает.

Ответы [ 4 ]

3 голосов
/ 03 апреля 2010

Этот вид кода никогда не может работать в общем виде. Он опирается на твердое предположение, что расположение памяти для T предсказуемо и непротиворечиво. Это верно только в том случае, если T является простым типом значения. Игнорирование порядка байтов на мгновение. Вы мертвы в воде, если T является ссылочным типом, вы будете копировать дескрипторы отслеживания, которые никогда не могут быть десериализованы, вам придется дать T структурное ограничение.

Но этого недостаточно, типы структур также не могут быть скопированы. Даже если у них нет полей ссылочного типа, что-то, что вы не можете ограничить. Внутренний макет определяется JIT-компилятором. Он меняет поля на досуге, выбирая поле, в котором поля правильно выровнены, а значение структуры принимает минимальный размер хранилища. Значение, которое вы сериализуете, может быть правильно прочитано только той программой, которая работает с точно такой же архитектурой процессора и версией компилятора JIT.

В фреймворке уже есть множество классов, которые делают то, что вы делаете. Наиболее близким совпадением является класс .NET 4.0 MemoryMappedViewAccessor. Он должен выполнять ту же работу, делая необработанные байты доступными в файле отображения памяти. Рабочей лошадкой является класс System.Runtime.InteropServices.SafeBuffer, ознакомьтесь с Reflector. К сожалению, вы не можете просто скопировать класс, он использует CLR для преобразования. Опять же, это еще одна неделя, прежде чем она станет доступной.

0 голосов
/ 27 сентября 2012

Посмотрите эту статью Встроенный MSIL в C # / VB.NET и Generic Pointers лучший способ получить код мечты:)

0 голосов
/ 28 октября 2010

Проверьте мой ответ на связанный вопрос: Какой самый быстрый способ преобразования числа с плавающей запятой [] в байт []?

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

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

0 голосов
/ 03 апреля 2010

Поскольку stream.Write не может взять указатель, вы не можете избежать копирования памяти, поэтому у вас будет некоторое замедление. Возможно, вы захотите использовать BinaryReader и BinaryWriter для сериализации ваших объектов, но вот код, который позволит вам делать то, что вы хотите. Помните, что все члены T также должны быть структурами.

unsafe static void Write<T>(Stream stream, T[] buffer) where T : struct
{
    System.Runtime.InteropServices.GCHandle handle = System.Runtime.InteropServices.GCHandle.Alloc(buffer, System.Runtime.InteropServices.GCHandleType.Pinned);
    IntPtr address = handle.AddrOfPinnedObject();
    int byteCount = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)) * buffer.Length;
    byte* ptr = (byte*)address.ToPointer();
    byte* endPtr = ptr + byteCount;
    while (ptr != endPtr)
    {
        stream.WriteByte(*ptr++);
    }
    handle.Free();
}
...