0Pricing
C# Academy · 课时

COM 互操作与运行时可调用包装器

使用 RCW 从 C# 调用 COM 组件,导入类型库,并处理 HRESULT 异常。

COM 互操作与运行时可调用包装器 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

什么是 COM 互操作

COM(组件对象模型)是 Microsoft 的传统二进制接口标准,目前仍用于 Office、Windows Shell、旧版 DirectX API 以及许多企业工具。.NET 可以通过互操作 wrappers 使用 COM 组件,在托管对象与 COM 接口之间进行转换。

运行时可调用包装器(RCW)

当您从 .NET 访问 COM 对象时,CLR 会创建一个运行时可调用包装器(RCW),即包装 COM 对象的托管代理。RCW 会自动处理引用计数(AddRef/Release)、单元线程和 COM 类型与 .NET 类型之间的封送。

// 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

导入类型库(TLB)

tlbimp.exe 工具(类型库导入器)会读取 COM 类型库(.tlb,或嵌入在 .dll 中),并生成 .NET 互操作程序集,其中包含强类型的 RCW 类和接口定义。

// 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

通过互操作程序集使用 COM 对象

引用互操作程序集后,COM 类型看起来就像常规的 .NET 类型。调用方法时,RCW 会将调用转换为对 COM 虚函数表的调度。请始终正确释放 COM 对象,以避免资源泄漏。

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 使用引用计数,即 AddRef/Release。RCW 只有在被垃圾回收器回收时才会调用 Release,这可能要晚得多。请调用 Marshal.ReleaseComObject(),立即减少 RCW 引用计数并释放 COM 对象,而无需等待垃圾回收。

// 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 异常

COM 方法通过返回 HRESULT 值来表示成功或失败。RCW 会自动检查 HRESULT;当它表示失败时,就会抛出 COMException(或更具体的异常)。您可以使用常规的 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}");
}

使用 dynamic 进行后期绑定

如果没有类型库或互操作程序集,您可以使用 C# 的 dynamic 关键字进行后期绑定的 COM 调度(IDispatch)。这种方式速度较慢,因为调度 ID 会在运行时解析,但不需要生成包装器。

// 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 事件接收器

COM 对象通过连接点触发事件。互操作程序集会生成事件接收器接口。您可以使用常规的 .NET 委托和事件进行订阅,RCW 会处理 COM IConnectionPoint 的底层连接。

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 单元:STA 与 MTA

许多 COM 组件(尤其是 Office 等与用户界面相关的组件)要求使用单线程单元(STA)。请始终使用 [STAThread] 标记创建 STA COM 对象的线程(Main),或者在线程启动前设置线程的单元状态。

// 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 与手动 COM 声明

您可以使用 [ComImport]、[Guid] 和 [InterfaceType] 属性手动声明 COM 接口。当不存在类型库,或您只需要大型 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);
}

快速检查

在 .NET COM 互操作中,运行时可调用包装器(RCW)的作用是什么?

回顾:COM 互操作与运行时可调用包装器

要点:

  • RCW 是包装 COM 对象的托管代理;CLR 会为每个 COM 对象自动创建一个 RCW
  • 使用 tlbimp.exe 或 VS“引用”生成强类型互操作程序集
  • 调用 Marshal.ReleaseComObject() 可立即释放 COM 对象(无需等待垃圾回收)
  • COM 错误会以带有原始 HRESULT 的 COMException 呈现
  • dynamic 支持在没有互操作程序集的情况下进行后期绑定的 COM 调度
  • Office 或用户界面 COM 组件需要 STA 线程,请使用 [STAThread] 或设置单元状态

常见问题解答

「COM 互操作与运行时可调用包装器」课时是免费的吗?

是的 — 「COM 互操作与运行时可调用包装器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「COM 互操作与运行时可调用包装器」这节课中我会学到什么?

使用 RCW 从 C# 调用 COM 组件,导入类型库,并处理 HRESULT 异常。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「COM 互操作与运行时可调用包装器」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. P/Invoke 基础
  2. LibraryImport 与源代码生成的 P/Invoke
  3. 不安全代码、指针与固定缓冲区
  4. COM 互操作与运行时可调用包装器
← 返回 C# Academy