COM 相互運用とランタイム呼び出し可能ラッパー
RCW を使って C# から COM コンポーネントを利用し、タイプライブラリをインポートして HRESULT 例外を処理します。
「COM 相互運用とランタイム呼び出し可能ラッパー」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
COM Interopとは
COM(Component Object Model)はMicrosoftのレガシーなバイナリインターフェイス標準で、現在もOffice、Windows Shell、DirectXのレガシーAPI、多くのエンタープライズツールで使用されています。.NETでは、マネージドオブジェクトとCOMインターフェイスの間を変換する相互運用ラッパーを介して、COMコンポーネントを利用できます。
Runtime Callable Wrapper(RCW)
.NETからCOMオブジェクトにアクセスすると、CLRはRuntime Callable Wrapper(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ツール(Type Library Importer)は、COMタイプライブラリ(.tlbまたは.dllに埋め込まれたもの)を読み込み、強い型付けのRCWクラスとインターフェイス定義を含む.NET相互運用アセンブリを生成します。
// 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のvtableディスパッチに変換されます。リソースリークを避けるため、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を呼び出すのはGCによって回収されたときだけで、その時期は大幅に遅くなる可能性があります。Marshal.ReleaseComObject()を呼び出すと、RCWの参照カウントを直ちに減らし、GCを待たずに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などのUI関連コンポーネント)はSingle-Threaded Apartment(STA)を必要とします。STAのCOMオブジェクトを作成するスレッドには、必ず[STAThread](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 InteropにおけるRuntime Callable Wrapper(RCW)の役割は何ですか?
振り返り:COM InteropとRuntime Callable Wrapper
主なポイント:
- RCWはCOMオブジェクトをラップするマネージドプロキシです。CLRがCOMオブジェクトごとに自動的に作成します
tlbimp.exeまたはVisual StudioのReferencesを使って、強い型付けの相互運用アセンブリを生成しますMarshal.ReleaseComObject()を呼び出すと、GCを待たずにCOMオブジェクトを直ちに解放できます- COMエラーは、元のHRESULTを含む
COMExceptionとして表面化します dynamicを使うと、相互運用アセンブリなしで遅延バインディングによるCOMディスパッチが可能になります- OfficeやUIのCOMコンポーネントにはSTAスレッドが必要です。
[STAThread]を使うか、アパートメント状態を設定します
よくある質問
「COM 相互運用とランタイム呼び出し可能ラッパー」レッスンは無料ですか?
はい。「COM 相互運用とランタイム呼び出し可能ラッパー」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「COM 相互運用とランタイム呼び出し可能ラッパー」で何を学びますか?
RCW を使って C# から COM コンポーネントを利用し、タイプライブラリをインポートして HRESULT 例外を処理します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「COM 相互運用とランタイム呼び出し可能ラッパー」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- P/Invokeの基礎
- LibraryImportとソース生成P/Invoke
- 安全でないコード、ポインター、固定バッファー
- COM 相互運用とランタイム呼び出し可能ラッパー