I get an exception when trying to marshal this structure
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct Data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
[FieldOffset(0x1)]
public byte[] a2;
}
It says "Could not load type 'WTF.Data' from assembly 'WTF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 1 that is incorrectly aligned or overlapped by a non-object field."
When I change offset 1 to 0 or 4, everything is ok. What am I doing wrong?
Thanks
The [StructLayout] affects both the managed and the marshaled layout of the struct. Bit of a quirk in .NET but creating blittable structs is a rather big win on interop and the CLR can't ignore the fact that managed code always runs on an entirely unmanaged operating system. Not having to create a copy of a struct but just being able to pass a pointer to the managed version is a very major perf win.
Your [FieldOffset] value violates a very strong guarantee of the .NET memory model, object reference assignments are always atomic. An expensive word that means that another thread can never observe an invalid object reference that is only partially updated. Atomicity requires proper alignment, to a multiple of 4 in 32-bit mode, of 8 in 64-bit mode. It they are misaligned then the processor may need to perform multiple memory bus cycles to glue the bytes together. That's bad, it causes tearing when another thread is also updating the variable. Getting parts of the pointer value from the old value, part from the new. What's left is a corrupted pointer that crashes the garbage collector. Very bad.
Obscure stuff from the high-level point of view of C#, it is however very important to provide basic execution guarantees. You can't get it misaligned to 1, no workaround as long as you use LayoutKind.Explicit. So don't use it.
Please see Hans Passant's answer first - aligned data is a good thing, and the CLR enforces it for a reason. It does seem to be possible to "cheat" though, if you for some reason really need or want to:
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Data
{
public byte Dummy;
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 4,
ArraySubType = UnmanagedType.U1 )]
public byte[] a2;
}
It is also possible with unsafe
code:
[StructLayout( LayoutKind.Explicit, Pack = 1 )]
public unsafe struct Data
{
[FieldOffset( 1 )]
public fixed byte a2[4];
}
But again, probably not a good idea.
Edit: A third option would be to simply make the array 5 bytes long and have it at offset 0, and then just ignore the first byte. This does seem safer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With