Chapter 25 - Native and COM Interoperability
Calling into Native DLLs
void Main() { 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)
[DllImport ("libc")] public static extern uint getuid(); public static void PrintUserID() { Console.WriteLine ($"User ID: {getuid()}"); }
Type Marshaling - StringBuilder
void Main() { 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
void Main() { 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)
[DllImport ("libc")] private static extern string getcwd (StringBuilder buf, int size); StringBuilder sb = new StringBuilder (256); Console.WriteLine (getcwd (sb, sb.Capacity));
Marshaling Classes and Structs
[DllImport ("kernel32.dll")] static extern void GetSystemTime (SystemTime t); static void Main() { SystemTime t = new SystemTime(); GetSystemTime (t); Console.WriteLine (t.Year); } [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. [StructLayout (LayoutKind.Sequential)] struct Timespec { public long tv_sec; /* seconds */ public long tv_nsec; /* nanoseconds */ } [DllImport ("libc")] private static extern int clock_gettime (int clk_id, ref Timespec tp); static DateTime startOfUnixTime = new DateTime (1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); static void Main() { Console.WriteLine (GetSystemTime()); } static DateTime GetSystemTime() { Timespec tp = new Timespec(); int success = clock_gettime (0, ref tp); if (success == 0) return startOfUnixTime.AddSeconds (tp.tv_sec).ToLocalTime(); throw new Exception ("Error checking the time."); }
Callbacks from Unmanaged Code
void Main() => EnumWindows (PrintWindow, IntPtr.Zero); 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; }
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 { get { return 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 { get { return 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");