使用LayoutKind.Explicit的布尔编组,这是否已按设计破坏或失败?

csh*_*net 5 c# pinvoke boolean marshalling layoutkind.explicit

首先,布尔类型被称为具有四字节值的默认编组类型.以下代码有效:

    struct A 
    { 
        public bool bValue1; 
        public int iValue2; 
    }
    struct B 
    { 
        public int iValue1;
        public bool bValue2; 
    }
    public static void Main()
    {
        int[] rawvalues = new int[] { 2, 4 };

        A a = (A)Marshal.PtrToStructure(GCHandle.Alloc(rawvalues, GCHandleType.Pinned).AddrOfPinnedObject(), typeof(A));
        Assert.IsTrue(a.bValue1 == true);
        Assert.IsTrue(a.iValue2 == 4);
        B b = (B)Marshal.PtrToStructure(GCHandle.Alloc(rawvalues, GCHandleType.Pinned).AddrOfPinnedObject(), typeof(B));
        Assert.IsTrue(b.iValue1 == 2);
        Assert.IsTrue(b.bValue2 == true);
    }
Run Code Online (Sandbox Code Playgroud)

显然,这些结构独立编组就好了.值按预期转换.但是,当我们将这些结构组合成一个"联合"时,通过声明LayoutKind.Explicit,如下所示:

    [StructLayout(LayoutKind.Explicit)]
    struct Broken
    {
        [FieldOffset(0)]
        public A a;
        [FieldOffset(0)]
        public B b;
    }
Run Code Online (Sandbox Code Playgroud)

我们突然发现自己无法正确编组这些类型.以下是上述结构的测试代码及其失败方式:

        int[] rawvalues = new int[] { 2, 4 };
        Broken broken = (Broken)Marshal.PtrToStructure(GCHandle.Alloc(rawvalues, GCHandleType.Pinned).AddrOfPinnedObject(), typeof(Broken));

        Assert.IsTrue(broken.a.bValue1 != false);// pass, not false
        Assert.IsTrue(broken.a.bValue1 == true);// pass, must be true?
        Assert.IsTrue(true.Equals(broken.a.bValue1));// FAILS, WOW, WTF?
        Assert.IsTrue(broken.a.iValue2 == 4);// FAILS, a.iValue1 == 1, What happened to 4?
        Assert.IsTrue(broken.b.iValue1 == 2);// pass
        Assert.IsTrue(broken.b.bValue2 == true);// pass
Run Code Online (Sandbox Code Playgroud)

看到这表达为真,这是非常幽默的:( a.bValue1!= false && a.bValue1 == true &&!true.Equals(a.bValue1))

当然,这里更大的问题是a.iValue2!= 4,而4更改为1(可能是由重叠的bool).

所以问题是:这是一个错误,还是按照设计失败了?

背景:这来自于 使用PInvoke时包含bool与uint的结构之间的区别是什么?

更新:当您使用大整数值(> 255)时,这甚至更奇怪,因为只有用于布尔值的字节被修改为1,因此将b.bValue2的0x0f00更改为0x0f01.对于上面的a.bValue1,它根本没有翻译,0x0f00为a.bValue1提供了一个假值.

更新#2:

对上述问题最明显和最合理的解决方案是使用uint进行编组并显示布尔属性.通过"解决方法"真正解决问题并不是问题.我大多想知道这是一个错误还是你想要的行为?

    struct A 
    { 
        private uint _bValue1;
        public bool bValue1 { get { return _bValue1 != 0; } } 
        public int iValue2; 
    }
    struct B 
    { 
        public int iValue1;
        private uint _bValue2;
        public bool bValue2 { get { return _bValue2 != 0; } } 
    }
Run Code Online (Sandbox Code Playgroud)

ear*_*ess 4

它正在按设计工作。

这是正在发生的事情:

获取新的 int[] { 2, 4 } 并将其编组为 A、B、Broken 和 Broken2。最后一个与 Broken 相同,但字段顺序相反(先是 b,然后是 a)。

如果我们将整数编组到这些结构中,我们会在内存中得到以下值:

  • 答:1、4
  • 乙:2、1
  • 破损: 2, 1
  • 破碎2: 1, 4

所以发生的事情如下:

  • 当编组器遇到布尔值时,它的值为:bool = (original != 0);
  • 当有两个字段映射到同一个内存时,最后一个字段的规则获胜

因此,对于 A,第一个 int 被转换为 1,对于 B,第二个 int 被转换为 1,对于 Broken,由于 B 是最后一个字段,所以它的规则适用,因此第二个 int 被转换为 1。对于 Broken2 也是如此。