2009-12-16 2 views
11

У меня есть следующие типы значений .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; 
} 

У меня есть код, который передается указатель на тип значения для неуправляемого кода, наряду со смещениями, обнаруженных с помощью вызова системы .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 

Я тогда иметь некоторый тестовый код: Еогеаспа (StringPair пару в паре) { Date 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 в структуре, тогда все будет работать, но этот код должен иметь дело с архитектурой плагина, где другие разработчики определили структуру, и от них нельзя ожидать, чтобы они запоминали тайные правила макета.

ответ

11

Информация, которую вы получите от класса Marshal имеет значение только если тип фактически получает выстраивал. Структура внутренней памяти управляемой структуры не может быть обнаружена с помощью каких-либо документированных средств, кроме как, возможно, выглядящих на ассемблере.

Это означает, что CLR может свободно реорганизовать компоновку структуры и оптимизировать упаковку. При замене полей D и V ваша структура меньше из-за требований к выравниванию двойника. Он экономит 6 байтов на вашей 64-битной машине.

Не знаете, почему это было бы проблемой для вас, этого не должно быть. Рассмотрим Marshal.StructureToPtr(), чтобы получить структуру, которую вы планируете использовать.

+1

Спасибо - я упустил тот факт, что методы Маршалла. * Применимы только к маршалированному указателю. По соображениям производительности я надеялся избежать дополнительной копии данных, но это выглядит довольно неустранимым, если я хочу поддерживать произвольные структуры –

+1

+1, чтобы уточнить, что структура структуры JIT является полностью отдельной проблемой, чем то, что указывает CLR, в принципе - он не может быть замечен - и, следовательно, не имеет значения для управляемых программ. Я заметил, что JITER, кажется, помещает управляемые поля перед неуправляемым. –

+0

Точная компоновка битов в памяти (экземпляра) типа значения может иметь некоторую релевантность для принятия решения о том, следует ли реализовать IEquatable (и рекомендуемые переопределения объекта.Equals и GetHashCode). Если вы не реализуете IEquatable, то я считаю, что генерируемый по умолчанию код выполняет побитовое сравнение по битам в памяти. Если в макете есть GAPS, вы можете тратить время на сравнение битов, которые не имеют значения (в C++ вы также можете рисковать корректностью, так как эти биты могут не инициализироваться, но я думаю, что они всегда находятся на C#). Знание макета сообщает решение о том, что реализовать. –

13

Если вам нужно явное расположение ... использование явного расположения ...

[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; 
} 
+2

Я мог бы поклясться, я пытался, что в какой-то момент в течение последних нескольких дней .. но это не похоже на работу Теперь. Тем не менее, дело в том, что последовательный * должен * работать, а структура сообщает о смещениях, которые не соответствуют тем, которые они используют. Я действительно не хочу, чтобы пользователи этой архитектуры плагина должны были указать атрибут и сами сделать математику, чтобы заставить это работать. –

1

Две вещи:

  • StructLayout(Sequential) не гарантирует упаковки. Возможно, вы захотите использовать Pack=1, в противном случае 32 и 64-битные платформы могут отличаться.

  • и строка - это ссылка, а не указатель.Если длина строки всегда фиксирована, вы можете использовать фиксированные массивы полукокса:

    public struct MyArray // This code must appear in an unsafe block 
    { 
        public fixed char pathName[128]; 
    }