代码之家  ›  专栏  ›  技术社区  ›  csharptest.net

带有LayoutKind.Explicit的布尔编组,这是否已损坏或按设计失败?

  •  5
  • csharptest.net  · 技术社区  · 15 年前

    首先,据说布尔类型具有四字节值的默认封送处理类型。因此,以下代码起作用:

        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);
        }
    

    很明显,这些结构独立地运作得很好。这些值将按预期进行转换。但是,当我们通过声明LayoutKind.Explicit将这些结构组合成一个“联合”时,如下所示:

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

    我们突然发现自己无法正确编组这些类型。以下是上述结构的测试代码及其故障原因:

            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
    

    把这句话看作是真的是很幽默的:(a.bValue1!=false&a.bValue1==true&true.Equals(a.bValue1))

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

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

    背景:这来自 What is the difference between structures containing bool vs uint when using PInvoke?

    更新:当您使用大整数值(>255)时,这就更奇怪了,因为只有用于布尔值的字节被修改为1,从而将b.bValue2的0x0f00更改为0x0f01。对于上面的a.bValue1,它根本不会被转换,0x0f00为a.bValue1提供了一个假值。

    上述问题最明显、最合理的解决方案是使用uint进行编组,并公开布尔属性。用“变通办法”真正解决问题是毫无疑问的。我最想知道的是,这是一个bug还是你期望的行为?

        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; } } 
        }
    
    3 回复  |  直到 7 年前
        1
  •  4
  •   earlNameless    15 年前

    以下是正在发生的事情:

    取新的int[]{2,4},让我们将其编组为A、B、breaked和brokern2。

    如果我们将int封送到这些结构中,我们将在内存中获得以下值:

    • A:1,4
    • 坏掉的:2,1
    • 经纪人2:1,4

    因此,发生的情况如下:

    • 当封送拆收器遇到布尔值时,其值为:bool=(original!=0);

    对于A,第一个int被转换成1,对于B,第二个int被转换成1, 对于breake,由于B是最后一个字段,因此其规则适用,因此第二个int被转换为1。Brokern2的情况也类似。

        2
  •  1
  •   Gonzalo    15 年前

    注释为“FAILS,WOW,WTF?”的行由于执行布尔比较的方式而失败。它将2与1进行比较:

    IL_007e:  ldc.i4.1
    IL_007f:  ldloca.s 3
    IL_0081:  ldflda valuetype Test/A Test/Broken::a
    IL_0086:  ldfld bool Test/A::bValue1
    IL_008b:  ceq
    

    ceq 最后将1与bValue中的字节(即2)进行比较。

    有趣的是 将测试“true”,因为它不是零。

    至于另一个问题(breaked.a.iValue2==4),当我申请时,它消失了:

    [MarshalAs (UnmanagedType.Bool)]
    

    到结构中的两个布尔字段。这确保布尔值被封送为整数(在.NET中为4字节)。

        3
  •  0
  •   csharptest.net    15 年前

    这似乎是正确的,因为添加了另一个ints结构:

        struct C
        {
            public int iValue1;
            public int iValue2;
        }
    

    [Serializable]
    [ComVisible(true)]
    public struct BOOL : IComparable, IConvertible, IComparable<BOOL>, IEquatable<BOOL>, IComparable<bool>, IEquatable<bool>
    {
        private uint _data;
    
        public BOOL(bool value) { _data = value ? 1u : 0u; }
        public BOOL(int value) { _data = unchecked((uint)value); }
        public BOOL(uint value) { _data = value; }
    
        private bool Value { get { return _data != 0; } }
        private IConvertible Convertible { get { return _data != 0; } }
    
        #region IComparable Members
        public int CompareTo(object obj) { return Value.CompareTo(obj); }
        #endregion
        #region IConvertible Members
        public TypeCode GetTypeCode() { return Value.GetTypeCode(); }
        public string ToString(IFormatProvider provider) { return Value.ToString(provider); }
        bool IConvertible.ToBoolean(IFormatProvider provider) { return Convertible.ToBoolean(provider); }
        byte IConvertible.ToByte(IFormatProvider provider) { return Convertible.ToByte(provider); }
        char IConvertible.ToChar(IFormatProvider provider) { return Convertible.ToChar(provider); }
        DateTime IConvertible.ToDateTime(IFormatProvider provider) { return Convertible.ToDateTime(provider); }
        decimal IConvertible.ToDecimal(IFormatProvider provider) { return Convertible.ToDecimal(provider); }
        double IConvertible.ToDouble(IFormatProvider provider) { return Convertible.ToDouble(provider); }
        short IConvertible.ToInt16(IFormatProvider provider) { return Convertible.ToInt16(provider); }
        int IConvertible.ToInt32(IFormatProvider provider) { return Convertible.ToInt32(provider); }
        long IConvertible.ToInt64(IFormatProvider provider) { return Convertible.ToInt64(provider); }
        sbyte IConvertible.ToSByte(IFormatProvider provider) { return Convertible.ToSByte(provider); }
        float IConvertible.ToSingle(IFormatProvider provider) { return Convertible.ToSingle(provider); }
        ushort IConvertible.ToUInt16(IFormatProvider provider) { return Convertible.ToUInt16(provider); }
        uint IConvertible.ToUInt32(IFormatProvider provider) { return Convertible.ToUInt32(provider); }
        ulong IConvertible.ToUInt64(IFormatProvider provider) { return Convertible.ToUInt64(provider); }
        object IConvertible.ToType(Type conversionType, IFormatProvider provider) { return Convertible.ToType(conversionType, provider); }
        #endregion
        #region IComparable<bool> Members
        public int CompareTo(BOOL other) { return Value.CompareTo(other.Value); }
        public int CompareTo(bool other) { return Value.CompareTo(other); }
        #endregion
        #region IEquatable<bool> Members
        public bool Equals(BOOL other) { return Value.Equals(other.Value); }
        public bool Equals(bool other) { return Value.Equals(other); }
        #endregion
        #region Object Override
        public override string ToString() { return Value.ToString(); }
        public override int GetHashCode() { return Value.GetHashCode(); }
        public override bool Equals(object obj) { return Value.Equals(obj); }
        #endregion
        #region implicit/explicit cast operators
        public static implicit operator bool(BOOL value) { return value.Value; }
        public static implicit operator BOOL(bool value) { return new BOOL(value); }
        public static explicit operator int(BOOL value) { return unchecked((int)value._data); }
        public static explicit operator BOOL(int value) { return new BOOL(value); }
        public static explicit operator uint(BOOL value) { return value._data; }
        public static explicit operator BOOL(uint value) { return new BOOL(value); }
        #endregion
        #region +, -, !, ~, ++, --, true, false unary operators overloaded.
        public static BOOL operator !(BOOL b) { return new BOOL(!b.Value); }
        public static bool operator true(BOOL b) { return b.Value; }
        public static bool operator false(BOOL b) { return !b.Value; }
        #endregion
        #region +, -, *, /, %, &, |, ^, <<, >> binary operators overloaded.
        public static BOOL operator &(BOOL b1, BOOL b2) { return new BOOL(b1.Value & b2.Value); }
        public static BOOL operator |(BOOL b1, BOOL b2) { return new BOOL(b1.Value | b2.Value); }
        #endregion
        #region ==, !=, <, >, <=, >= comparison operators overloaded
        public static bool operator ==(BOOL b1, BOOL b2) { return (b1.Value == b2.Value); }
        public static bool operator !=(BOOL b1, BOOL b2) { return (b1.Value != b2.Value); }
        #endregion
    }