与阵列的元帅联盟

时间:2014-01-30 01:44:36

标签: c# .net marshalling unions

我遇到了一个奇怪的场景,它在C#/ .NET中包含了包含数组的联合。请考虑以下程序:

namespace Marshal
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct InnerType
        {
            byte Foo;
            //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
            //byte[] Bar;
        }


        [StructLayout(LayoutKind.Explicit, Pack = 1)]
        struct UnionType
        {
            [FieldOffset(0)]
            InnerType UnionMember1;

            [FieldOffset(0)]
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
            byte[] UnionMember2;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(@"SizeOf UnionType: {0}", System.Runtime.InteropServices.Marshal.SizeOf(typeof(UnionType)));
        }
    }
}

如果您运行此程序,您将收到以下异常:

Could not load type 'UnionType' from assembly 'Marshal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

现在,如果您取消注释两个注释掉的行,程序将运行正常。我想知道为什么会这样。为什么在InnerType中添加额外的数组可以解决问题呢?顺便说一下,你制作阵列的大小并不重要。如果没有数组,UnionMember1和UnionMember2应该在大小上相互匹配。对于数组,它们的大小不匹配,但不会抛出任何异常。

更新 将InnerType更改为以下内容也会导致异常(此时在InnerType上):

[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct InnerType
{
    [FieldOffset(0)]
    byte Foo;

    [FieldOffset(1)]
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
    byte[] Bar;
}

在我看来,这应该等同于原始代码(LayoutKind.Sequential),其中byte[] Bar取消注释。

我不相信这里的问题与单词边界有关 - 我使用Pack = 1.相反,我认为它是异常的第二部分,“...它包含偏移处的对象字段0是......由非对象字段重叠。“ byte []是引用类型,而byte本身是值类型。我可以看到“byte Foo”最终会重叠“byte [] UnionMember2”。但是,这仍然无法解释为什么在原始代码中取消注释“byte [] bar”会使异常消失。

1 个答案:

答案 0 :(得分:0)

我的假设是顺序布局被推翻,如本答案所述:LayoutKind.Sequential not followed when substruct has LayoutKind.Explicit

请注意,pack设置为1并不能消除填充,即Bar没有FieldOffset为1.它对齐到4个字节(在x32上),并且根据Marshal.OffsetOf()它应该按预期为4。

但是,.NET运行时实际上可能会将引用类型Bar放在托管内存中的字节Foo之前,在这种情况下,它会在与UnionMember2的联合中正确重叠。

有趣的是,使用Foo int和float会发生相同的情况,但是使用long和double会再次出现异常。看起来它在大小上对字段进行排序,但如果大小相等则首先放置引用类型。

当我切换到x64时,它也与long Foo一起使用,这将支持这一理论。最后,我打开了一个内存窗口(Debug-> Windows-> Memory)并输入位置&instance.UnionMember1.Foo并向上滚动一点以显示Foo之前的字节。然后使用立即窗口在Foo和Bar中设置一个值,证明Bar为0且Foo为4.(将var instance = new UnionType()添加到Main

请注意,虽然这可能不是您的意图,但Byte[]仅被视为参考类型。您可以将其替换为object。您可以改为使用fixed byte Bar[1],具体取决于您的目标。