0Pricing
C# Academy · Lesson

COM Interop & Runtime Callable Wrappers

Consume COM components from C# with RCW, import type libraries, and handle HRESULT exceptions.

COM Interop & Runtime Callable Wrappers is a free C# Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is COM Interop?

COM (Component Object Model) is Microsoft's legacy binary interface standard, still used by Office, Windows Shell, DirectX legacy APIs, and many enterprise tools. .NET can consume COM components via interop wrappers that translate between managed objects and COM interfaces.

Runtime Callable Wrappers (RCW)

When you access a COM object from .NET, the CLR creates a Runtime Callable Wrapper (RCW) — a managed proxy that wraps the COM object. The RCW handles reference counting (AddRef/Release), apartment threading, and marshaling between COM and .NET types automatically.

// The RCW is created automatically when you instantiate a COM class
// via a registered ProgID or CLSID

// Example: create an Excel Application COM object
Type excelType = Type.GetTypeFromProgID("Excel.Application")!;
object excelApp = Activator.CreateInstance(excelType)!;

// excelApp is an RCW — the CLR wraps the underlying IDispatch COM object
// COM AddRef is called; CLR tracks references
Console.WriteLine(excelApp.GetType().Name); // ApplicationClass

Importing Type Libraries (TLB)

The tlbimp.exe tool (Type Library Importer) reads a COM type library (.tlb or embedded in .dll) and generates a .NET interop assembly with strongly typed RCW classes and interface definitions.

// From a Developer Command Prompt:
// tlbimp MyComLib.dll /out:MyComLib.Interop.dll

// Or reference via Visual Studio:
// Add Reference → COM → Microsoft Excel 16.0 Object Library
// → Generates Microsoft.Office.Interop.Excel.dll automatically

// The generated Interop assembly contains:
// - Interface types (matching COM vtable layout)
// - Co-class wrappers (implement the interfaces)
// - Enum types (from the type library)
// - Delegate types for COM event sinks

Using a COM Object via Interop Assembly

Once you reference the interop assembly, COM types look like regular .NET types. Calling a method translates through the RCW to a COM vtable dispatch. Always release COM objects properly to avoid resource leaks.

using Microsoft.Office.Interop.Excel;

Application excel = new Application();
excel.Visible = false;

Workbooks books = excel.Workbooks;
Workbook wb = books.Add();
Worksheet ws = (Worksheet)wb.Sheets[1];

((Range)ws.Cells[1, 1]).Value = "Hello, COM!";
wb.SaveAs(@"C:\Temp\test.xlsx");
wb.Close();

// Release COM RCW explicitly
Marshal.ReleaseComObject(ws);
Marshal.ReleaseComObject(wb);
Marshal.ReleaseComObject(books);
excel.Quit();
Marshal.ReleaseComObject(excel);

Marshal.ReleaseComObject

COM uses reference counting — AddRef/Release. The RCW calls Release only when GC-collected, which may be much later. Call Marshal.ReleaseComObject() to decrement the RCW reference count immediately and release the COM object without waiting for GC.

