5

首先,Boolean 类型据说有一个四字节值的默认 marshal 类型。所以下面的代码有效:

    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(可能是重叠的布尔值)。

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

背景:这来自 使用 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; } } 
    }
4

3 回答 3

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 也是如此.

于 2009-11-14T00:02:12.137 回答
1

该行评论为“失败,哇,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。

有趣的是if (broken.a.bValue1)将测试“真”,因为它不为零。

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

[MarshalAs (UnmanagedType.Bool)]

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

于 2009-11-09T21:59:52.837 回答
0

看起来earlNameless 是正确的,因为添加了另一个整数结构:

    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
}
于 2009-11-18T19:31:25.763 回答