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

显然,这些结构独立编组就好了。 值按预期转换。 但是,当我们将这些结构组合成一个“联合”时,通过声明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(可能是由重叠的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; } } } 

它按设计工作。

以下是发生的事情:

拿新的int [] {2,4}并让它编组成A,B,Broken和Broken2。 最后一个与Broken相同,但是字段的顺序颠倒了(第一个b,然后是a)。

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

  • 答:1,4
  • B:2,1
  • 破碎:2,1
  • 破2:1,4

所以发生的事情如下:

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

因此对于A,第一个int转换为1,对于B,第二个int转换为1,对于Broken,因为B是最后一个字段,其规则适用,因此第二个int转换为1.类似于Broken2 。

这条线评论’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。

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

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

 [MarshalAs (UnmanagedType.Bool)] 

到结构中的两个布尔字段。 这可以确保将布尔值整理为整数(在.NET中为4个字节)。

似乎earlNameless是正确的,因为添加另一个int结构:

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

到工会结束似乎至少纠正了部分问题。 但是,这仍然存在缺陷,因为布尔值只考虑单字节值,并且certificate是不可靠的。 最后,我提出的最佳答案是使用自定义类型进行编组。

 [Serializable] [ComVisible(true)] public struct BOOL : IComparable, IConvertible, IComparable, IEquatable, IComparable, IEquatable { 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 Members public int CompareTo(BOOL other) { return Value.CompareTo(other.Value); } public int CompareTo(bool other) { return Value.CompareTo(other); } #endregion #region IEquatable 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 }