x64上的TypeLoadException但在带有structlayouts的x86上很好

时间:2009-01-19 06:20:52

标签: c# .net x86 64-bit

如果要查看实际异常,则需要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."

如果我将目标cpu强制为32,它可以正常工作。

此外,如果我将InnerType从类更改为结构,它也可以。有人可以解释发生了什么或我做错了什么?

感谢

5 个答案:

答案 0 :(得分:20)

关于重叠类型的部分在这里会产生误导。问题是.Net引用类型必须始终在指针大小边界上对齐。您的联合在x86中工作,因为字段偏移量是4个字节,这是32位系统的指针大小,但在x64上失败,因为它必须偏移8的倍数。如果将偏移量设置为3,则会发生同样的情况5在x86平台上。

编辑:对于怀疑者 - 我在互联网上找不到现成的参考资料,但请查看Expert .NET 2.0 IL Assembler By Serge Lidin第175页。

答案 1 :(得分:3)

我还注意到你将char数据类型打包成1个字节。 .NET中的Char类型大小为2个字节。我无法验证这是否是实际问题,但我会仔细检查。

答案 2 :(得分:0)

如果您希望将结构放在其自身为Layoutind.Explict的其他结构中,您应该使用显式的Size值(以字节为单位),如果您希望它们在不同的位模式下工作(或者在具有不同包装要求的机器上) 你所说的是“按顺序排列,不要内部打包,但最后要使用尽可能多的空间”。 如果您没有指定Size,则运行时可以自由添加所需的空间。

它通常拒绝让结构和对象类型重叠的原因是GC例程必须可以自由遍历实时对象图。在执行此操作时,它无法知道联合(重叠)字段作为对象引用或作为原始位(例如int或float)是否有意义。由于必须遍历所有活动对象引用才能正常运行,因此它最终将遍历“随机”位,这些位可能指向堆中的任何位置(或者在其中),就好像它们是您在知道它之前的引用一样一般保护错误。

由于32/64引用将根据运行时占用32位或64位,因此必须使用Explict,只有带引用的引用引用和具有值类型的值类型,确保引用类型与两个目标平台的边界对齐它们不同(注意:运行时依赖,见下文)并执行以下操作之一:

  1. 确保所有引用字段都是结构中的最后一个条目 - 然后可以根据运行时环境的位数自由地使结构更大/更小。
  2. 强制所有对象引用使用64位,无论您使用的是32位还是64位环境
  3. 关于对齐的注意事项: 道歉 我在未对齐的引用字段上出错 - 编译器删除了类型加载,除非我对结构执行了一些操作。

    [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/publications/files/ECMA-ST/Ecma-335.pdf中,请参阅第16.6.2节,其中规定了原始大小值的对齐,包括&amp ;.它指出,如果需要,存在未对齐的前缀指令。

    然而,在单声道(OSX intel和Win32 intel 32位)上面的代码可以工作。运行时不会尊重布局并且默默地“纠正”事物,或者它允许任意对齐(历史上它们在这方面不如MS运行时灵活,这是令人惊讶的)。 mono生成的CLI中间形式不包含任何.unaligned指令前缀,因此它似乎不符合规范。

    那教会我只检查单声道。

答案 3 :(得分:0)

我在同样的问题上挣扎,并且讨厌我在MSDN上找不到关于这个主题的明确参考。在阅读了这里的答案之后,我开始专注于.NET中的x86和x64差异,并发现以下内容:Migrating 32-bit Managed Code to 64-bit。在这里,他们清楚地指出指针在x86上是4个字节,在x64上是8个字节。希望它对其他人有所帮助。

在Stack Overflow上有很多相关的问题。我将添加其中两个,其中提到其他有趣的东西。

答案 4 :(得分:-2)

由于它不符合CLS(参见此处:http://msdn.microsoft.com/en-us/library/system.uint16.aspx

,因此Uint16可能出现问题。