Передача строк/массивов внутри структур между C++/C#

Я передаю структуру с С# на С++.

Код С#:

[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Data
{
[MarshalAs(UnmanagedType.U4)]
public int number;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public int[] array;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
public string buffer;
}

Код С++:

struct Data
{
public:
    int number;
    int array[5];
    char buffer[512];
    //char *buffer;
};

Вышеупомянутый метод работает нормально. Но вместо этого, если я использую указатели для обработки данных в C++, я получаю сообщение об ошибке:

Необработанное исключение: System.AccessViolationException: попытка чтения или записи защищенной памяти

struct Data
{
public:
    int number;
    int *array;
    char *buffer;
};

Почему я не могу работать с указателями здесь? Является ли обработка этого случая с помощью указателей выгодной?


person Joga    schedule 21.12.2015    source источник
comment
Вы не можете изменить объявление C++ без изменения объявления C#. После чего вы очень быстро обнаружите, что int[] слетать не собирается. Структуры с указателями - довольно неприятная проблема управления памятью, никогда не очень ясно, кто отвечает за повторное освобождение памяти. Вам придется взять на себя ответственность и использовать IntPtr. И беспокойтесь о том, будет ли код C++ глубоко копировать массив и строку, если это не так, у вас возникнет следующая проблема сохранения этих указателей в силе.   -  person Hans Passant    schedule 21.12.2015


Ответы (2)


Проблема в том, как ваши данные представлены в памяти.

Предположим, у вас есть экземпляр структуры C#, который маршалирует в неуправляемый код или даже в файл.

[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct Data
{
[MarshalAs(UnmanagedType.U4)]
public int number = 5;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public int[] array = {0, 1, 2, 3, 4};

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]

public string buffer = "Happy new Year";
}

В соответствии с этим ваша схема памяти будет такой (в шестнадцатеричном представлении):

05 00 00 00 00 00 00 00
01 00 00 00 02 00 00 00
03 00 00 00 04 00 00 00
00 48 00 61 00 70 00 70 
00 79 00 20 00 6E 00 65 
00 77 00 20 00 59 00 65 
00 61 00 72

Здесь у нас есть первые четыре байта «05 00 00 00», что означает число «5» в памяти для вашей переменной «number». (Обратите внимание, что эти байты расположены в обратном порядке, поскольку архитектура Intel — LittleEndian, подробности см. в разделе Endiannes.)

Тогда у нас есть следующие пять целых чисел: «00 00 00 00» = 0, «01 00 00 00» = 1, «02 00 00 00» = 2, «03 00 00 00» = 3, «04 00 00 00» = 4 для массива с именем «массив».

И строка «буфер» выглядит так:

"00 48" = H
"00 61" = a
"00 70" = p
"00 70" = p
"00 79" = y
"00 20" = <space>
"00 6E" = n
"00 65" = e
"00 77" = w
"00 20" = <space>
"00 59" = Y
"00 65" = e
"00 61" = a
"00 72" = r

Есть некоторая хитрость в том, что .NET всегда использует Unicode для хранения своих строковых переменных. Каждый символ Unicode имеет двухбайтовое представление.

Теперь для этой структуры C++

struct Data
{
public:
    int number;
    int array[5];
    char buffer[512];
    //char *buffer;
};

sizeof(int) равен 4. Таким образом, содержимое памяти для переменной «number» = «05 00 00 00», что равно номеру пять. массив[0],массив1,массив[2],массив[3],массив[4 ] разложить по блокам памяти "00 00 00 00" = 0, "01 00 00 00" = 1, "02 00 00 00" = 2, "03 00 00 00" = 3, "04 00 00 00" = 4 Все остальное остается для переменной buffer[512]. Но в c++ sizeof(char) == 1. Тип данных char обычно используется для представления старого текста в стиле ASCII с однобайтовой кодировкой. Вместо этого вы должны использовать wchar_t, который идеально подходит для кодировок Unicode.

Теперь давайте посмотрим на

struct Data
{
public:
    int number;
    int *array;
    char *buffer;
};

Эта структура будет проецироваться на ту же структуру памяти, что и описанная выше. Если вы работаете в 32-битной среде (win32), содержимое указателя «массив» будет «00 00 00 00» (4 байта для указателя), а указатель «буфер» будет «01 00 00 00».

Если вы работаете в 64-битной среде (win64), содержимое указателя «массива» будет «00 00 00 00 01 00 00 00» (8 байтов для указателя), а указатель буфера будет «02 00 00 00 03 00». 00 00".

Это какие-то невалидные указатели, которые указывают черт знает куда. Вот почему вы получаете нарушение прав доступа, когда пытаетесь их разыменовать.

person Anton Vorobyev    schedule 21.12.2015
comment
Каждый символ Unicode имеет свое двухбайтовое представление: это невозможно; слишком много. UTF-16 кодирует кодовые точки Unicode в одной или двух 16-битных кодовых единицах. - person Tom Blodget; 22.12.2015
comment
Итак, должен ли я оставить свой код как таковой или изменить его, чтобы он работал с использованием указателей? Что безопаснее? И кроме того, есть ли преимущества перехода на указатели? - person Joga; 22.12.2015
comment
Я бы оставил как есть. - person Anton Vorobyev; 21.01.2016

Первая структура работает, потому что она выделяет массив в структуре. Второй проблематичен, потому что он выделяет только указатель int и указатель char (который sizeof(void*) зависит от вашей платформы) в структуре, а не массив int. Если вы настаиваете на использовании указателей, вы должны самостоятельно выделять и освобождать память (т.е. new и delete[]).

person Mr. Anderson    schedule 21.12.2015
comment
Итак, должен ли я оставить свой код как таковой или изменить его, чтобы он работал с использованием указателей? Что безопаснее? И кроме того, есть ли преимущества перехода на указатели? - person Joga; 22.12.2015