Defining Structs and Unions
Struct and Union shares a lot of the same characteristics, only that struct may be either a Union and packed data structure or both. You can define the FieldOffset in C# that allows you to overlap the fields when defining a Union. Also when attempting to marshal structs, struct should have StructLayout attribute set to LayoutKind.Sequential or LayoutKind.Explicit. The difference between the three layouts are, one is C# managed struct, a simplified layout for marshaling based on datatype and structs in ordered fashion, and explicit requires each field to have FieldOffset attribute to explicitly define where field should be located.Now let's get started:
Copy and paste this code for Tutorial.c
#include <stdint.h>
#include <stdlib.h>
struct TutorialStruct {
int32_t FirstField;
int16_t SecondField;
int16_t ThirdField;
int64_t FourthField;
};
union TutorialUnion {
int16_t FirstField;
int32_t SecondField;
int64_t ThirdField;
};
struct TutorialStruct* CreateTutorialStruct()
{
struct TutorialStruct* tutorialStructPtr = (struct TutorialStruct*)malloc(sizeof(struct TutorialStruct));
tutorialStructPtr->FirstField = 11;
tutorialStructPtr->SecondField = 22;
tutorialStructPtr->ThirdField = 33;
tutorialStructPtr->FourthField = 44;
return tutorialStructPtr;
}
union TutorialUnion* CreateTutorialUnion()
{
union TutorialUnion* tutorialUnionPtr = (union TutorialUnion*)malloc(sizeof(union TutorialUnion));
tutorialUnionPtr->FirstField = 12443;
return tutorialUnionPtr;
}
And then let's clear Program.cs to keep it tidy:
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Tutorial1
{
public class LibTutorial : DLSupport
{
public LibTutorial() : base(Path.GetDirectoryName(typeof(LibTutorial).Assembly.Location),
"libtutorial.so", LoadFlag.RTLD_LAZY)
{
}
static void Main()
{
LibTutorial lib = new LibTutorial();
}
}
}
First thing first, let's define our struct.
We have a clearly defined struct in C that first field is an int, second and third fields are shorts, and then the last field is a long. Let's define struct as such:
public struct TutorialStruct
{
public int FirstField;
public short SecondField;
public short ThirdField;
public long FourthField;
}
Now we have to remember to add an attribute to our struct, so to keep it relatively simple, let's set our LayoutKind to sequential like so:
[StructLayout(LayoutKind.Sequential)]
And we have:
[StructLayout(LayoutKind.Sequential)]
public struct TutorialStruct
{
public int FirstField;
public short SecondField;
public short ThirdField;
public long FourthField;
}
Next, we need to define a union which means fields are going to be overlapping over one another, so to define that, we need to set the offset to 0 and set the LayoutKind to Explicit like so:
[StructLayout(LayoutKind.Explicit)]
public struct TutorialUnioni
{
[FieldOffset(0)]
public short FirstField;
[FieldOffset(0)]
public int SecondField;
[FieldOffset(0)]
public long ThirdField;
}
Now we have to define our functions that would recieve the pointer of those structs from C. Since it's a pointer, we will need to define those pointer as IntPtr and marshal a struct from those.
So we have:
public CreateTutorialStruct_dt CreateTutorialStruct;
public CreateTutorialUnion_dt CreateTutorialUnion;
public delegate IntPtr CreateTutorialStruct_dt();
public delegate IntPtr CreateTutorialUnion_dt();
and
public LibTutorial() : base(Path.GetDirectoryName(typeof(LibTutorial).Assembly.Location),
"libtutorial.so", LoadFlag.RTLD_LAZY)
{
CreateTutorialStruct = GetFunctionDelegate<CreateTutorialStruct_dt>("CreateTutorialStruct");
CreateTutorialUnion = GetFunctionDelegate<CreateTutorialUnion_dt>("CreateTutorialUnion");
}
Now we have our structs and functions defined, now let's try calling them and marshaling them.
So first, let's call the functions we've defined and assigned in Main method:
var lib = new LibTutorial();
var tutorialStructPtr = lib.CreateTutorialStruct();
var tutorialUnionPtr = lib.CreateTutorialUnion();
then we'll use one of Marshal's function, PtrToStructure which accepts a generic argument of struct type we're going to use and a parameter to let Marshal what to marshal from:
var tutorialStruct = Marshal.PtrToStructure<TutorialStruct>(tutorialStructPtr);
var tutorialUnion = Marshal.PtrToStructure<TutorialUnion>(tutorialUnionPtr);
Now we have our structs, we need to display the fields of those structs! I've written a little reflection magic to quickly iterate the fields in a struct and output it's value:
Console.WriteLine("tutorialStruct");
foreach (var field in tutorialStruct.GetType().GetFields())
Console.WriteLine("\t{0}", field.GetValue(tutorialStruct));
Console.WriteLine("tutorialUnion");
foreach (var field in tutorialUnion.GetType().GetFields())
Console.WriteLine("\t{0}", field.GetValue(tutorialUnion));
Your code should looks like this:
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Tutorial3
{
public class LibTutorial : DLSupport
{
public LibTutorial() : base(Path.GetDirectoryName(typeof(LibTutorial).Assembly.Location),
"libtutorial.so", LoadFlag.RTLD_LAZY)
{
CreateTutorialStruct = GetFunctionDelegate<CreateTutorialStruct_dt>("CreateTutorialStruct");
CreateTutorialUnion = GetFunctionDelegate<CreateTutorialUnion_dt>("CreateTutorialUnion");
}
public CreateTutorialStruct_dt CreateTutorialStruct;
public CreateTutorialUnion_dt CreateTutorialUnion;
public delegate IntPtr CreateTutorialStruct_dt();
public delegate IntPtr CreateTutorialUnion_dt();
static void Main()
{
var lib = new LibTutorial();
var tutorialStructPtr = lib.CreateTutorialStruct();
var tutorialUnionPtr = lib.CreateTutorialUnion();
var tutorialStruct = Marshal.PtrToStructure<TutorialStruct>(tutorialStructPtr);
var tutorialUnion = Marshal.PtrToStructure<TutorialUnion>(tutorialUnionPtr);
Console.WriteLine("tutorialStruct");
foreach (var field in tutorialStruct.GetType().GetFields())
Console.WriteLine("\t{0}", field.GetValue(tutorialStruct));
Console.WriteLine("tutorialUnion");
foreach (var field in tutorialUnion.GetType().GetFields())
Console.WriteLine("\t{0}", field.GetValue(tutorialUnion));
}
}
[StructLayout(LayoutKind.Sequential)]
public struct TutorialStruct
{
public int FirstField;
public short SecondField;
public short ThirdField;
public long FourthField;
}
[StructLayout(LayoutKind.Explicit)]
public struct TutorialUnion
{
[FieldOffset(0)]
public short FirstField;
[FieldOffset(0)]
public int SecondField;
[FieldOffset(0)]
public long ThirdField;
}
}
Now you try build and run and see the result for yourself which should output:
Now let's talk about some of the quirks of C# when marshaling struct. When using MarshalAs attribute for struct such as this:
[return: MarshalAs(UnmanagedType.LPStruct)]
public delegate TutorialStruct CreateTutorialStruct_dt();
[return: MarshalAs(UnmanagedType.LPStruct)]
public delegate TutorialUnion CreateTutorialUnion_dt();
It does not do it's intended purpose of deferencing struct pointer by default, you have to use Marshal.PtrToStructure to deference it.
There is another quirk that you have to keep in mind, when working with struct that maybe defined as this:
struct ComplexStruct
{
int32_t FirstField;
union {
int16_t ShortField;
int64_t LongField;
}
};
You have to keep an account of offset in bytes for Unions that may be nested inside a struct, while you can set the same offset for Short and Long fields, you have to calculate the offset by 4 so it wouldn't overlap with FirstField which would be defined as such:
[StructLayout(LayoutKind.Explicit)]
public struct ComplexStruct
{
[FieldOffset(0)]
public int FirstField;
[FieldOffset(4)]
public short ShortField;
[FieldOffset(4)]
public long LongField;
}
Also keep in mind that structs can be defined inside another struct and Marshal will be able to marshal it correctly if struct is passed by value, but if by reference, it need to be stored as IntPtr and then Marshaled with Marshal.PtrToStructure.
Also feel free to access the tutorials github repository here.


