C# Char* в строку

Я много огляделся и не могу найти решение для чего-либо похожего на то, что я делаю. У меня есть два приложения: собственное приложение C++ и управляемое приложение C#. Приложение C++ выделяет пул байтов, которые используются в диспетчере памяти. Каждый объект, выделенный в этом диспетчере памяти, имеет заголовок, и каждый заголовок имеет char*, указывающий на имя, данное объекту. Приложение C# действует как средство просмотра этой памяти. Я использую файлы с отображением памяти, чтобы позволить приложению С# считывать память во время работы приложения С++. Моя проблема в том, что я пытаюсь прочитать имя объекта из структуры заголовка и отобразить его на С# (или просто сохранить его в строке, что угодно). Используя небезопасный код, я могу преобразовать четыре байта, составляющих char*, в IntPtr, преобразовать их в void* и вызвать Marshal.PtrToStringAnsi. Это код:

IntPtr namePtr = new IntrPtr(BitConverter.ToInt32(bytes, index));
unsafe
{
    void* ptr = namePtr.ToPointer();
    char* cptr = (char*)ptr;
    output = Marshal.PtrToStringAnsi((IntPtr)ptr);
}

В этом случае bytes — это массив, считанный из файла отображения памяти, который представляет весь пул байтов, созданный собственным приложением, а index — это индекс первого байта указателя имени.

Я проверил, что с управляемой стороны адрес, возвращаемый вызовом namePtr.ToPointer(), точно совпадает с адресом указателя имени в собственном приложении. Если бы это был нативный код, я бы просто привел ptr к char*, и все было бы хорошо, но в управляемом коде, который я читал, я должен использовать Marshaller для этого.

Этот код дает разные результаты. Иногда cptr имеет значение null, иногда указывает на \0, а иногда указывает на несколько азиатских символов (которые при выполнении метода PtrToStringAnsi создают кажущиеся неуместными символы). Я думал, что это может быть fixed, но ToPointer создает фиксированный указатель. И иногда после приведения к char* отладчик говорит Unable to evaluate the expression. The pointer is not valid или что-то в этом роде (нелегко воспроизвести каждую переменную, которая возвращается). А в других случаях я получаю нарушение прав доступа при чтении памяти, что приводит меня к стороне C++.

Что касается C++, я подумал, что могут быть некоторые проблемы с фактическим чтением памяти, потому что, хотя память, в которой хранится указатель, является частью файла с отображением памяти, фактические байты, составляющие текст, не являются. Итак, я посмотрел, как изменить доступ для чтения/записи к памяти (заметьте, в Windows) и нашел метод VirtualProtect в библиотеках Windows, который я использую для изменения доступа к памяти на PAGE_EXECUTE_WRITECOPY, который, как я полагал, даст любому приложению, которое имеет указатель на этот адрес, сможет хотя бы прочитать, что там. Но и это не решило проблему.

Короче говоря:

