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