2009-11-09 10 views
5

En primer lugar, se dice que el tipo booleano tiene un tipo de mariscal predeterminado de un valor de cuatro bytes. Por lo tanto, el siguiente código funciona:Boolean Marshalling con LayoutKind.Explicit, ¿está roto o fallando según lo diseñado?

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

Claramente estas estructuras marcan independientemente bien. Los valores se traducen como se esperaba. Sin embargo, cuando combinamos estas estructuras en una "unión" declarando LayoutKind.Explicit así:

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

de repente nos encontramos incapaces de formar correctamente este tipo. Aquí está el código de prueba para la estructura anterior y cómo se produce un error:

 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 

Es muy cómico ver esto expresa como verdadero: (a.bValue1 = false & & a.bValue1 == true & & verdad! .Equals (a.bValue1))

Por supuesto, el problema más grande aquí es que a.iValue2! = 4, más bien el 4 ha sido cambiado a 1 (presumiblemente por el bool superpuesto).

Entonces la pregunta: ¿Es esto un error, o simplemente falló como se diseñó?

Antecedentes: esto vino de What is the difference between structures containing bool vs uint when using PInvoke?

Actualización: Esto es aún más extraño cuando se utiliza los valores de número entero grande (> 255), ya que sólo el byte que se utiliza para el booleano está siendo modificado a un 1, por lo tanto cambiando 0x0f00 a 0x0f01 para el b.bValue2. Para a.bValue1 anterior, no está traducido en absoluto y 0x0f00 proporciona un valor falso para a.bValue1.

Actualización # 2:

La solución más obvia y razonable a la cuestión (s) anteriormente es utilizar un uint para el cálculo de referencias y exponer propiedades booleanas en su lugar. Realmente resolver el problema con una 'solución alternativa' no está en duda. Me pregunto si esto es un error o es este el comportamiento que esperarías?

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

Respuesta

4

Está trabajando como está diseñado.

Esto es lo que está sucediendo:

tomar el nuevo int [] {2, 4} y permite calcular la referencia que en A, B, Roto y Broken2. Lo último es lo mismo que Roto, pero con el orden de los campos invertido (primero b, luego a).

Si Marshal los enteros en estas estructuras que obtenemos los siguientes valores en la memoria:

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

Así que lo que está ocurriendo es la siguiente:

  • Cuando el Mariscal encuentra un valor booleano, el valor es: bool = (original! = 0);
  • Cuando hay dos campos que se asignan a la misma memoria, las reglas del último campo ganan

Así que para A, el primer int se convierte a 1, para B, el segundo int se convierte a 1 , para Roto, ya que B es el último campo, se aplican sus reglas, y por lo tanto el segundo int se convierte a 1. De forma similar para Broken2.

1

La línea comentó con 'FAILS, WOW, WTF?' falla debido a la forma en que se realiza la comparación booleana. Se está comparando 2 a 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 termina comparando 1 al byte en bValue, que es 2.

Lo curioso es que si (broken.a.bValue1) probará "verdadero" porque no es cero.

Por lo que el otro problema (broken.a.iValue2 == 4), que se fue cuando solicité:

[MarshalAs (UnmanagedType.Bool)] 

a ambos campos booleanos en las estructuras. Esto asegura que los booleanos se calculan como un entero (4 bytes en .NET).

+0

Me resulta interesante que la evaluación de "broken.a.bValue1 == true" es correcto cuando "true.Equals (broken.a.bValue1)" no lo es. Probaré lo de MarshalAs (UnmanagedType.Bool). –

+0

usando MarshalAs (UnmanagedType.Bool) parece no tener ningún efecto. –

+0

Pruebe esto: http://pastebin.ca/1665189 No hay nada impreso en el resultado de error una vez que comente el problema que ocurre debido a la IL anterior. – Gonzalo

0

Parecería earlNameless es correcta, como la adición de otra estructura de enteros:

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

hasta el final de la unión parece corregir al menos una parte del problema. Sin embargo, esto sigue siendo defectuoso ya que boolean solo considerará un valor de byte único y, como se ha demostrado, no es confiable. Finalmente, la mejor respuesta que he encontrado es utilizar un tipo personalizado para la clasificación.

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