У меня есть указатель (в C#) на первый символ в массиве символов (который был выделен в приложении C++), и я пытаюсь прочитать этот массив символов в строку в C#.

РЕДАКТИРОВАТЬ:

Заголовок исходного кода выглядит так:

struct AllocatorHeader
{
// These bytes are reserved, and their purposes may change.
char _reserved[4];

// A pointer to a destructor mapping that is associated with this object.
DestructorMappingBase* _destructor;

// The size of the object this header is for.
unsigned int _size;

char* _name;
};

Поле _name — это то, что я пытаюсь разыменовать в С#.

РЕДАКТИРОВАТЬ:

На данный момент, даже используя приведенные ниже решения, я не могу разыменовать этот char* в управляемом коде. Таким образом, я просто сделал копию char* в пуле, на который ссылается файл с отображением памяти, и использовал указатель на него. Это работает, что заставляет меня поверить, что это проблема, связанная с защитой. Если я найду способ обойти это в какой-то момент, я отвечу на свой вопрос. До тех пор это будет моим обходным путем. Спасибо всем, кто помог!


person Will Custode    schedule 21.10.2013    source источник
comment
Возможно, вы могли бы использовать GetByteCount(), выделить массив байтов, скопировать байты с помощью GetBytes(), затем используйте GetString() для только что заполненного массива байтов. Возможно, немного запутанно, но должно работать.   -  person Richard J. Ross III    schedule 21.10.2013
comment
О да, чтобы использовать GetBytes(), вам придется использовать блок fixed в массиве, чтобы превратить его в указатель.   -  person Richard J. Ross III    schedule 21.10.2013
comment
Я попробую это и дам вам знать, если это сработает. Один момент...   -  person Will Custode    schedule 21.10.2013
comment
Является ли массив символов С++ одним байтом на символ? или утф-16? если последнее, это должно быть просто new string(addr, 0, len)   -  person Marc Gravell    schedule 21.10.2013
comment
Это первое, где каждый символ представляет собой ASCII (1 байт). Я пробую идею @RichardJ.RossIII, но сталкиваюсь с некоторыми проблемами.   -  person Will Custode    schedule 21.10.2013
comment
@WilliamCustode позвольте мне добавить его в качестве ответа, чтобы вы могли проверить все сразу.   -  person Richard J. Ross III    schedule 21.10.2013
comment
Если структура не упакована, у вас могут быть проблемы с выравниванием, то есть вы ищете не в том месте.   -  person Richard J. Ross III    schedule 21.10.2013
comment
Вы не можете использовать указатели внутри отображаемых в память файлов, потому что каждый процесс, отображающий файл, будет использовать совершенно другой виртуальный адрес. Вы должны заменить указатели смещениями в байтах от начала файла. Если вы не можете изменить код С++ для этого, вам нужно каким-то образом преобразовать внешний указатель в эквивалентный адрес в вашем адресном пространстве.   -  person Brian Sandlin    schedule 21.10.2013


Ответы (2)


Кажется, это работает для меня в моем простом тесте:

private static unsafe String MarshalUnsafeCStringToString(IntPtr ptr, Encoding encoding) {
    void *rawPointer = ptr.ToPointer();
    if (rawPointer == null) return "";

    char* unsafeCString = (char*)rawPointer;

    int lengthOfCString = 0;
    while (unsafeCString[lengthOfCString] != '\0') {
        lengthOfCString++;
    }

    // now that we have the length of the string, let's get its size in bytes
    int lengthInBytes = encoding.GetByteCount (unsafeCString, lengthOfCString);
    byte[] asByteArray = new byte[lengthInBytes];

    fixed (byte *ptrByteArray = asByteArray) {
        encoding.GetBytes(unsafeCString, lengthOfCString, ptrByteArray, lengthInBytes);
    }

    // now get the string
    return encoding.GetString(asByteArray);
}

Возможно, это немного запутанно, но если ваша строка заканчивается NUL, она должна работать.

person Richard J. Ross III    schedule 21.10.2013
comment
Логически это выглядит так, как будто это сработает (спасибо), но теперь я постоянно получаю Cannot dereference 'unsafeCString'. The pointer is not valid. при индексации unsafeCString, и исключение составляет Attempted to read or write protected memory. This is often an indication that other memory is corrupt. - person Will Custode; 21.10.2013
comment
@WilliamCustode очень странно. Как именно выглядит ваша исходная структура? - person Richard J. Ross III; 21.10.2013
comment
@WilliamCustode О! Это только что пришло ко мне. В C# char является 16-битным. В С++ это обычно 8 бит. Вам нужно использовать указатель байта, а не указатель char. - person Richard J. Ross III; 21.10.2013
comment
Отредактировал и добавил структуру. Я также опубликую некоторые из C++. - person Will Custode; 21.10.2013

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

Если бы вы знали базовый адрес в обоих процессах, вы могли бы настроить указатели. Но, честно говоря, весь ваш подход глубоко ошибочен. Вы действительно должны использовать некоторые реальные IPC здесь. Было бы достаточно именованных каналов, сокетов, WCF, даже старых добрых сообщений Windows.


В комментарии вы говорите:

Если массив символов выделен в куче, я могу нормально разыменовать этот указатель. Но когда char* инициализируется чем-то вроде char* a = "Hello world"; это не работает.

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

person David Heffernan    schedule 21.10.2013
comment
Этот метод не имеет недостатков; указатель, который читается на управляемой стороне, является фактическим указателем на строку (char*) на исходной стороне. Проблема в том, что сам char* находится в статическом пространстве, а не в куче, и имеет более высокий уровень защиты, что предотвращает такие вещи, как выполнение данных и динамическое изменение инструкций вне процесса. Именно это препятствие я пытался преодолеть, и обходного пути выделения char* в куче было достаточно. - person Will Custode; 22.10.2013
comment
Вы только что подтвердили то, что я сказал. Управляемый код не может разыменовывать собственный адрес по указанным выше причинам. - person David Heffernan; 22.10.2013
comment
Я так не думаю; Вы говорите, что указатель указывает на что-то недопустимое, потому что он вырван из контекста, хотя на самом деле он указывает на что-то действительное, несмотря на контекст. Просто то, на что он указывает, окружено защитой, которая запрещает то, что я пытаюсь сделать. Это не базовый указатель относительно адреса, с которого выполняется приложение, это все же значимый адрес. - person Will Custode; 22.10.2013
comment
Как родное приложение узнает, где mmap сопоставляется с управляемым процессом? А как ты кучу в ммап засунул? - person David Heffernan; 22.10.2013
comment
Я создаю файл с отображением памяти, который имеет буфер заданного размера. Этот буфер управляется диспетчером памяти в собственном коде. Заголовки размещены в этой куче и содержат char*. Это указывает на статическое пространство. Управляемое приложение считывает байты ммфайла, реконструирует указатель и разыменовывает его, чтобы получить массив символов. Если массив символов выделен в куче, я могу нормально разыменовать этот указатель. Но когда char* инициализируется чем-то вроде char* a = "Hello world";, это не работает. - person Will Custode; 22.10.2013
comment
Это не сработает. Теперь у вас есть указатель на литерал, которого нет в вашей общей куче. Я обновил ответ. - person David Heffernan; 23.10.2013
comment
Есть ли у вас какой-то еще не упомянутый способ учета того факта, что блок разделяемой памяти может иметь разные базовые адреса в разных процессах? - person David Heffernan; 23.10.2013
comment
давайте продолжим это обсуждение в чате - person Will Custode; 23.10.2013