When we want to control the memory layout, we must use System.Runtime.InteropServices namespace, it contains some useful attributes such as StructLayoutAttribute, FieldOffsetAttribute and also some useful enum like LayoutKind.
using System.Runtime.InteropServices;
namespace LearnCSharp
{
[StructLayout(LayoutKind.Explicit, Size = 8)]
struct UnionTest
{
[FieldOffset(0)]
public sbyte i8;
[FieldOffset(0)]
public Int16 i16;
[FieldOffset(0)]
public Int32 i32;
[FieldOffset(0)]
public Int64 i64; // the biggest field, 8 bytes, so Size = 8
}
class Program
{
static void Main(string[] args)
{
UnionTest ut = new UnionTest();
ut.i64 = 0;
ut.i8 = 0x1;
ut.i16 |= 0x1000;
ut.i32 |= 0x10000000;
Console.WriteLine("{0:x16}", ut.i64);
return;
}
}
}
LayoutKind Enum has 3 members:
Sequential(good enough for general purpose)
The members of the object are laid out sequentially, in the order in which they appear when exported to unmanaged memory. The members are laid out according to the packing specified in StructLayoutAttribute.Pack, and can be noncontiguous.
Explict(for special layout, powerful but you have to type more code to specify each field)
The precise position of each member of an object in unmanaged memory is explicitly controlled. Each member must use the FieldOffsetAttribute to indicate the position of that field within the type.
Auto(use it when you don't want to expose it to unmanaged code)
The runtime automatically chooses an appropriate layout for the members of an object in unmanaged memory. Objects defined with this enumeration member cannot be exposed outside of managed code. Attempting to do so generates an exception.
In the code below, struct Rect1 and Rect2 has the same memory layout, we use explicit and sequential only for demonstration.
using System.Runtime.InteropServices;
namespace LearnCSharp
{
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int x;
public int y;
}
// Use LayoutKind.Sequential and Pack to make the memory layout same as unmanaged code
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct Rect1
{
public int left;
public int top;
public int right;
public int bottom;
}
// Use explicit offset to make the memory layout same as unmanaged code
[StructLayout(LayoutKind.Explicit)]
public struct Rect2
{
[FieldOffset(0)]
public int left;
[FieldOffset(4)]
public int top;
[FieldOffset(8)]
public int right;
[FieldOffset(12)]
public int bottom;
}
class LibWrapper
{
[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool PtInRect(ref Rect1 r, Point p);
[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool PtInRect(ref Rect2 r, Point p);
}
class TestApplication
{
public static void Main()
{
try
{
bool PointInRect;
Point myPoint = new Point();
myPoint.x = 50;
myPoint.y = 50;
Rect1 rect1 = new Rect1();
rect1.left = 10;
rect1.right = 100;
rect1.top = 10;
rect1.bottom = 100;
PointInRect = LibWrapper.PtInRect(ref rect1, myPoint);
if (PointInRect == true)
Console.WriteLine("Point lies within the Rect");
else
Console.WriteLine("Point did not lies within the Rect");
Rect2 rect2 = new Rect2();
rect2.left = 10;
rect2.right = 100;
rect2.top = 10;
rect2.bottom = 100;
PointInRect = LibWrapper.PtInRect(ref rect2, myPoint);
if (PointInRect == true)
Console.WriteLine("Point lies within the Rect");
else
Console.WriteLine("Point did not lies within the Rect");
}
catch (Exception e)
{
Console.WriteLine("Exception : " + e.Message);
}
}
}
}