Ответ 1
Это случай отсутствия диагностики. Кто-то должен был высказаться и сказать, что ваша декларация не поддерживается. Если это кто-то является либо компилятором С#, производящим ошибку компиляции, либо маршаллером поля CLR, создающим исключение во время выполнения.
Не похоже, что вы не можете получить диагностику. Вы обязательно получите его, когда начнете использовать структуру, как предполагалось:
a_struct_test3 lStruct3 = new a_struct_test3();
lStruct3.some_data = new a_nested[4];
lStruct3.some_data[0] = new a_nested();
lStruct3.some_data[0].a_notherstring[0] = (sbyte)'a'; // Eek!
Что вызывает CS1666: "Вы не можете использовать буферы фиксированного размера, содержащиеся в незафиксированных выражениях. Попробуйте использовать фиксированный оператор". Не то, чтобы "попробовать этот" совет был полезным:
fixed (sbyte* p = &lStruct3.some_data[0].a_notherstring[0]) // Eek!
{
*p = (sbyte)'a';
}
Точная ошибка CS1666. Следующее, что вы попробуете, это поместить атрибут в фиксированный буфер:
public unsafe struct a_struct_test3 {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public fixed sbyte a_string[3];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public a_nested[] some_data;
}
//...
a_struct_test3 lStruct3 = new a_struct_test3();
lStruct3.some_data = new a_nested[4];
IntPtr lPtr3 = Marshal.AllocHGlobal(15);
Marshal.StructureToPtr(lStruct3, lPtr3, false); // Eek!
Сохраняет компилятор С#, но теперь CLR говорит вверх, и вы получаете исключение TypeLoadException во время выполнения: "Дополнительная информация: не может маршализовать поле" a_string "типа" MarshalNested.a_struct_test3 ": недопустимая комбинация управляемых/неуправляемых типов (этот тип значения должен быть сопряжен с Struct).
Итак, в двух словах вы должны были получить либо CS1666, либо TypeLoadException в своей первоначальной попытке. Этого не произошло, потому что компилятор С# не был вынужден смотреть на плохую часть, он генерирует только CS1666 в инструкции, которая обращается к массиву. И это не произошло во время выполнения, потому что полевой маршаллер в CLR не пытался маршалировать массив, потому что он равен нулю. Вы можете подать отчет об обратной связи с ошибкой на сайте connect.microsoft.com, но я был бы очень удивлен, если они не будут закрывать его "по дизайну".
В общем, неясная деталь очень важна для маршаллера поля в среде CLR, фрагмента кода, который преобразует значения структуры и объекты класса из их управляемого макета в их неуправляемый макет. Он плохо документирован, Microsoft не хочет прибивать детали конкретной реализации. В основном потому, что они слишком сильно зависят от целевой архитектуры.
Важно то, является ли значение или объект более гибким. Он горит, когда управляемый и неуправляемый макет идентичен. Это происходит только тогда, когда каждый член типа имеет одинаковый размер и выравнивание в обоих макетах. Обычно это происходит, когда поля имеют очень простой тип значения (например, byte или int) или структуру, которая сама является blittable. Не известно, когда это bool, слишком много конфликтующих неуправляемых типов bool. Поле типа массива никогда не сглаживается, управляемые массивы не выглядят похожими на массивы C, поскольку они имеют заголовок объекта и элемент длины.
Наличие желаемого значения или объекта очень желательно, он избегает создания маршаллера из-за необходимости создавать копию. Нативный код получает простой указатель на управляемую память, и все, что необходимо, - это привязать память. Очень быстро. Также очень опасно, если декларация не соответствует, то собственный код может легко окрашиваться вне строк и повреждать кучу GC или стек кадров. Очень распространенная причина для программы, которая использует pinvoke для случайной бомбы с ExecutionEngineException, чрезвычайно трудно диагностировать. Такая декларация действительно заслуживает небезопасного ключевого слова, но компилятор С# не настаивает на этом. И это не так, компиляторам не разрешено делать какие-либо предположения о макете управляемого объекта. Вы сохраняете это безопасно, используя Debug.Assert() для возвращаемого значения Marshal.SizeOf<T>
, оно должно быть точным совпадением со значением sizeof(T)
в программе C.
Как уже отмечалось, массивы являются препятствием для получения ослабляемого значения или объекта. Ключевое слово fixed
предназначено в качестве обходного пути для этого. CLR рассматривает его как непрозрачный тип значения без каких-либо членов, просто капля байтов. Нет заголовка объекта и элемента Length, как можно ближе к массиву C. И используется в коде С#, как если бы вы использовали массив в программе на C, вы должны использовать указатель для обращения к элементам массива и три раза проверять, что вы не окрашиваете вне строк. Иногда вы должны использовать фиксированный массив, когда вы объявляете объединение (перекрывающиеся поля), и вы перекрываете массив со значением. Отравляя сборщик мусора, он больше не может понять, содержит ли поле корень объекта. Не обнаружен компилятором С#, но надежно отключает исключение TypeLoadException во время выполнения.
Короче говоря, используйте fixed
только для типа blittable. Смешивание полей типа фиксированного размера с полями, которые должны быть маршалированы, не может работать. И не полезно, объект или значение копируются в любом случае, поэтому вы можете использовать тип дружественного массива.