6

I've run into an odd scenario marshaling unions that contain arrays in C#/.NET. Consider the following program:

namespace Marshal
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct InnerType
        {
            byte Foo;
            //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
            //byte[] Bar;
        }


        [StructLayout(LayoutKind.Explicit, Pack = 1)]
        struct UnionType
        {
            [FieldOffset(0)]
            InnerType UnionMember1;

            [FieldOffset(0)]
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
            byte[] UnionMember2;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(@"SizeOf UnionType: {0}", System.Runtime.InteropServices.Marshal.SizeOf(typeof(UnionType)));
        }
    }
}

If you run this program, you'll get the following exception:

Could not load type 'UnionType' from assembly 'Marshal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

Now if you uncomment the two commented out lines, the program will run fine. I'm wondering why this is. Why does adding an extra array to InnerType fix the problem? Incidentally, it doesn't matter what size you make the array. Without the array, UnionMember1 and UnionMember2 should match each other in size. With the array, their sizes do not match, yet no exceptions are thrown.

Update Changing InnerType to the following also causes the exception (on InnerType this time):

[StructLayout(LayoutKind.Explicit, Pack = 1)]
struct InnerType
{
    [FieldOffset(0)]
    byte Foo;

    [FieldOffset(1)]
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
    byte[] Bar;
}

It seems to me that this should be equivalent to the original code (with LayoutKind.Sequential) where byte[] Bar is uncommented.

I don't believe the problem here has anything to do with word boundaries--I'm using Pack = 1. Rather I think it's the second part of the exception, "...it contains an object field at offset 0 that is ... overlapped by a non-object field." byte[] is a reference type while byte itself is a value type. I can see that "byte Foo" will end up overlapping "byte[] UnionMember2". However, this still doesn't explain why uncommenting "byte[] bar" in my original code makes the exception go away.

csharpwinphonexaml
  • 3,659
  • 10
  • 32
  • 63
watkipet
  • 959
  • 12
  • 23
  • These posts should help: http://stackoverflow.com/questions/1190079/incorrectly-aligned-or-overlapped-by-a-non-object-field-error, http://stackoverflow.com/questions/4673099/unions-in-c-sharp-incorrectly-aligned-or-overlapped-with-a-non-object-field. Changing the field offset to 8 for the UnionMember2 fixes this. – David Venegoni Jan 30 '14 at 04:12
  • @David Venegoni - Thanks! I looked at these already, though. #1190079 is specific to the CF marshaller. I'm not using CF and I am using Pack. #4673099 has a solution to that user's problem but doesn't explain exactly what causes the exception. Using an offset of 8 does get rid of the exception (and the union), but still doesn't give any clues as to why adding InnerType.bar suppresses the exception. – watkipet Jan 30 '14 at 15:59
  • The pinvoke marshaller will not let you map a struct to a byte[], that's not a meaningful conversion. A size of 1 is bizarre as well, that's just a plain byte. You are also not allowed to overlap a reference type like byte[] with a value type, the garbage collector can no longer accurately see if the field stores a reference. Fixing that requires declaring the field as a fixed size buffer instead. – Hans Passant Jan 30 '14 at 16:41
  • 1
    @Hans Passant - You hit the nail on the head regarding overlapping reference and value types. However, this still doesn't answer why adding "byte[] Bar" makes the exception go away. Regarding the one-byte array, that's just there to keep the example simple. It's a contrived example. – watkipet Jan 30 '14 at 16:53
  • You can only get good help if you show a real example. – Hans Passant Jan 30 '14 at 16:56

1 Answers1

0

My hypothesis is that the sequential layout is overruled as described in this SO answer: LayoutKind.Sequential not followed when substruct has LayoutKind.Explicit.

Note that the pack setting of 1 does not eliminate padding, ie Bar does not have a FieldOffset of 1. It is aligned to 4 bytes (on x32), and according to Marshal.OffsetOf() it should be at 4 as expected.

However, the .NET runtime might actually put the reference type Bar before the byte Foo in managed memory, in which case it would correctly overlap in the union with UnionMember2.

The interesting thing is that with Foo int and float the same happens, but with long and double it again gives the exception. It seems like it sorts the fields on size, but puts reference types first if the size is equal.

When I switched to x64 then it also worked with long Foo, which would support this theory. Finally, I have opened a memory window (Debug->Windows->Memory) and typed in the location &instance.UnionMember1.Foo and scrolled a bit up to reveal the bytes before Foo. Then set a value in Foo and Bar using the immediate window which proved Bar is at 0 and Foo is at 4. (Add var instance = new UnionType() to Main)

Please keep in mind though that this is probably not what you intended, the Byte[] are just considered reference type. You could replace it with object. You might be able to use fixed byte Bar[1] instead, depending on your goals.

Community
  • 1
  • 1
Herman
  • 2,738
  • 19
  • 32