Union
This section provides a brief introduction to the union type.
Sometimes, it is necessary to store different types of variables in the same memory unit. For example, an integer variable and a string variable can be stored at the same starting memory address. Although these two variables occupy different byte sizes in memory, they start from the same address, which is achieved through overlapping techniques where multiple variables overlap each other. This structure, where several different variables share the same memory segment, is called a union.
union { int a; char b; };
Characteristics of union:
1. The same memory segment can be used to store several different types of members, but only one can be stored at a time.
2. The active member of the union is the last variable stored; once a new member is stored, the previous member becomes inactive.
Difference between union and struct
struct: The memory length occupied is the sum of the memory occupied by each member, with each member occupying its own memory unit.
union: The memory length occupied is equal to the length of the longest member.
Using union in C#
To interact with a union in C#, the StructLayoutAttribute and FieldOffsetAttribute attributes are required. For an introduction to these two attributes, refer to
C# P/Invoke: Using StructLayout Attribute to Control Memory Structure
Once you understand the principle of union, mapping it in C# is quite simple.
For example, the union from the previous code snippet can be represented in C# as follows:
[StructLayout(LayoutKind.Explicit)] struct MyStruct { [FieldOffset(0)]int a; [FieldOffset(0)]char b; };
In the previous article, the Pack field of the StructLayoutAttribute class was introduced. Here, we will introduce another field, the Size field, which is used to indicate the absolute size of a class or structure.
When using a union, if it is not a value type in C#, the size must be specified.
Test Program:
Simple case:
C++
union MYUNION{ int b; double d;};
extern "C" __declspec(dllexport) MYUNION GetMyUnion();
extern "C" __declspec(dllexport) MYUNION GetMyUnion(){ MYUNION myunion; myunion.b = 10; return myunion;}
C#
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Runtime.InteropServices;
namespace UnionTest{ class Program { [DllImport("UnionLib.dll")] public static extern MyUnion GetMyUnion();
static void Main(string[] args) {
// Only value type case var myunion = GetMyUnion(); Console.WriteLine(myunion.b); } }}
Run result:

Case with reference types:
Note: Value types and reference types must not overlap. Therefore, when using a union with reference types, they need to be handled separately.
Two export functions are defined here; the TestUnion2 function can accept MYUNION2 and output it. The GetMyUnion2 function directly returns MYUNION2.
C++
union MYUNION2{ int i; char str[128];};
extern "C" __declspec(dllexport) void TestUnion2(MYUNION2 u, int type);
extern "C" __declspec(dllexport) void TestUnion2(MYUNION2 u, int type){ if (type == 1) { std::cout << u.i << std::endl; } else { std::cout << u.str << std::endl; }}
extern "C" __declspec(dllexport) MYUNION2 GetMyUnion2();
extern "C" __declspec(dllexport) MYUNION2 GetMyUnion2(){ MYUNION2 myunion2; strcpy_s(myunion2.str, 11, "HelloWorld"); return myunion2;}
C#
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Runtime.InteropServices;
namespace UnionTest{ class Program { [DllImport("UnionLib.dll")] public static extern IntPtr GetMyUnion2();
[DllImport("UnionLib.dll")] public static extern void TestUnion2(MyUnion2_INT mm, int i);
[DllImport("UnionLib.dll")] public static extern void TestUnion2(MyUnion2_STR mm,int i);
static void Main(string[] args) { // Using reference type cannot be returned directly by signature, need to convert // If the i field in MYUNION2 is used here, MyUnion2_INT can be used as the return value var myunion2Ptr = GetMyUnion2(); MyUnion2_STR unionStr = new MyUnion2_STR(); unionStr = (MyUnion2_STR)Marshal.PtrToStructure(myunion2Ptr, typeof(MyUnion2_STR)); Console.WriteLine(unionStr.str);
// Note: Value types and reference types must not overlap // In the case of using reference types, union cannot be returned directly MyUnion2_INT mu1 = new MyUnion2_INT(); mu1.i = 30; TestUnion2(mu1, 1);
MyUnion2_STR mu2 = new MyUnion2_STR(); mu2.str = "abc"; TestUnion2(mu2, 2); } }
/// <summary> /// If the int field in the union is used, use this structure /// </summary> [StructLayout(LayoutKind.Explicit, Size = 128)] public struct MyUnion2_INT { [FieldOffset(0)] public int i; }
/// <summary> /// If the char[] field in the union is used, use this structure /// </summary> [StructLayout(LayoutKind.Sequential)] public struct MyUnion2_STR { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string str; }
}
Run result:

Lastly, let’s look at the case of using union in struct:
This example code uses Windows Data Types (LPWSTR). The pointer size varies across different platforms, so when calling from C#, the corresponding structure must be defined based on the platform.
C++
struct MYSTRUCTUNION{ UINT uType; union { LPWSTR pStr; char cStr[260]; };};
extern "C" __declspec(dllexport) MYSTRUCTUNION GetMyUnion3();
extern "C" __declspec(dllexport) MYSTRUCTUNION GetMyUnion3(){ MYSTRUCTUNION myunion; myunion.uType = 0; myunion.pStr = L"HelloWorld"; return myunion;}
C#
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Runtime.InteropServices;
namespace UnionTest{ class Program { [DllImport("UnionLib.dll")] public static extern MyStructUnion GetMyUnion3();
static void Main(string[] args) { // Case of using struct and union together // The reference type part uses a pointer for conversion; using a string directly will fail marshaling var myStructUnion = GetMyUnion3(); var str = Marshal.PtrToStringUni(myStructUnion.pStr); Console.WriteLine(str); } }
#if x86 /// <summary> /// 32-bit /// </summary> [StructLayout(LayoutKind.Explicit, Size = 264)] public struct MyStructUnion { [FieldOffset(0)] public uint uType;
[FieldOffset(4)] public IntPtr pStr;
[FieldOffset(4)] public IntPtr cStr; }
#endif
#if x64 /// <summary> /// 64-bit /// </summary> [StructLayout(LayoutKind.Explicit, Size = 272)] public struct MyStructUnion { [FieldOffset(0)] public uint uType;
[FieldOffset(8)] public IntPtr pStr;
[FieldOffset(8)] public IntPtr cStr; }#endif
}
Run result:

Example code
https://github.com/zhaotianff/cnblog-demo-code/tree/main/CSharpUnionDemo