Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boolean Marshalling with LayoutKind.Explicit, Is this broken or failing as designed?

Tags:

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; } } 
    }
like image 213
csharptest.net Avatar asked Nov 09 '09 20:11

csharptest.net


2 Answers

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.

like image 115
earlNameless Avatar answered Oct 18 '22 13:10

earlNameless


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).

like image 38
Gonzalo Avatar answered Oct 18 '22 13:10

Gonzalo