9

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

谢谢

4

5 回答 5

20

The part about overlapping types is misleading here. The problem is that in .Net reference types must always be aligned on pointer size boundaries. Your union works in x86 since the field offset is 4 bytes which is the pointer size for a 32 bit system but fails on x64 since there it must be offset a multiple of 8. The same thing would happen if you set the offset to 3 or 5 on the x86 platform.

EDIT: For the doubters - I couldn't find a ready reference on the internet but check out Expert .NET 2.0 IL Assembler By Serge Lidin page 175.

于 2009-01-19T17:54:10.653 回答
3

I have also noticed that you are packing your char data type into 1 byte. Char types in .NET are 2 bytes in size. I cannot verify if this is the actual issue, but I would double-check that.

于 2009-01-22T05:15:22.493 回答
0

如果您想将结构放置在本身就是 Layoutind.Explict 的其他结构中,如果您希望它们以不同的位数模式(或在具有不同打包要求的机器上)工作,则应该使用显式 Size 值(以字节为单位)您在说什么有“按顺序排列东西,不要在内部打包,但在最后使用尽可能多的空间”。如果您未指定 Size,则运行时可以随意添加任意数量的空间。

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

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

  1. 确保所有引用字段都是结构中的最后一个条目 - 然后可以根据运行时环境的位数自由地使结构更大/更小。
  2. 无论您是在 32 位还是 64 位环境中,强制所有对象引用使用 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/publications/files/ECMA-ST/Ecma-335.pdf见第 16.6.2 节,它要求对齐原生大小值,包括 &。它指出,如果需要,存在未对齐的前缀指令可以解决此问题。

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

那会教我只检查单声道。

于 2009-01-19T17:33:14.750 回答
0

I struggled with the same problem and hated I couldn't find a clear reference on this topic on MSDN. After reading the answer here, I started concentrating me on the x86 and x64 differences in .NET and found the following: Migrating 32-bit Managed Code to 64-bit. Here they clearly state that pointers are 4 bytes on x86 and 8 bytes on x64. Hope it can be helpful for others.

There are by the way many related questions here on Stack Overflow. I will add two of them, that mention other interesting things.

于 2012-05-31T14:52:59.443 回答
-2

Uint16 可能有问题,因为它不符合 CLS(请参见此处:http: //msdn.microsoft.com/en-us/library/system.uint16.aspx

于 2009-01-19T08:08:18.120 回答