Обновлено: Мой вопрос не о разнице между blittable и неуправляемым, а о том, что именно означает документация для StructLayoutAttribute. В этом выпуске от 2022 года до сих пор можно увидеть термин «блитируемый», а не «неуправляемый».
В официальной документации для StructLayoutAttribute говорится:
Для преобразуемых типов LayoutKind.Sequential управляет как макетом, так и в управляемой памяти и макет в неуправляемой памяти. Для непреобразуемые типы, он управляет макетом, когда класс или структура маршалируется в неуправляемый код, но не контролирует расположение в управляемой памяти.
Из этого списка Преобразуемые и непреобразуемые типы, System.Boolean является непреобразуемым.
Однако следующая структура будет иметь последовательный макет в управляемой памяти, хотя она содержит непреобразуемое (но неуправляемое) поле bool.
[StructLayout(LayoutKind.Sequential)]
struct Unmanaged {
public byte b1;
public int i1;
public bool bool1;
public byte b2;
//public string str; // -> uncomment to have auto layout
public unsafe override string ToString() {
//https://forum.unity.com/threads/question-about-size-and-padding-of-a-type-in-c.1274090/
var b1FieldOffset = (long)Unsafe.AsPointer(ref this.b1) - (long)Unsafe.AsPointer(ref this);
var i1FieldOffset = (long)Unsafe.AsPointer(ref this.i1) - (long)Unsafe.AsPointer(ref this);
var b2FieldOffset = (long)Unsafe.AsPointer(ref this.b2) - (long)Unsafe.AsPointer(ref this);
var bool1Offset = (long)Unsafe.AsPointer(ref this.bool1) - (long)Unsafe.AsPointer(ref this);
var sb = new StringBuilder();
sb.AppendLine($"Size: {Unsafe.SizeOf<Unmanaged>()}");
sb.AppendLine($"b1 Offset: {b1FieldOffset}");
sb.AppendLine($"i1 Offset: {i1FieldOffset}");
sb.AppendLine($"bool1 Offset: {bool1Offset}");
sb.AppendLine($"b2 Offset: {b2FieldOffset}");
return sb.ToString();
}
}
ToString выведет:
Size: 12
b1 Offset: 0
i1 Offset: 4
bool1 Offset: 8
b2 Offset: 9
Если мы раскомментируем строковое поле, которое является одновременно непреобразуемым и неуправляемым, мы получим следующую структуру управляемой памяти (последовательная не учитывается):
Size: 16
b1 Offset: 12
i1 Offset: 8
bool1 Offset: 13
b2 Offset: 14
После того, как я уже предоставил отзыв об устаревшем руководстве для поля Pack в той же документации (десятичное число составляет 2 4-байтовых поля и 1 8-байтовое поле в .NET5+, а не 4 4-байтовых поля), я подозреваю, что документация, касающаяся blittable/non-blittable, может быть устарело, и на самом деле это означает, что это неуправляемый/управляемый для Неуправляемые типы
Должен ли я опубликовать еще один отзыв, чтобы документация изменилась, или мои подозрения необоснованны?





