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");