Chapter 24 - Native and COM Interoperability

Calling into Native DLLs

MessageBox (IntPtr.Zero,
           "Please do not press this again.", "Attention", 0);

[DllImport ("user32.dll")]
static extern int MessageBox (IntPtr hWnd, string text, string caption, int type);

Get User ID (Linux)

Console.WriteLine ($"User ID: {getuid()}");

[DllImport ("libc")]
static extern uint getuid();

Type Marshaling - StringBuilder

StringBuilder s = new StringBuilder (256);
GetWindowsDirectory (s, 256);
Console.WriteLine (s.ToString());

[DllImport ("kernel32.dll")]
static extern int GetWindowsDirectory (StringBuilder sb, int maxChars);

Type Marshaling - ArrayPool

GetWindowsDirectory().Dump();

string GetWindowsDirectory()
{
  var array = ArrayPool<char>.Shared.Rent (256);
  try
  {
    int length = GetWindowsDirectory (array, 256);
    return new string (array, 0, length).ToString();
  }
  finally
  {
    ArrayPool<char>.Shared.Return (array);
  }
  
  [DllImport ("kernel32.dll", CharSet = CharSet.Unicode)]
  static extern int GetWindowsDirectory (char[] buffer, int maxChars);
}

Get Current Working Directory (Linux)

StringBuilder sb = new StringBuilder (256);
Console.WriteLine (getcwd (sb, sb.Capacity));

[DllImport ("libc")]
static extern string getcwd (StringBuilder buf, int size);

Marshaling Classes and Structs

SystemTime t = new SystemTime();
GetSystemTime (t);
Console.WriteLine (t.Year);

[DllImport ("kernel32.dll")]
static extern void GetSystemTime (SystemTime t);

[StructLayout (LayoutKind.Sequential)]
class SystemTime
{
  public ushort Year;
  public ushort Month;
  public ushort DayOfWeek;
  public ushort Day;
  public ushort Hour;
  public ushort Minute;
  public ushort Second;
  public ushort Milliseconds;
}

System Time (Linux)

// The struct layout here was tested on Ubuntu Linux 18.04.
// Other Unix flavors may require a different layout.
// The *.h files for the system's C compiler are a good starting point.
Console.WriteLine (GetSystemTime());

static DateTime GetSystemTime()
{
  DateTime startOfUnixTime =
    new DateTime (1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);

  Timespec tp = new Timespec();
  int success = clock_gettime (0, ref tp);
  if (success != 0) throw new Exception ("Error checking the time.");
  return startOfUnixTime.AddSeconds (tp.tv_sec).ToLocalTime();
}

[DllImport ("libc")]
static extern int clock_gettime (int clk_id, ref Timespec tp);

[StructLayout (LayoutKind.Sequential)]
struct Timespec
{
  public long tv_sec;   /* seconds */
  public long tv_nsec;  /* nanoseconds */
}

Callbacks from Unmanaged Code - function pointers

EnumWindows (&PrintWindow, IntPtr.Zero);

[DllImport ("user32.dll")]
static extern int EnumWindows (delegate*<IntPtr, IntPtr, bool> hWnd, IntPtr lParam);

static bool PrintWindow (IntPtr hWnd, IntPtr lParam)
{
  Console.WriteLine (hWnd.ToInt64());
  return true;
}

Callbacks from Unmanaged Code - unmanaged function pointers

EnumWindows (&PrintWindow, IntPtr.Zero);

[DllImport ("user32.dll")]
static extern int EnumWindows (
  delegate* unmanaged <IntPtr, IntPtr, byte> hWnd, IntPtr lParam);

[UnmanagedCallersOnly]
static byte PrintWindow (IntPtr hWnd, IntPtr lParam)
{
  Console.WriteLine (hWnd.ToInt64());
  return 1;
}

Callbacks from Unmanaged Code - calling conventions

EnumWindows (&PrintWindow, IntPtr.Zero);

[DllImport ("user32.dll")]
static extern int EnumWindows (
  delegate* unmanaged[Stdcall] <IntPtr, IntPtr, byte> hWnd, IntPtr lParam);

[UnmanagedCallersOnly (CallConvs = new[] { typeof (CallConvStdcall) })]
static byte PrintWindow (IntPtr hWnd, IntPtr lParam)
{
  Console.WriteLine (hWnd.ToInt64());
  return 1;
}

Callbacks from Unmanaged Code - delegates

class CallbackFun
{
  delegate bool EnumWindowsCallback (IntPtr hWnd, IntPtr lParam);

  [DllImport("user32.dll")]
  static extern int EnumWindows (EnumWindowsCallback hWnd, IntPtr lParam);

  static bool PrintWindow (IntPtr hWnd, IntPtr lParam)
  {
    Console.WriteLine (hWnd.ToInt64());
    return true;
  }
  static readonly EnumWindowsCallback printWindowFunc = PrintWindow;

