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

发布于 2024-08-10 09:24:31 字数 2663 浏览 4 评论 0原文

首先,布尔类型据说有一个四字节值的默认编组类型。因此,以下代码有效:

    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

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

当然,这里更大的问题是 a.iValue2 != 4,而不是 4 已更改为 1(大概是通过重叠的 bool)。

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

背景:这来自< a href="https://stackoverflow.com/questions/1602899">使用 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; } } 
    }

First of all the Boolean type is said to have a default marshal type of a four-byte value. So the following code works:

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

Clearly these structures marshal independently just fine. The values are translated as expected. However, when we combine these structures into a "union" by declaring LayoutKind.Explicit like this:

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

We suddenly find ourselves unable to correctly marshal these types. Here is the test code for the above structure and how it fails:

        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

It's very humorous to see this express as true: (a.bValue1 != false && a.bValue1 == true && !true.Equals(a.bValue1))

Of course the bigger problem here is that a.iValue2 != 4, rather the 4 has been changed to 1 (presumably by the overlapped bool).

So the question: Is this a bug, or just failed as designed?

Background: this came from What is the difference between structures containing bool vs uint when using PInvoke?

Update: This is even stranger when you use large integer values (> 255) as only the byte that is used for the boolean is being modified to a 1, thus changing 0x0f00 to 0x0f01 for the b.bValue2. For a.bValue1 above it's not translated at all and 0x0f00 provides a false value for a.bValue1.

Update #2:

The most obvious and reasonable solution to the above issue(s) is to use a uint for the marshalling and expose boolean properties instead. Really solving the issue with a 'workaround' is not at question. I'm mostly wondering is this a bug or is this the behavior you would expect?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浪推晚风 2024-08-17 09:24:32

它正在按设计工作。

正在发生的事情是这样的:

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

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

  • A: 1, 4
  • B: 2, 1
  • Broken: 2, 1
  • Broken2: 1, 4

因此,发生的情况如下:

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

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

It is working as designed.

Here is what is happening:

Take the new int[] { 2, 4 } and lets marshal it into A, B, Broken, and Broken2.
The last is the same as Broken, but with fields' order reversed (first b, then a).

If we marshal the ints into these structures we get the following values in memory:

  • A: 1, 4
  • B: 2, 1
  • Broken: 2, 1
  • Broken2: 1, 4

So what is happening is the following:

  • When the marshaller encounters a boolean, the value of it is: bool = (original != 0);
  • When there are two fields that map into the same memory, the rules of the last field win

So for A, the first int gets converted to 1, for B, the second int gets converted to 1,
for Broken, since B is the last field, its rules apply, and hence the second int gets converted to 1. Similarly for Broken2.

穿越时光隧道 2024-08-17 09:24:32

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

The line commented with 'FAILS, WOW, WTF?' fails because of the way boolean comparison is performed. It is comparing 2 to 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 ends up comparing 1 to the byte in bValue, which is 2.

The funny thing is that if (broken.a.bValue1) will test 'true' because it's non-zero.

As far as the other problem (broken.a.iValue2 == 4), it went away when I applied:

[MarshalAs (UnmanagedType.Bool)]

to both boolean fields in the structures. This makes sure the booleans are marshaled as an integer (4 bytes in .NET).

孤独难免 2024-08-17 09:24:32

看起来 EarlNameless 是正确的,因为

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

在 union 末尾添加另一个 ints: 结构似乎至少纠正了部分问题。然而,这仍然是有缺陷的,因为布尔值只会考虑单字节值,并且如所演示的那样是不可靠的。最后,我想出的最佳答案是使用自定义类型进行封送。

[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
}

It would appear earlNameless is correct, as adding another structure of ints:

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

to the end of the union seems to correct at least part of the problem. However, this is still flawed since the boolean will only consider a single-byte value and as demonstrated is not dependable. Finally the best answer I've come up with is to use a custom type for the marshaling.

[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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文