This article is also written in my site, Just Like a Magic.
Before You Start
If this is your first time you hear about unions or you need to know more about them, please refer to our article "A short speech about Unions" first.
How to Marshal a Union
You can marshal a union the same way you marshal structures. However, because of the way that unions laid-out into memory, you will need to explicitly set variable positions inside the type.
You can marshal a union in few steps:
-
Create your marshaling type, no matter whether your marshaling type is a managed structure or class. However, you should consider the passing mechanism when working with reference types like classes.
-
Decorate the type with the StructLayoutAttribute attribute specifying LayoutKind.Explicit to control exactly the memory location of every member inside the type.
-
Add your required fields only. Because we are controlling the memory layout explicitly, order of fields is no important.
-
Decorate every field with FieldOffsetAttribute attribute specifying the absolute position -in bytes- of the member from the start of the structure.
Example
Consider the following union:
union SOME_CHARACTER {
int iCode;
char cChar;
};
Now, it's the time for the meat of our lesson. The following code snippets defines the marshaling type of our union:
[StructLayout(LayoutKind.Explicit)]
public struct SOME_CHARACTER
{
// Both members located on the same
// position in the beginning of the union
// This is the continer. it is 4 bytes
[FieldOffset(0)]
[MarshalAs(UnmanagedType.I4)]
public int iCode;
// This is only 1 byte.
[FieldOffset(0)]
public char cChar;
}
static void Main()
{
SOME_CHARACTER character = new SOME_CHARACTER();
// The code for letter 'A'
character.dwCode = 65;
// Should prints 'A'
Console.WriteLine("wcChar = {0}", character.wcChar);
character.wcChar = 'B';
// Should prints 66
Console.WriteLine("Code = {0}", character.dwCode);
}
From the previous code, we learn that !
-
Unions marshaled like structures, they can be marshaled as either managed structures or classes.
-
Setting StructLayoutAttribute.LayoutKind to LayoutKind.Explicit allows us to exactly control the memory location of the members.
-
We use the FieldOffsetAttribute to specify the starting location in bytes of the field into the type in memory.
-
To create the union between the fields, we set both the fields to the same memory location.
-
In the example, iCode begins from byte 0 to byte 4. And cChar begins from byte 0 to byte 1.
-
If we do not need to take advantage of the union, we can emit cChar because it is contained inside the range of iCode. But, we cannot emit iCode because it is the container.
-
When we change either one of the union variables, the other variable changes too because they share the same memory address. Notice that in our example, int is 4-bytes and char is only 1 byte. Therefore, iCode interprets the whole value, while cChar interprets only the first byte (8 bits) of the value.
Unions with Arrays
Now, consider the following union:
union UNION_WITH_ARRAY
{
int nValue;
char str[10];
};
This union must be marshaled in a special way because managed code does not permit value types and reference types to overlap.
As a refresher, a value-type is the type stored into the stack memory; it inherits from System.ValueType. Value-types represented in all primitive data types, structures, and enumerations. On the other hand, reference-types are types stored in the memory heap; they inherit from System.Object. Most types in .NET are reference-types (except System.ValueType of course.)
As a result, we cannot union both members of our example, because whether marshaling the second variable str as an array, a System.Text.StringBuilder, or a System.String, it is still a reference-type. Therefore, we have to leave the advantage of unions, and marshal only a single member. For our example, we will create two marshaling types for our union, one with the first member marshaled, and the other with the other member.
As we know, the layout and size of the type inside the memory is the most crucial. Therefore, we must preserve the layout and size of our union. This union has a 10-bytes array as a container and only one member contained, and this member is only 4-bytes. Therefore, we have two choices, to marshal the union with the container member, or to marshal it with the contained member but to extend it enough to be as large as the container. In this example, we will take the two approaches.
The following are two code segments. The first demonstrates how to marshal only the second member which is the container, while the second demonstrates how to marshal the first member.
// Setting StructLayoutAttribute.CharSet
// ensures the correct encoding for all
// string members of the union in our example
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct UNION_WITH_ARRAY_1
{
// As we know, character arrays can be marshaled
// as either an array or as a string
// Setting MarshalAsAttribute is required
// for the array and the string
// That is another way:
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
//public char[] charArray;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string charArray;
}
// StructLayoutAttribute.Size determines
// the size -in bytes- of the type.
// If the size specified is larger than
// members' size, the last member will be extended
// Because this is only a single
// member, we laid it out sequentially.
[StructLayout(LayoutKind.Sequential, Size = 128)]
public struct UNION_WITH_ARRAY_2
{
[MarshalAs(UnmanagedType.I2)]
public short number;
}
Try it out!
If you are brave enough, you might try to marshal DEVMODE structure; that is one of the most complex structures in the Windows API. If you are interested you can refer to the MSDN library for the documentation of DEVMODE structure. Don't be shocked when you first see that structure. (My advice is to pray for God before you think about marshaling DEVMODE structure.)