// Pattern: release COM objects in finally block
Application? excel = null;
Workbook? wb = null;
try
{
    excel = new Application();
    wb = excel.Workbooks.Add();
    // ... do work ...
}
finally
{
    if (wb   != null) Marshal.ReleaseComObject(wb);
    if (excel != null)
    {
        excel.Quit();
        Marshal.ReleaseComObject(excel);
    }
    // Force GC to clean up any remaining RCWs
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

HRESULT & COM Exceptions

COM methods return HRESULT values to signal success or failure. The RCW automatically checks the HRESULT and throws a COMException (or a more specific exception) when it indicates failure. You handle them with normal try/catch.

try
{
    // COM method that might fail
    Workbook wb = excel.Workbooks.Open(@"C:\missing.xlsx");
}
catch (COMException ex) when (ex.HResult == unchecked((int)0x800A03EC))
{
    // Excel-specific HRESULT for file not found
    Console.WriteLine($"Excel error: {ex.Message}");
}
catch (COMException ex)
{
    // General COM failure
    Console.WriteLine($"COM error 0x{ex.HResult:X8}: {ex.Message}");
}

Late Binding with dynamic

If you don't have a type library or interop assembly, you can use C#'s dynamic keyword for late-bound COM dispatch (IDispatch). This is slower — dispatch IDs are resolved at runtime — but requires no generated wrappers.

// Late-bound COM via dynamic — no interop DLL needed
Type type = Type.GetTypeFromProgID("Word.Application")!;
dynamic word = Activator.CreateInstance(type)!;

word.Visible = false;
dynamic docs = word.Documents;
dynamic doc = docs.Add();

doc.Content.Text = "Late-bound COM example";
doc.SaveAs2(@"C:\Temp\test.docx");
doc.Close();
word.Quit();

Marshal.ReleaseComObject(doc);
Marshal.ReleaseComObject(docs);
Marshal.ReleaseComObject(word);

COM Event Sinks

COM objects fire events through connection points. The interop assembly generates event sink interfaces. You subscribe using normal .NET delegates/events, and the RCW handles the COM IConnectionPoint plumbing.

using Microsoft.Office.Interop.Excel;

Application excel = new Application();
excel.Visible = true;

// Subscribe to COM event via generated event wrapper
excel.WorkbookBeforeClose += (wb, ref cancel) =>
{
    Console.WriteLine($"Closing: {wb.Name}");
    // Set cancel = true to prevent close
};

excel.WorkbookOpen += (wb) =>
{
    Console.WriteLine($"Opened: {wb.Name}");
};

// Open a workbook to trigger events
excel.Workbooks.Open(@"C:\Temp\test.xlsx");

COM Apartments: STA vs MTA

Many COM components (especially UI-related ones like Office) require a Single-Threaded Apartment (STA). Always mark threads that create STA COM objects with [STAThread] (Main) or set thread apartment state before starting the thread.

// Console apps default to MTA — COM Office automation requires STA
// Option 1: Mark Main with [STAThread]
[STAThread]
static void Main()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    // ...
}

// Option 2: Run on an STA thread manually
Thread staThread = new Thread(() =>
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    // ...
    excel.Quit();
    Marshal.ReleaseComObject(excel);
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();

ComImport & Manual COM Declarations

You can declare COM interfaces manually using [ComImport], [Guid], and [InterfaceType] attributes — useful when no type library exists or you only need a subset of a large COM API.

[ComImport]
[Guid("00000000-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IUnknown
{
    void QueryInterface(ref Guid riid, out IntPtr ppvObject);
    int  AddRef();
    int  Release();
}

// Declare a specific COM interface you want to consume:
[ComImport]
[Guid("0000010C-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPersist
{
    void GetClassID(out Guid pClassID);
}

Quick Check

What is the purpose of a Runtime Callable Wrapper (RCW) in .NET COM Interop?

Recap: COM Interop & Runtime Callable Wrappers

Key takeaways:

  • RCW = managed proxy wrapping a COM object; CLR creates one automatically per COM object
  • Use tlbimp.exe or VS References to generate strongly typed interop assemblies
  • Call Marshal.ReleaseComObject() to release COM objects immediately (don't wait for GC)
  • COM errors surface as COMException with the original HRESULT
  • dynamic enables late-bound COM dispatch without an interop assembly
  • Office/UI COM components require STA thread — use [STAThread] or set apartment state

Frequently asked questions

Is the “COM Interop & Runtime Callable Wrappers” lesson free?

Yes — the full text of “COM Interop & Runtime Callable Wrappers” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “COM Interop & Runtime Callable Wrappers”?

Consume COM components from C# with RCW, import type libraries, and handle HRESULT exceptions. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “COM Interop & Runtime Callable Wrappers” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. P/Invoke Fundamentals
  2. LibraryImport & Source-Generated P/Invoke
  3. Unsafe Code, Pointers & Fixed Buffers
  4. COM Interop & Runtime Callable Wrappers
← Back to C# Academy