  static void Main() => EnumWindows (printWindowFunc, IntPtr.Zero);
}

Callback (Linux)

// The struct layout here was tested on Ubuntu Linux 18.04.
// Other Unix flavors may require a different layout. The *.h files for the system's C compiler are a good starting point.
[DllImport ("libc")]
private static extern int ftw (string dirpath, DirClbk cl, int maxFD);

[StructLayout (LayoutKind.Sequential)]
struct timespec
{
  long tv_sec;  /* seconds */
  long tv_nsec; /* nanoseconds */
}

[StructLayout (LayoutKind.Sequential)]
unsafe struct Stat
{
  public ulong st_dev;     /* Device.  */
  public ulong st_ino;     /* File serial number.  */
  public ulong st_nlink;   /* Link count.  */
  public uint st_mode;     /* File mode.  */
  public uint st_uid;      /* User ID of the file's owner. */
  public uint st_gid;      /* Group ID of the file's group.*/
  int __pad0;
  public ulong st_rdev;    /* Device number, if device.  */
  public uint st_size;     /* Size of file, in bytes. */
  public ulong st_blksize; /* Optimal block size for I/O.  */
  public ulong st_blocks;  /* Number 512-byte blocks allocated. */
  public Timespec st_atim; /* Time of last access.  */
  public Timespec st_mtim; /* Time of last modification.  */
  public Timespec st_ctim; /* Time of last status change.  */
  fixed ulong __glibc_reserved [3];
}

private delegate int DirClbk (string fName, ref Stat stat, int type);

private static int DirEntryCallback (string fName, ref Stat stat, int type)
{
  Console.WriteLine ($"{fName} - {stat.st_size} bytes");
  return 0;
}

// Use the code like this:
ftw ("/tmp", DirEntryCallback, maxFileDescriptorsToUse);

Simulating a C Union

void Main()
{
  NoteMessage n = new NoteMessage();
  Console.WriteLine (n.PackedMsg);    // 0

  n.Channel = 10;
  n.Note = 100;
  n.Velocity = 50;
  Console.WriteLine (n.PackedMsg);    // 3302410

  n.PackedMsg = 3328010;
  Console.WriteLine (n.Note);         // 200
}

[DllImport ("winmm.dll")]
public static extern uint midiOutShortMsg (IntPtr handle, uint message);

[StructLayout (LayoutKind.Explicit)]
public struct NoteMessage
{
  [FieldOffset (0)] public uint PackedMsg;    // 4 bytes long

  [FieldOffset (0)] public byte Channel;      // FieldOffset also at 0
  [FieldOffset (1)] public byte Note;
  [FieldOffset (2)] public byte Velocity;
}

Shared Memory Client

static unsafe void Main()
{
  using (SharedMem sm = new SharedMem ("MyShare", true,
                            (uint)sizeof (MySharedData)))
  {
    void* root = sm.Root.ToPointer();
    MySharedData* data = (MySharedData*)root;

    Console.WriteLine ($"Value is {data->Value}");
    Console.WriteLine ($"Letter is {data->Letter}");
    Console.WriteLine ($"11th Number is {data->Numbers [10]}");

    // Our turn to update values in shared memory!
    data->Value++;
    data->Letter = '!';
    data->Numbers [10] = 987.5f;
    Console.WriteLine ("Updated shared memory");
    Console.ReadLine();
  }
}

[StructLayout (LayoutKind.Sequential)]
unsafe struct MySharedData
{
  public int Value;
  public char Letter;
  public fixed float Numbers [50];
}

public sealed class SharedMem : IDisposable
{
  // Here we're using enums because they're safer than constants

  enum FileProtection : uint      // constants from winnt.h
  {
    ReadOnly = 2,
    ReadWrite = 4
  }

  enum FileRights : uint          // constants from WinBASE.h
  {
    Read = 4,
    Write = 2,
    ReadWrite = Read + Write
  }

  static readonly IntPtr NoFileHandle = new IntPtr (-1);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr CreateFileMapping (IntPtr hFile,
                                          int lpAttributes,
                                          FileProtection flProtect,
                                          uint dwMaximumSizeHigh,
                                          uint dwMaximumSizeLow,
                                          string lpName);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr OpenFileMapping (FileRights dwDesiredAccess,
                                        bool bInheritHandle,
                                        string lpName);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr MapViewOfFile (IntPtr hFileMappingObject,
                                      FileRights dwDesiredAccess,
                                      uint dwFileOffsetHigh,
                                      uint dwFileOffsetLow,
                                      uint dwNumberOfBytesToMap);

  [DllImport ("Kernel32.dll", SetLastError = true)]
  static extern bool UnmapViewOfFile (IntPtr map);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern int CloseHandle (IntPtr hObject);