Чтобы ответить на свой вопрос, мне пришлось глубоко покопаться в исходном коде среды выполнения , обвинить определенные версии, прочитать множество проблем на GitHub, комментарии и поэкспериментировать с разными версиями .NET (от 5 до 7).
На данный момент единственной реальной документацией, к сожалению, является исходный код. Стандарт ECMA 335 — Common Language Infrastructure (CLI) последняя редакция датируется 2012 годом, при этом не планируется выпуск версии 7 , которая включает « Дополнение к спецификации ECMA-335 CLI»
В дополнении нет упоминания о расположении классов/структур, поэтому последним официальным источником информации о том, как все должно работать, является 6-е издание ECMA-335 от 2012 года. Соответствующие части метаданных макета последовательного типа:
I.9.5 Расположение классов
sequentiallayout: класс с пометкой sequentiallayout направляет загрузчик сохранить порядок полей в том виде, в каком он был задан, но в противном случае конкретные смещения рассчитываются на основе типа CLI поля; это могут быть сдвигается с помощью явной информации о смещении, заполнении и/или выравнивании.
II.10.1.2 Атрибуты макета типа
последовательный: CLI должен располагать поля в последовательном порядке, на основе порядка полей в логической таблице метаданных (п. II.22.15).
и более общий II.10.7 Управление макетом экземпляра
[Примечание. Метаданные, управляющие макетом экземпляра, не являются «подсказкой», это неотъемлемая часть VES, которая должна поддерживаться всеми соответствующими реализации CLI. последнее примечание]
Никакого различия в размещении в управляемой и неуправляемой памяти не делается. На этом этапе нам придется вернуться к документации из моего вопроса:
Для преобразуемых типов LayoutKind.Sequential управляет как макетом в управляемая память и расположение в неуправляемой памяти. Для неблитируемых типы, он управляет макетом при маршалировании класса или структуры. к неуправляемому коду, но не контролирует расположение в управляемой памяти
Очевидно, это одинаково для всех версий .NET (5,6,7,8). По моему мнению, последнее противоречит тому, что диктует ECMA 335.
Наиболее подходящий исходный код для этого, который я нашел, находится в runtime/src/coreclr/vm/classlayoutinfo.cpp, а точнее, в функции
DetermineBlittabilityAndManagedSequential
В нем код проходит через поля структуры/класса и проверяет, являются ли они blitable или они лишат нашего типа возможности последовательного управления (чего мы и добиваемся). Логика .NET 5 и .NET 6 отличается от логики .NET 7 (опять же недокументирована).
В .NET 5/6
*fDisqualifyFromManagedSequential |= CheckIfDisqualifiedFromManagedSequential(corElemType, fsig, &pFieldInfoArrayOut->m_placement);
Это наиболее важные части функции:
BOOL CheckIfDisqualifiedFromManagedSequential(CorElementType corElemType, MetaSig& fsig, RawFieldPlacementInfo* pManagedPlacementInfo) {
if (CorTypeInfo::IsPrimitiveType(corElemType))
else if (corElemType == ELEMENT_TYPE_PTR)
else if (corElemType == ELEMENT_TYPE_VALUETYPE)
{
return !pNestedType.GetMethodTable()->IsManagedSequential();
}
Если поле типа представляет собой структуру, которая сама по себе не управляется последовательно, это дисквалифицирует тип для последовательного управления. Это в некоторой степени логичное, но совершенно недокументированное поведение (помните, что в документации выше это вообще не упоминается для версий .NET 5 и 6). Мы можем проверить это с помощью следующего кода
[StructLayout(LayoutKind.Sequential)]
struct Unmanaged {
public bool bool1;
public char ch;
public int i1;
public byte b2;
public AutoStruct autostruct;
public unsafe override string ToString() {
var bool1Offset = (long)Unsafe.AsPointer(ref this.bool1) - (long)Unsafe.AsPointer(ref this);
var chFieldOFfset = (long)Unsafe.AsPointer(ref this.ch) - (long)Unsafe.AsPointer(ref this);
var i1FieldOffset = (long)Unsafe.AsPointer(ref this.i1) - (long)Unsafe.AsPointer(ref this);
var b2FieldOffset = (long)Unsafe.AsPointer(ref this.b2) - (long)Unsafe.AsPointer(ref this);
var autoStructFieldOffset = (long)Unsafe.AsPointer(ref this.autostruct) - (long)Unsafe.AsPointer(ref this);
var sb = new StringBuilder();
sb.AppendLine($"Size: {Unsafe.SizeOf<Unmanaged>()}");
sb.AppendLine($"bool1 Offset: {bool1Offset}");
sb.AppendLine($"ch Offset: {chFieldOFfset}");
sb.AppendLine($"i1 Offset: {i1FieldOffset}");
sb.AppendLine($"b2 Offset: {b2FieldOffset}");
sb.AppendLine($"AutoStruct Offset: {autoStructFieldOffset}");
return sb.ToString();
}
}
[StructLayout(LayoutKind.Auto)]
struct AutoStruct {
byte b1;
int i1;
}
Вызов ToString вернет это в .NET5/6 (рекомендуется запустить в LinqPad для легкого переключения между различными средами выполнения)
Size: 16
bool1 Offset: 6
ch Offset: 4
i1 Offset: 0
b2 Offset: 7
AutoStruct Offset: 8
Обратите внимание, что у нас нет последовательного макета, поскольку AutoStruct дисквалифицирует нашу неуправляемую структуру и CLR берет на себя управление.
Однако в .NET7+ мы получаем «ожидаемое поведение».
Size: 24
bool1 Offset: 0
ch Offset: 2
i1 Offset: 4
b2 Offset: 8
AutoStruct Offset: 16
Изменение связано с тем, что проверка ManagedSequential теперь этот код
*fDisqualifyFromManagedSequential |= TypeHasGCPointers(corElemType, typeHandleMaybe);
и полный код того, что означает «blittable», он же управляемо-последовательный.
BOOL TypeHasGCPointers(CorElementType corElemType, TypeHandle pNestedType)
{
if (CorTypeInfo::IsPrimitiveType(corElemType) || corElemType == ELEMENT_TYPE_PTR || corElemType == ELEMENT_TYPE_FNPTR)
{
return FALSE;
}
if (corElemType == ELEMENT_TYPE_VALUETYPE)
{
_ASSERTE(!pNestedType.IsNull());
return pNestedType.GetMethodTable()->ContainsPointers() != FALSE;
}
return TRUE;
}
Похоже, ManagedSequntial, также известный как blittable (в документации), аналогичен неуправляемому (функция Csharp) для .NET7+.
@Charlieface, как этот дубликат? Тот, на который вы указали, просто обсуждает семантические различия между терминами и не приводит к реальному выводу. Я спрашиваю о.р.т. StructLayout, который на практике ведет себя так, как будто означает неуправляемый, но на самом деле он не связан с предложением языка. Также есть отличие: структура с примитивными полями с автоматическим макетом здесь «непреобразуемая».