Boolean Marshalling с LayoutKind.Explicit, это сломано или терпит неудачу, как задумано? - PullRequest
5 голосов
/ 09 ноября 2009

Прежде всего, говорят, что булев тип имеет маршальный тип по умолчанию с четырехбайтовым значением. Так работает следующий код:

    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 и uint, при использовании PInvoke?

Обновление: это даже странно, когда вы используете большие целочисленные значения (> 255), поскольку только байт, который используется для логического значения, изменяется на 1, таким образом изменяя 0x0f00 на 0x0f01 для b.bValue2. Для 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; } } 
    }

Ответы [ 3 ]

4 голосов
/ 14 ноября 2009

Работает как задумано.

Вот что происходит:

Возьмите новый int [] {2, 4} и позвольте маршалировать его в A, B, Broken и Broken2. Последний такой же, как Broken, но с обратным порядком полей (сначала b, затем a).

Если мы распределяем целые числа в эти структуры, мы получаем следующие значения в памяти:

  • A: 1,4
  • B: 2, 1
  • Сломано: 2, 1
  • Broken2: 1, 4

Итак, происходит следующее:

  • Когда маршаллер встречает логическое значение, его значение равно: bool = (original! = 0);
  • Когда два поля отображаются в одну и ту же память, правила последнего поля выигрывают

Таким образом, для A первый int конвертируется в 1, для B, второй int конвертируется в 1, для Broken, так как B является последним полем, применяются его правила, и, следовательно, второе int преобразуется в 1. Аналогично для Broken2.

1 голос
/ 10 ноября 2009

Строка прокомментирована как «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)]

для обоих логических полей в структурах. Это гарантирует, что логические значения будут маршалированы как целое число (4 байта в .NET).

0 голосов
/ 18 ноября 2009

Может показаться, что 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
}
...