LayoutKind.Explicit를 사용한 부울 마샬링, 이것이 설계대로 손상되었거나 실패했습니까?

StackOverflow https://stackoverflow.com/questions/1703759

문제

우선 부울 유형은 4바이트 값의 기본 마샬 유형을 갖는다고 합니다.따라서 다음 코드가 작동합니다.

    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를 포함하는 구조의 차이점은 무엇입니까?

업데이트:부울에 사용되는 바이트만 1로 수정되어 b.bValue2에 대해 0x0f00을 0x0f01로 변경하므로 큰 정수 값(> 255)을 사용하는 경우 이는 더욱 이상합니다.위의 a.bValue1의 경우 전혀 변환되지 않으며 0x0f00은 a.bValue1에 대해 잘못된 값을 제공합니다.

업데이트 #2:

위 문제에 대한 가장 명확하고 합리적인 해결책은 마샬링에 단위를 사용하고 대신 부울 속성을 노출하는 것입니다.실제로 '해결 방법'으로 문제를 해결하는 것은 의문의 여지가 없습니다.나는 이것이 버그인지, 아니면 이것이 당신이 기대하는 동작인지 궁금합니다.

    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로 마샬링하십시오. 마지막은 깨진 것과 동일하지만 필드의 주문이 반전된다 (첫 번째 B, A).

우리가 이러한 구조에 ints를 마샬링하면 다음 값을 메모리에서 얻습니다.

  • A : 1, 4
  • B : 2, 1
  • 깨진 : 2, 1
  • Broken2 : 1, 4

그래서 일어나는 일은 다음과 같습니다.

  • 마샬러가 부울을 만나면 그 값은 다음과 같습니다. bool = (원본! = 0);
  • 동일한 메모리에 매핑되는 두 개의 필드가 있으면 마지막 필드의 규칙이 승리합니다.

따라서 A의 경우, 첫 번째 int가 B에 대해 1로 변환됩니다. B의 경우, 두 번째 int는 1로 변환됩니다. B는 마지막 필드이므로 규칙이 적용되므로 두 번째 int는 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 BValue의 바이트와 1을 비교하는데, 이는 2입니다.

재미있는 점은 그게 그게됩니다 if (broken.a.bvalue1) 0이 아니기 때문에 'True'를 테스트합니다.

다른 문제 (Broken.a.ivalue2 == 4)까지, 내가 적용했을 때 사라졌습니다.

[MarshalAs (UnmanagedType.Bool)]

구조물의 부울 분야에. 이것은 부울을 정수 (.NET의 4 바이트)로 마샬링합니다.

다른 정수 구조를 추가하면 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
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top