Iain Sproat
Iain Sproat

Reputation: 5330

Unions in C# - incorrectly aligned or overlapped with a non-object field

I am marshaling via PInvoke to a native C dll which expects the following call.

private static extern int externalMethod(IntPtr Data, [MarshalAs(UnmanagedType.U4)] ref int dataLength);

The dataLength parameter is the length of the struct being passed via the IntPtr Data parameter. It throws an exception if the two do not match. The external method uses a C Union joining together four types.

I've managed to recreate unions in C# by using the FieldOffsetAttribute. I am then calculating the length of the C# union and calling the method with the following:

int len = Marshal.SizeOf(data);
IntPtr ptr = Marshal.AllocCoTaskMem(len);
externalMethod(ptr, len);

However, I get the error System.TypeLoadException : ... Could not load type because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field. with the following code. I believe it is perhaps either one of the strings or the integer array (variable B7)? How would I go about changing this to make it work - do I have to break the integer array into multiple variables?

[StructLayoutAttribute(LayoutKind.Explicit)]
public struct Union{
    [FieldOffset(0)]
    public A a;

    [FieldOffset(0)]
    public B b;

    [FieldOffset(0)]
    public C c;

    [FieldOffset(0)]
    public D d;
}

[StructLayout(LayoutKind.Sequential)]
public struct A
{
    public int A1;
    public int A2;
    public int A3;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 17)]
    public string A4;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 4)]
    public string A5;
}

[StructLayout(LayoutKind.Sequential)]
public struct B
{
    public int B1;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 2)]
    public string B2;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 4)]
    public string B3;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 6)]
    public string B4;
    public int B5;
    public int B6;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U4, SizeConst = 255)]
    public int[] B7;
}

[StructLayout(LayoutKind.Sequential)]
public struct C
{
    public int C1;
    public int C2;
    public int C3;
    public int C4;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 32)]
    public string C5;
    public float C6;
    public float C7;
    public float C8;
    public float C9;
    public float C10;
    public float C11;
    public float C12;
    public float C13;
    public float C14;
}

[StructLayout(LayoutKind.Sequential)]
public struct D
{
    public int D1;
    [MarshalAs(UnmanagedType.LPTStr, SizeConst = 36)]
    public string D2;
}

Upvotes: 2

Views: 3402

Answers (2)

Paul Alexander
Paul Alexander

Reputation: 32367

Just use the A/B/C/D structs directly and skip the union. In your extern calls, simply substitute the correct struct in the method declaration.

extern void UnionMethodExpectingA( A a );

If the unmanaged methods actually accept a union and behave differently based on the type passed, then you can declare different extern methods that all end up calling the same unmanaged entry point.

[DllImport( "unmanaged.dll", EntryPoint="ScaryMethod" )]
extern void ScaryMethodExpectingA( A a );

[DllImport( "unmanaged.dll", EntryPoint="ScaryMethod" )]
extern void ScaryMethodExpectingB( B b );

Updated for "length" parameter. The logic still applies. Just create a "wrapper" method and do the same thing.

void CallScaryMethodExpectingA( A a )
{
  ScaryMethodExpectingA( a, Marshal.SizeOf( a ) );
} 

Upvotes: 4

Timwi
Timwi

Reputation: 66573

It is difficult to answer this question without knowing what you are trying to achieve. An explicitly-layouted struct is a very bad choice for any normal use-case; this only makes sense if you are using the data in native calls (pinvoke), and in those cases you definitely don’t want to use the managed class string. The [MarshalAs] attribute only takes effect during the call invocations, not constantly every time the field is read from or written to by your managed code. It doesn’t allow you to overlap a string pointer with an int because doing so would allow you to set the pointer to a meaningless value and then accessing the string would crash the CLR. The same is true for arrays, so you can’t use char[] either.

If you are the author of the native code that you need to call, then I strongly recommend to write four separate methods instead of a single one that accepts four completely different data structures.

If you cannot change the native code, then you could always declare your four structs A, B, C and D the way you do now and just use them directly, without the union. Just declare four different pinvoke declarations for the same native function (use the EntryPoint property on the [DllImport] attribute).

Upvotes: 4

Related Questions