Search code examples
c#marshalling

Marshal C++ union in C# that contains a pointer to an array


I have a problem creating a structure layout in C# that corresponds to the one in C++ so it can be marshaled. Here's the types that should be marshaled in C++:

enum E1
{
};

enum E2
{
};

struct Foo
{
    int foo1;
    int foo2;
};

struct UnionField1
{
    int uf11;
    Foo* uf12;
};

struct UnionField2
{
    int uf21;
    int uf22;
    int uf23;
};

struct UnionField3
{
    int uf31;
    int uf32;
};

struct Bar
{
    E1 bar1;
    union
    {
        UnionField1 bar2;
        UnionField2 bar3;
        UnionField3 bar4;
    };
    E2 bar5;
};

And here's my variant of them in C#:

public enum E1
{
}

public enum E2
{
}

public struct Foo
{
    public int foo1;
    public int foo2;
}

public struct UnionField1
{
    public int uf11;
    public Foo[] uf12;
    //public nint uf12;
}

public struct UnionField2
{
    public int uf21;
    public int uf22;
    public int uf23;
}

public struct UnionField3
{
    public int uf31;
    public int uf32;
}

[StructLayout(LayoutKind.Explicit)]
public struct Bar
{
    [FieldOffset(0)]
    public E1 bar1;
    [FieldOffset(8)]
    public UnionField1 bar2;
    [FieldOffset(8)]
    public UnionField2 bar3;
    [FieldOffset(8)]
    public UnionField3 bar4;
    [FieldOffset(24)]
    public E2 bar5;
}

If I try to do something like Marshal.SizeOf<Bar>(), I get the error "System.TypeLoadException : Could not load type 'Bar' from assembly '..., Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 8 that is incorrectly aligned or overlapped by a non-object field."

However, everything works fine if I change the field UnionField1.uf12 to the one commented out (nint instead of Foo[]). My question is why is that and is there a way to make this layout work with array representation so that I won't be required to convert Foo[] to nint?

As far as I understand Foo[] and nint should be marshaled in the same way in terms of memory layout, packing, etc am I wrong?

I also tried adding to UnionField1.uf12 attributes [MarshalAs(UnmanagedType.LPArray)] and [MarshalAs(UnmanagedType.LPStruct)] but it didn't help.


Solution

  • You can't overlap a reference-type object field with any other type. An array is a reference-type.

    You are going to have to marshal the array manually into a pointer.

    public struct UnionField1
    {
        public int uf11;
        public nint uf12;
    }
    

    Make sure to release the memory in a finally to avoid leaks.

    var size = Marshal.SizeOf<Foo>();
    var ptr = Marshal.AllocHGlobal(size * YourArray.Length);
    yourUnion.bar1.uf12 = ptr;
    try
    {
        for (var i = 0; i < YourArray.Length; i++)
        {
            Marshal.StructureToPtr(YourArray[i], ptr + i * size, false);
        }
        // make call
    }
    finally
    {
        Marshal.FreeHGlobal(ptr);
    }