Макет типа значения .NET в памяти
У меня есть следующие типы значений .NET:
[StructLayout(LayoutKind.Sequential)]
public struct Date
{
public UInt16 V;
}
[StructLayout(LayoutKind.Sequential)]
public struct StringPair
{
public String A;
public String B;
public String C;
public Date D;
public double V;
}
У меня есть код, передающий указатель на тип значения неуправляемому коду, а также смещения, обнаруженные при вызове System.Runtime.InteropServices.Marshal.OffsetOf. Неуправляемый код заполняет дату и двойные значения.
Сдвиги, которые сообщаются для структуры StringPair, являются именно тем, что я ожидал бы: 0, 8, 16, 24, 32
У меня есть следующий код в тестовой функции:
FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public);
for ( int i = 0; i < fields.Length; i++ )
{
int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32();
Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset));
}
который печатает именно эти смещения.
>> field A @ offset 0
>> field B @ offset 8
>> field C @ offset 16
>> field D @ offset 24
>> field V @ offset 32
Затем у меня есть тестовый код: foreach (пара StringPair попарно) { Дата d = pair.D; double v = pair.V; ...
С абитуриентом, связанным с ним в отладчике:
Date d = pair.D;
0000035d lea rax,[rbp+20h]
00000361 add rax,20h
00000367 mov ax,word ptr [rax]
0000036a mov word ptr [rbp+000000A8h],ax
00000371 movzx eax,word ptr [rbp+000000A8h]
00000378 mov word ptr [rbp+48h],ax
double v = pair.V;
0000037c movsd xmm0,mmword ptr [rbp+38h]
00000381 movsd mmword ptr [rbp+50h],xmm0
Загружает поле D со смещением 32 (0x20) и поле V со смещением 24 (0x38-0x20). JIT изменил порядок вокруг. Отладчик Visual Studio также показывает этот инвертированный порядок.
Почему!? Я тянул свои волосы, пытаясь понять, где моя логика идет не так. Если я поменяю порядок D и V в структуре, тогда все будет работать, но этот код должен иметь дело с архитектурой плагина, где другие разработчики определили структуру, и от них нельзя ожидать, чтобы они запоминали тайные правила макета.
Ответы
Ответ 1
Информация, которую вы получаете из класса маршала, имеет значение только в том случае, если тип фактически получает маршалин. Макет внутренней памяти управляемой структуры не может быть обнаружен с помощью каких-либо документированных средств, кроме, возможно, выглядящего на ассемблере.
Это означает, что CLR может свободно реорганизовать компоновку структуры и оптимизировать упаковку. При замене полей D и V ваша структура меньше из-за требований к выравниванию двойника. Он сохраняет 6 байтов на вашей 64-разрядной машине.
Не уверен, почему это будет проблемой для вас, этого не должно быть. Рассмотрим Marshal.StructureToPtr(), чтобы получить структуру, расположенную так, как вы хотите.
Ответ 2
Если вам нужен явный макет... использовать явный макет...
[StructLayout(LayoutKind.Explicit)]
public struct StringPair
{
[FieldOffset(0)] public String A;
[FieldOffset(8)] public String B;
[FieldOffset(16)] public String C;
[FieldOffset(24)] public Date D;
[FieldOffset(32)] public double V;
}
Ответ 3
Две вещи:
-
StructLayout(Sequential)
не гарантирует упаковку. Вы можете использовать Pack=1
, в противном случае 32 и 64-битные платформы могут отличаться.
-
и строка - это ссылка, а не указатель. Если длина строки всегда исправлена, вы можете использовать фиксированные массивы char:
public struct MyArray // This code must appear in an unsafe block
{
public fixed char pathName[128];
}