Interop in C# — Complete Guide
In this tutorial, you will learn about Interop in C#. We cover key concepts, practical examples, and best practices to help you master this topic.
Hook
Not everything is written in C#. Sometimes you need to call operating system APIs, use native C++ libraries, or interoperate with COM components. .NET provides several mechanisms for interoperability: P/Invoke for C-style APIs, COM interop for Windows components, and unsafe code for direct memory manipulation.
Learning Path
graph LR A[Interop] --> B[P/Invoke] A --> C[Unsafe Code] A --> D[COM Interop] B --> E[DllImport] B --> F[Marshalling] C --> G[Pointers] D --> H[RCW] style A fill:#4a90d9,color:#fff style B fill:#4a90d9,color:#fff style C fill:#4a90d9,color:#fff style D fill:#4a90d9,color:#fff style E fill:#4a90d9,color:#fff style F fill:#4a90d9,color:#fff style G fill:#4a90d9,color:#fff style H fill:#4a90d9,color:#fff
Platform Invocation (P/Invoke)
P/Invoke lets you call functions from native DLLs.
using System;
using System.Runtime.InteropServices;
public static class NativeMethods
{
// Windows API
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(
IntPtr hWnd,
string lpText,
string lpCaption,
uint uType);
// Linux/POSIX
[DllImport("libc", SetLastError = true)]
public static extern int getpid();
// macOS
[DllImport("libSystem.dylib")]
public static extern int getpid_mac();
}
// Usage
// NativeMethods.MessageBox(IntPtr.Zero, "Hello from C#!", "P/Invoke", 0);
Console.WriteLine($"Process ID: {NativeMethods.getpid()}");
Struct Marshalling
Pass complex data structures to native code.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SystemInfo
{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
}
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo(out SystemInfo lpSystemInfo);
// Usage
GetSystemInfo(out var info);
Console.WriteLine($"Processors: {info.dwNumberOfProcessors}");
Console.WriteLine($"Page size: {info.dwPageSize} bytes");
Source-Generated P/Invoke (.NET 7+)
Use source generators for better performance.
// Source-generated P/Invoke (.NET 7+)
[LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
public static partial int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
// No marshalling code at runtime - generated at compile time
Unsafe Code and Pointers
Work directly with memory using unsafe blocks.
public static unsafe class UnsafeOperations
{
public static void PointerDemo()
{
int value = 42;
int* ptr = &value;
Console.WriteLine($"Value: {value}");
Console.WriteLine($"Pointer: {(nint)ptr:X}");
Console.WriteLine($"Dereferenced: {*ptr}");
*ptr = 100;
Console.WriteLine($"Modified value: {value}");
}
public static void ArrayPointer()
{
int[] numbers = { 10, 20, 30, 40, 50 };
fixed (int* ptr = numbers)
{
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"numbers[{i}] = {*(ptr + i)}");
}
}
}
public static void StackAlloc()
{
// Allocate on stack (no GC pressure)
int* buffer = stackalloc int[256];
for (int i = 0; i < 256; i++)
buffer[i] = i * i;
Console.WriteLine($"buffer[100] = {buffer[100]}");
}
public static void MemoryCopy(byte* source, byte* destination, int length)
{
for (int i = 0; i < length; i++)
destination[i] = source[i];
}
}
// Must compile with AllowUnsafeBlocks
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in .csproj
COM Interop
Interoperate with COM components on Windows.
// Early binding (requires COM reference)
// Add reference to Microsoft Excel Object Library
using Microsoft.Office.Interop.Excel;
public class ExcelExporter
{
public void ExportToExcel(string[,] data)
{
var excel = new Application { Visible = true };
var workbook = excel.Workbooks.Add();
var worksheet = (Worksheet)workbook.Sheets[1];
for (int row = 0; row < data.GetLength(0); row++)
for (int col = 0; col < data.GetLength(1); col++)
worksheet.Cells[row + 1, col + 1] = data[row, col];
// Release COM objects
Marshal.ReleaseComObject(worksheet);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(excel);
}
}
// Late binding (no COM reference needed)
Type? excelType = Type.GetTypeFromProgID("Excel.Application");
object? excel = Activator.CreateInstance(excelType!);
excelType?.InvokeMember("Visible", BindingFlags.SetProperty, null, excel, new object[] { true });
Native Memory Management
Allocate and free native memory manually.
public static class NativeMemoryDemo
{
public static IntPtr AllocateAndUse(int size)
{
// Allocate unmanaged memory
IntPtr ptr = Marshal.AllocHGlobal(size);
// Write data
for (int i = 0; i < size; i++)
Marshal.WriteByte(ptr, i, (byte)i);
// Read data
byte first = Marshal.ReadByte(ptr);
Console.WriteLine($"First byte: {first}");
return ptr; // Caller must free
}
public static void Free(IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
}
// SafeHandle for automatic cleanup
public class NativeBuffer : SafeHandle
{
public NativeBuffer(int size)
: base(IntPtr.Zero, ownsHandle: true)
{
SetHandle(Marshal.AllocHGlobal(size));
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
}
NativeAOT
Compile .NET to native code for scenarios where the runtime is not available.
<!-- .csproj -->
<PropertyGroup>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
# Publish as native binary
dotnet publish -r win-x64 -c Release
# Produces a single native executable with no .NET runtime dependency
Common Mistakes
Incorrect marshalling of strings: Use
CharSet = CharSet.UnicodeandMarshalAsattributes to match native string encoding.Memory leaks with native resources: Always free native allocations. Use
SafeHandlefor automatic cleanup.P/Invoke stack imbalance: Ensure calling convention matches (
CallingConvention.CdeclvsCallingConvention.StdCall).Forgetting to pin managed objects: Use
fixedstatement orGCHandle.Allocwhen passing managed arrays to native code.Unsafe code without proper validation: Validate all pointer operations to avoid buffer overflows and memory corruption.
Practice Questions
Write a P/Invoke wrapper for the
clock_gettimesystem call on Linux to measure high-resolution time.Create a library that uses unsafe code to perform fast byte array comparison (like memcmp).
Build a COM-based automation tool that controls Microsoft Word from C#.
Challenge: Implement a memory-mapped file reader using P/Invoke to
CreateFileMappingandMapViewOfFile.
FAQ
Mini Project: System Monitor
Use P/Invoke to display system information across platforms.
using System;
using System.Runtime.InteropServices;
public static class SystemMonitor
{
// Windows
[DllImport("kernel32.dll")]
private static extern bool GlobalMemoryStatusEx(out MEMORYSTATUSEX lpBuffer);
[StructLayout(LayoutKind.Sequential)]
private struct MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
}
// Linux
[DllImport("libc", SetLastError = true)]
private static extern int sysinfo(out SysInfo info);
private struct SysInfo
{
public long uptime;
public ulong[] loads;
public ulong totalram;
public ulong freeram;
public ulong sharedram;
public ulong bufferram;
public ulong totalswap;
public ulong freeswap;
public ushort procs;
public ulong totalhigh;
public ulong freehigh;
public uint mem_unit;
}
public static void ShowSystemInfo()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
GlobalMemoryStatusEx(out var mem);
Console.WriteLine("Windows System Info:");
Console.WriteLine($" Memory load: {mem.dwMemoryLoad}%");
Console.WriteLine($" Total RAM: {mem.ullTotalPhys / (1024 * 1024 * 1024)} GB");
Console.WriteLine($" Available: {mem.ullAvailPhys / (1024 * 1024 * 1024)} GB");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
sysinfo(out var info);
Console.WriteLine("Linux System Info:");
Console.WriteLine($" Uptime: {info.uptime / 3600} hours");
Console.WriteLine($" Total RAM: {info.totalram / (1024 * 1024 * 1024)} GB");
Console.WriteLine($" Free RAM: {info.freeram / (1024 * 1024 * 1024)} GB");
Console.WriteLine($" Processes: {info.procs}");
}
else
{
Console.WriteLine($"OS: {RuntimeInformation.OSDescription}");
Console.WriteLine($"Arch: {RuntimeInformation.OSArchitecture}");
}
}
}
SystemMonitor.ShowSystemInfo();
Interop capabilities make C# uniquely powerful -- you can leverage existing native libraries, access operating system APIs, and interoperate with other technologies. The .NET platform's interop features give you the best of both managed and native worlds.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro