与阵列的元帅联盟

wat*_*pet 6 .net c# 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)));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您运行此程序,您将获得以下异常:

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.
Run Code Online (Sandbox Code Playgroud)

现在,如果您取消注释两个注释掉的行,程序将运行正常.我想知道为什么会这样.为什么在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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

Her*_*man 0

我的假设是,顺序布局被否决,如以下答案中所述:当子结构具有 LayoutKind.Explicit 时,不遵循 LayoutKind.Sequential。

请注意,pack 设置为 1 并不会消除填充,即BarFieldOffset 不为 1。它与 4 字节对齐(在 x32 上),并且根据 Marshal.OffsetOf()预期,它应该为 4。

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

有趣的是,对于Fooint 和 float 也会发生同样的情况,但是对于 long 和 double 又会出现异常。看起来它按大小对字段进行排序,但如果大小相等,则将引用类型放在第一位。

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

请记住,这可能不是您想要的,它们Byte[]只是被视为引用类型。您可以将其替换为object. 您也许可以使用它fixed byte Bar[1],具体取决于您的目标。