TypeLoadException на x64, но хорошо на x86 с structlayouts - PullRequest
9 голосов
/ 19 января 2009

Вам понадобится 64-битный компьютер, если вы хотите увидеть исключение актуального. Я создал несколько фиктивных классов, которые повторяют проблему.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
    public class InnerType
    {
        char make;
        char model;
        UInt16 series;
    }

 [StructLayout(LayoutKind.Explicit)]
    public class OutterType
    {
        [FieldOffset(0)]
        char blah;

        [FieldOffset(1)]
        char blah2;

        [FieldOffset(2)]
        UInt16 blah3;

        [FieldOffset(4)]
        InnerType details;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var t = new OutterType();
            Console.ReadLine();
        }
    }

Если я запускаю это на 64 clr, я получаю исключение загрузки типа,

System.TypeLoadException was unhandled 
  Message="Could not load type 'Sample.OutterType' from assembly 'Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field."

Если я заставлю целевой процессор 32, это будет нормально.

Кроме того, если я изменяю InnerType с класса на структуру, это также работает. Может кто-нибудь объяснить, что происходит или что я делаю не так?

спасибо

Ответы [ 5 ]

20 голосов
/ 19 января 2009

Часть о перекрывающихся типах вводит в заблуждение здесь. Проблема в том, что в .Net ссылочные типы всегда должны быть выровнены по границам размера указателя. Ваше объединение работает в x86, поскольку смещение поля составляет 4 байта, что является размером указателя для 32-разрядной системы, но происходит сбой на x64, поскольку там должно быть смещение, кратное 8. То же самое произойдет, если вы установите смещение на 3 или 5 на платформе x86.

РЕДАКТИРОВАТЬ: Для сомневающихся - я не смог найти готовую ссылку в Интернете, но посмотрите Expert .NET 2.0 IL Ассемблер Серж Лидин стр. 175.

3 голосов
/ 22 января 2009

Я также заметил, что вы упаковываете свой тип данных char в 1 байт. Типы символов в .NET имеют размер 2 байта. Я не могу проверить, является ли это реальной проблемой, но я бы дважды проверил это.

0 голосов
/ 31 мая 2012

Я боролся с той же проблемой и терпеть не мог найти четкую ссылку на эту тему в MSDN. Прочитав ответ здесь, я начал концентрироваться на различиях между x86 и x64 в .NET и обнаружил следующее: Миграция 32-битного управляемого кода в 64-битную . Здесь четко указано, что указатели имеют размер 4 байта на x86 и 8 байтов на x64. Надеюсь, что это может быть полезно для других.

Кстати, здесь много вопросов, связанных с переполнением стека. Я добавлю два из них, которые упоминают другие интересные вещи.

0 голосов
/ 19 января 2009

Если вы хотите поместить структуры в другие структуры, которые сами являются Layoutind.Explict, вам следует использовать явное значение размера (в байтах), если вы ожидаете, что они будут работать в разных режимах битности (или на машинах с различными требованиями к упаковке) То, что вы говорите, это «выкладывайте вещи последовательно и не упаковывайте их внутренне, а используйте в конце столько места, сколько вам нужно». Если вы не укажете Size, среда выполнения может добавить столько места, сколько пожелает.

Причина, по которой в общем случае отказывается допускать перекрытие структур и типов объектов, заключается в том, что процедура GC должна свободно проходить по графу живых объектов. При этом он не может знать, имеет ли значение объединенное (перекрывающееся) поле как ссылку на объект или как необработанные биты (скажем, int или float). Так как должен проходить все ссылки на живые объекты для правильного поведения, в конечном итоге он будет проходить «случайные» биты, которые могут указывать где-нибудь в куче (или вне ее), как если бы они были ссылками, прежде чем вы это узнаете повторная неисправность общей защиты.

Поскольку 32/64 ссылки будут занимать 32 или 64 бита в зависимости от времени выполнения, вы должны использовать Explict, только ссылки объединения со ссылками и типы значений с типами значений, убедитесь, что ваши ссылочные типы выровнены по границам обеих целевых платформ, если они отличаются (Примечание: время выполнения зависит от ниже) и выполняют одно из следующих действий:

  1. Убедитесь, что все ссылочные поля являются последней записью в структуре - тогда можно свободно увеличивать / уменьшать структуру в зависимости от разрядности среды выполнения.
  2. Заставить все ссылки на объекты потреблять 64 бита, независимо от того, находитесь ли вы в 32- или 64-битной среде

Примечание по выравниванию: извинения Я ошибся в невыровненных ссылочных полях - компилятор удалил загрузку типа, если я не выполнил какое-либо действие со структурой.

[StructLayout(LayoutKind.Explicit)]
public struct Foo
{
    [FieldOffset(0)]
    public byte padding;
    [FieldOffset(1)]
    public string InvalidReference;
}

public static void RunSnippet()
{
    Foo foo;
    foo.padding = 0;
    foo.ValidReference = "blah";
    // Console.WriteLine(foo); // uncomment this to fail
}

Соответствующие подробности содержатся в спецификации ECMA http://www.ecma -international.org / публикации / файлы / ECMA-ST / Ecma-335.pdf см. Раздел 16.6.2, в котором предписывается выравнивание исходного размера значения, включая &. В нем отмечается, что для приведения этого в соответствие требуется инструкция без выравнивания.

На моно (как OSX Intel, так и Win32 Intel 32 bit) вышеуказанный код работает. Либо среда выполнения не учитывает макеты и молча «корректирует» вещи, либо допускает произвольное выравнивание (исторически они были менее гибкими, чем среда выполнения MS в этом отношении, что удивительно). Промежуточная форма CLI, генерируемая mono, не содержит префиксов инструкций .unaligned, так как она, как представляется, не соответствует спецификации.

Это научит меня проверять только моно.

0 голосов
/ 19 января 2009

Может быть, что-то не так с Uint16, потому что он не соответствует CLS (см. Здесь: http://msdn.microsoft.com/en-us/library/system.uint16.aspx)

...