C++ Union in C#

C# equivalent to C union

C# doesn't natively support the C/C++ notion of unions. You can however use the StructLayout(LayoutKind.Explicit) and FieldOffset attributes to create equivalent functionality.
Note that this works only for primitive types like int and float.

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
struct byte_array
{
[FieldOffset(0)]
public byte byte1;

[FieldOffset(1)]
public byte byte2;

[FieldOffset(2)]
public byte byte3;

[FieldOffset(3)]
public byte byte4;

[FieldOffset(0)]
public short int1;

[FieldOffset(2)]
public short int2;
}

C++ union in C#

You can use explicit field layouts for that:

[StructLayout(LayoutKind.Explicit)] 
public struct SampleUnion
{
[FieldOffset(0)] public float bar;
[FieldOffset(4)] public int killroy;
[FieldOffset(4)] public float fubar;
}

Untested. The idea is that two variables have the same position in your struct. You can of course only use one of them.

More informations about unions in struct tutorial

Mapping C Union to C# struct

It's always best to use a type for the union, and then aggregate it with the containing struct. That lets the framework lay out the types and calculate offsets, apart from the union where you place all members at offset zero.

The byte array in the union makes things more complicated. One option here is to use a fixed buffer.

[StructLayout(LayoutKind.Explicit)]
public unsafe struct IDTYPE_UNION
{
[FieldOffset(0)]
public ushort i_legacy;
[FieldOffset(0)]
public fixed byte i[16];
};

Put it in the containing struct like this:

[StructLayout(LayoutKind.Sequential, Pack=2)]
public struct IDTYPE
{
public byte f;
public byte t;
public IDTYPE_UNION union;
};

Another option is to simply omit the i_legacy member and if you really do need to read out that data, do so from the byte array:

[StructLayout(LayoutKind.Sequential, Pack=2)]
public struct IDTYPE
{
public byte f;
public byte t;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
public byte[] i;

ushort i_legacy
{
get
{
return (ushort)((ushort)i[0] << 8 | (ushort)i[1]);
}
}
};

This option does allow you to avoid having to use unsafe code.

Marshaling complex C structs with c unions for C#

This is one solution how to get data from C/C++ to C#. Here I will describe what I did wrong and what one has to take care of.

To recall in our minds my requirement has been (still is) that any data represented as union in C/C++, needs to be represented as such in C#. This means for following structure:

typedef struct dataStreamConfiguration{
size_t FeatureSelector;
union {
viewCap AsViewCap;
pathCap AsPathCap;
}dataStream;
}dataStreamConfiguration;

Any data inside AsViewCap must have its kind of representation in AsPathCap, because its simple the same memory. If one of these two are modified, the other is also. To handle C/C++ unions in C# you need to provide a memory layout. As Stephan Schlecht already meantioned it is vital to know the alignment! My project is compiled for 32bit the alignment is at the 4 Byte boarder. Thus my initial layout in my question was simply wrong. You need to check the layout in your C/C++ project and adjust it in the C# struct definion properly: Here is my corrected code, both union members start at 4th Byte:

[StructLayout(LayoutKind.Explicit)]
public unsafe struct dataStreamConfiguration{
[FieldOffset(0)]
public UInt16 FeatureSelector;
[FieldOffset(4)]
public viewCap AsViewCap;
[FieldOffset(4)]
public pathCap AsPathCap;
}

Doing this you are half way there! Yes! But there is one more thing. With this change the Code will compile but, you will get an exception at runtime. Yes, at runtime. Quite quick but it is so. The error message something like: "object-field at offset 4 is incorrectly aligned or overlapped by a non-object field"
C# is bugging is because in C# there are basic types like Integer etc. and reference types.

These reference types can give us errors if we do not handel them correctly. C# has a very nice working marshalling, but in case of unions, its up to you to make it as nice as it can get.

Explenation:
What went wrong in my code is that the struct viewCap is having arrays, marshalled by the C# marshaller. The marshaller is doing his duty and creating an array. However, an array is a reference type and created on the heap. What you will get on the stack (data transfer C++ <-> C#) is the reference address to the array on the heap. Hmpf! Thus the second structure in the union with its basic types would overlapping the address and thus invalidating the reference. Gladly the runtime environment stops us from doing that :-) Moreover, C# is defragmenting the memory. If the struct layout is not efficient regarding memory usage C# will reoder the content. You can avoid this by annotating the struct which the layout kind: Sequential.

Remember:
If you got an array inside a type which is part of a union (C/C++), you cannot use the C# marshaller! Use Layoutkind Explicit for unions and Sequential for structs!

Solution:
There might be several other solutions I am not aware of, but what is 100% working is to unroll the arrays. Yes, it's a lot of work. But it works!

