C# x64上存在TypeLoadException,但在具有structlayouts的x86上没有问题

C# x64上存在TypeLoadException,但在具有structlayouts的x86上没有问题,c#,.net,x86,64-bit,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 OutterTyp

如果要查看实际异常,需要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从类更改为结构,它也可以工作。有人能解释一下发生了什么或我做错了什么吗


谢谢

如果您想将结构放在其他结构中,而这些结构本身是布局的,那么Uint16可能会出现问题,因为它不符合CLS(请参见此处:)

如果您希望它们在不同的位模式下工作,请使用显式大小值(以字节为单位)(或在具有不同包装要求的机器上) 你所说的是“按顺序排列,不要在内部打包,而是在最后使用你喜欢的空间”。 如果不指定大小,则运行时可以随意添加任意大小的空间

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

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

  • 确保所有引用字段都是结构中的最后一个条目-然后可以根据运行时环境的位自由地使结构变大/变小
  • 强制所有对象引用使用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规范中有相关详细信息,请参见第16.6.2节,该节要求对齐本机大小值,包括&。注意,如果需要,存在未对齐前缀指令来解决此问题

    但是,在mono上(OSX intel和Win32 intel 32位),上述代码都能正常工作。要么运行时不尊重布局并以静默方式“更正”内容,要么允许任意对齐(在这方面,历史上它们不如MS运行时灵活,这令人惊讶)。 mono生成的CLI中间形式不包含任何.unaligned指令前缀,因此它似乎不符合规范


    这将教会我只检查mono。

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


    编辑:对于怀疑者-我在internet上找不到现成的参考资料,但请查看第175页。

    我还注意到,您正在将您的字符数据类型打包为1字节。在.NET中,字符类型的大小为2字节。我无法验证这是否是实际问题,但我会再次检查。

    我遇到了同样的问题,并且非常讨厌d我在MSDN上找不到关于这个主题的明确参考。在阅读了这里的答案后,我开始关注.NET中x86和x64的差异,并发现了以下内容:。这里他们明确指出,指针在x86上是4字节,在x64上是8字节。希望这对其他人有所帮助

    顺便说一句,这里有许多关于堆栈溢出的相关问题。我将添加其中两个,它们提到了其他有趣的事情


    如果我删除uint16并减小偏移量,问题仍然会出现。请记住,.NET中的字符是16位。blah和blah2部分重叠,blah2和blah3也是如此。如果运行RunSnippet示例,您将得到与上述相同的TypeLoadException。回答很好。清晰、简洁、完美。