  IntPtr fileHandle, fileMap;

  public IntPtr Root => fileMap;

  public SharedMem (string name, bool existing, uint sizeInBytes)
  {
    if (existing)
      fileHandle = OpenFileMapping (FileRights.ReadWrite, false, name);
    else
      fileHandle = CreateFileMapping (NoFileHandle, 0,
                                      FileProtection.ReadWrite,
                                      0, sizeInBytes, name);
    if (fileHandle == IntPtr.Zero)
      throw new Win32Exception();

    // Obtain a read/write map for the entire file
    fileMap = MapViewOfFile (fileHandle, FileRights.ReadWrite, 0, 0, 0);

    if (fileMap == IntPtr.Zero)
      throw new Win32Exception();
  }

  public void Dispose()
  {
    if (fileMap != IntPtr.Zero) UnmapViewOfFile (fileMap);
    if (fileHandle != IntPtr.Zero) CloseHandle (fileHandle);
    fileMap = fileHandle = IntPtr.Zero;
  }
}

Shared Memory Server

static unsafe void Main()
{
  using (SharedMem sm = new SharedMem ("MyShare", false, (uint)sizeof(MySharedData)))
  {
    void* root = sm.Root.ToPointer();
    MySharedData* data = (MySharedData*)root;

    Console.Write("Before this process writes to shared mem:");
    Console.WriteLine ($"Value is {data->Value}");
    Console.WriteLine ($"Letter is {data->Letter}");
    Console.WriteLine ($"11th Number is {data->Numbers [10]}");

    data->Value = 123;
    data->Letter = 'X';
    data->Numbers [10] = 1.45f;
    Console.WriteLine ("Written to shared memory");

    Console.ReadLine();

    Console.WriteLine ($"Value is {data->Value}");
    Console.WriteLine ($"Letter is {data->Letter}");
    Console.WriteLine ($"11th Number is {data->Numbers [10]}");
    Console.ReadLine();
  }
}

[StructLayout (LayoutKind.Sequential)]
unsafe struct MySharedData
{
  public int Value;
  public char Letter;
  public fixed float Numbers [50];
}

public sealed class SharedMem : IDisposable
{
  // Here we're using enums because they're safer than constants

  enum FileProtection : uint      // constants from winnt.h
  {
    ReadOnly = 2,
    ReadWrite = 4
  }

  enum FileRights : uint          // constants from WinBASE.h
  {
    Read = 4,
    Write = 2,
    ReadWrite = Read + Write
  }

  static readonly IntPtr NoFileHandle = new IntPtr (-1);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr CreateFileMapping (IntPtr hFile,
                                          int lpAttributes,
                                          FileProtection flProtect,
                                          uint dwMaximumSizeHigh,
                                          uint dwMaximumSizeLow,
                                          string lpName);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr OpenFileMapping (FileRights dwDesiredAccess,
                                        bool bInheritHandle,
                                        string lpName);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern IntPtr MapViewOfFile (IntPtr hFileMappingObject,
                                      FileRights dwDesiredAccess,
                                      uint dwFileOffsetHigh,
                                      uint dwFileOffsetLow,
                                      uint dwNumberOfBytesToMap);

  [DllImport ("Kernel32.dll", SetLastError = true)]
  static extern bool UnmapViewOfFile (IntPtr map);

  [DllImport ("kernel32.dll", SetLastError = true)]
  static extern int CloseHandle (IntPtr hObject);

  IntPtr fileHandle, fileMap;

  public IntPtr Root => fileMap;

  public SharedMem (string name, bool existing, uint sizeInBytes)
  {
    if (existing)
      fileHandle = OpenFileMapping (FileRights.ReadWrite, false, name);
    else
      fileHandle = CreateFileMapping (NoFileHandle, 0,
                                      FileProtection.ReadWrite,
                                      0, sizeInBytes, name);
    if (fileHandle == IntPtr.Zero)
      throw new Win32Exception();

    // Obtain a read/write map for the entire file
    fileMap = MapViewOfFile (fileHandle, FileRights.ReadWrite, 0, 0, 0);

    if (fileMap == IntPtr.Zero)
      throw new Win32Exception();
  }

  public void Dispose()
  {
    if (fileMap != IntPtr.Zero) UnmapViewOfFile (fileMap);
    if (fileHandle != IntPtr.Zero) CloseHandle (fileHandle);
    fileMap = fileHandle = IntPtr.Zero;
  }
}

Calling a COM Component from C#

var excel = new Excel.Application();
excel.Visible = true;
Excel.Workbook workBook = excel.Workbooks.Add();
((Excel.Range)excel.Cells [1, 1]).Font.FontStyle = "Bold";
((Excel.Range)excel.Cells [1, 1]).Value2 = "Hello World";
workBook.SaveAs (Path.GetTempFileName() + ".xlsx");
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription