Boolean Marshalling com LayoutKind.Explicit, É este quebrado ou não como projetado?
-
19-09-2019 - |
Pergunta
Em primeiro lugar o tipo booleano é dito ter um padrão Marechal tipo de um valor de quatro bytes. Então o seguinte código funciona:
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);
}
É evidente que estas estruturas organizar de forma independente apenas multa. Os valores são traduzidos como esperado. No entanto, quando combinamos essas estruturas em uma "união", declarando LayoutKind.Explicit assim:
[StructLayout(LayoutKind.Explicit)]
struct Broken
{
[FieldOffset(0)]
public A a;
[FieldOffset(0)]
public B b;
}
Nós de repente encontrar-nos não é possível empacotar corretamente estes tipos. Aqui está o código de teste para a estrutura acima e como ele falhar:
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
É muito bem-humorado para ver este expressa como verdadeiro: (a.bValue1 = false && a.bValue1 == true && true.Equals (a.bValue1)!!)
É claro que o maior problema aqui é que a.iValue2! = 4, em vez do 4 foi alterado para 1 (presumivelmente pela bool sobreposta).
Portanto, a pergunta:? Este é um bug, ou apenas falhou como projetado
Fundo: este veio O que é a diferença entre as estruturas que contêm bool vs uint ao usar PInvoke?
Update: Isto é ainda mais estranho quando você usa grandes valores inteiros (> 255), como apenas o byte que é usado para o boolean está sendo modificada para um 1, alterando assim 0x0f00 para 0x0f01 para o b.bValue2. Para a.bValue1 acima não é traduzido em tudo e 0x0f00 fornece um valor falso para a.bValue1.
Atualização # 2:
A solução mais óbvia e razoável para a questão acima (s) é usar um uint para a triagem e expor propriedades boolean vez. Realmente resolver o problema com uma 'solução' não está em questão. Estou principalmente perguntando isso é um bug ou é este o comportamento que você esperaria?
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; } }
}
Solução
Ele está trabalhando como projetado.
Aqui está o que está acontecendo:
Pegue o novo int [] {2, 4} e permite marechal-lo em A, B, quebrado, e broken2. O último é o mesmo que quebrado, mas com ordem campos invertida (primeiro b, então a).
Se empacotar os ints para estas estruturas que obtemos os seguintes valores na memória:
- A: 1, 4
- B: 2, 1
- quebrado: 2, 1
- broken2: 1, 4
Então, o que está acontecendo é o seguinte:
- Quando o marshaller encontros um booleano, o valor é: bool = (! Originais = 0);
- Quando há dois campos que o mapa na mesma memória, as regras do último campo ganhar
Assim, para A, o primeiro int é convertido para 1, para B, o segundo int é convertido para 1, para quebrado, já que B é o último campo, as suas regras se aplicam, e, portanto, o segundo int é convertido para 1. Da mesma forma para broken2.
Outras dicas
A linha comentada com 'falhar, WOW, WTF? não por causa da maneira comparação boolean é realizada. Ele 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 acaba comparando um para o byte em bValue, que é 2.
O engraçado é que if (broken.a.bValue1) vai testar 'verdadeiro' porque é diferente de zero.
Quanto ao outro problema (broken.a.iValue2 == 4), ele foi embora quando me candidatei:
[MarshalAs (UnmanagedType.Bool)]
para ambos os campos boolean nas estruturas. Isso garante os booleans são empacotados como um inteiro (4 bytes em .NET).
Parece earlNameless está correta, como a adição de uma outra estrutura de inteiros:
struct C
{
public int iValue1;
public int iValue2;
}
para o fim da união parece correta, pelo menos, parte do problema. No entanto, isso ainda é falho desde o boolean só irá considerar um valor de byte único e como demonstrado não é confiável. Finalmente a melhor resposta que eu vim acima com é usar um tipo personalizado para o empacotamento.
[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
}