So the final struct looks like this:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct viewCap{
public Byte A;
public Byte B;
public UInt16 BCount;
public UInt32Struct ViewCapEnum_0;
public UInt32Struct ViewCapEnum_1;
public UInt32Struct ViewCapEnum_2;
public UInt32Struct ViewCapEnum_3;
[...]
public comSize MinComSize;
public UInt16 CapaCount;
public featTemplateCap TemplCap_0;
public featTemplateCap TemplCap_1;
public featTemplateCap TemplCap_2;
public featTemplateCap TemplCap_3;
[...]
public UInt16 TypeCapaCount;
public featTypeCap FeatTypeCapa_0;
public featTypeCap FeatTypeCapa_1;
public featTypeCap FeatTypeCapa_2;
public featTypeCap FeatTypeCapa_3;
[...]
public Byte GCount;
}

Happy runtime environment, happy life!

Yes, for this specific solution you would need to adjust the code which is using the arrays. However, its bulletproof and understandable how this works under the hood which makes it easy for maintenance. As my code is generated, an unrolled array is no big deal.

C++ struct/union into C# struct

Here is what I got, assuming a packed layout (which is not always the case, you will need to check your C++ code):

public class Program
{
public static void Main()
{
Console.WriteLine($"sizeof(_INFO): {Marshal.SizeOf(typeof(_INFO))}");
Console.WriteLine($"sizeof(Identification): {Marshal.SizeOf(typeof(Identification))}");
Console.WriteLine($"sizeof(Buffer): {Marshal.SizeOf(typeof(Buffer))}");
Console.WriteLine($"sizeof(_HEADER): {Marshal.SizeOf(typeof(_HEADER))}");
Console.WriteLine();

Console.WriteLine("To prove that it behaves union-like:");
var header = new _HEADER();
header.Identification.Protocol = 5;
Console.WriteLine($"header.Identification.Protocol: {header.Identification.Protocol}");
Console.WriteLine($"header.Buffer.Width: {header.Buffer.Width}");
}

public const int MAX_ = 10;
}

public enum TJ { _44, _42 }

public enum _TYPE { _OFF, _ON }

public enum _HEADER_TYPE { _HEADER_TYPE_IDENTIFICATION, _HEADER_TYPE_PING }

[StructLayout(LayoutKind.Explicit, Pack=4, Size=20)]
public struct _INFO
{
[FieldOffset(0)] public TJ S;
[FieldOffset(4)] public long Q;
[FieldOffset(12)] public long R1;
}

[StructLayout(LayoutKind.Explicit, Pack=4, Size=32+2*8*Program.MAX_)]
public struct Identification
{
[FieldOffset(0)] public long Protocol;
[FieldOffset(8)] public _TYPE CType;
[FieldOffset(12)] public _INFO InfoDesired;

[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = Program.MAX_)]
[FieldOffset(32)] public long[] ResolutionX;

[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = Program.MAX_)]
[FieldOffset(32 + Program.MAX_ * 8)] public long[] ResolutionY;
}

[StructLayout(LayoutKind.Explicit, Pack=4, Size=32)]
public struct Buffer
{
[FieldOffset(0)] public long Width;
[FieldOffset(4)] public _TYPE Type;
[FieldOffset(12)] public _INFO Info;
}

[StructLayout(LayoutKind.Explicit, Pack=4, Size=204)]
public struct _HEADER
{
// First slot (4 bytes)
[FieldOffset(0)] public _HEADER_TYPE HeaderType;

// Second slot (8 bytes)
[FieldOffset(4)] public ulong cc;

// The next 2 structs share the third slot (204 bytes)
[FieldOffset(12)] public Identification Identification;
[FieldOffset(12)] public Buffer Buffer;
}

Output:

sizeof(_INFO): 20
sizeof(Identification): 192
sizeof(Buffer): 32
sizeof(_HEADER): 204

To prove that it behaves union-like:
header.Identification.Protocol: 5
header.Buffer.Width: 5

The points to note are that:

  • an enum is basically an int, so it uses 4 bytes;
  • a long uses 8 bytes;
  • the argument of FieldOffset is in byte;
  • you need to keep MAX_ in sync between C++ code and C# code.

Declaring C struct with union on C#

Declare the two structs in the union as C# structs in the usual way. Then declare a type for the union, using an explicit layout.

[StructLayout(LayoutKind.Explicit)] 
public struct _WAITCHAIN_NODE_INFO_UNION
{
[FieldOffset(0)]
_WAITCHAIN_NODE_INFO_LOCK_OBJECT LockObject;
[FieldOffset(0)]
_WAITCHAIN_NODE_INFO_THREAD_OBJECT ThreadObject;
}

Then add the union to your struct:

[StructLayout(LayoutKind.Sequential)]
public struct WAITCHAIN_NODE_INFO
{
public WCT_OBJECT_TYPE ObjectType;
public WCT_OBJECT_STATUS ObjectStatus;
public _WAITCHAIN_NODE_INFO_UNION Union;
}

When you overlay objects like this, extra requirements are placed on the types involved. You cannot overlay a type containing a string or an array for instance. So the character array will have to be implemented as a value type, for instance a fixed array. This is inconvenient to operate with but MS did not define the types with C# in mind.



Related Topics



Leave a reply



Submit