次の方法で共有


チュートリアル: ComWrappers API を使用する

このチュートリアルでは、 ComWrappers 型を適切にサブクラス化して、最適化された AOT に優しい COM 相互運用ソリューションを提供する方法について説明します。 このチュートリアルを開始する前に、COM、そのアーキテクチャ、および既存の COM 相互運用ソリューションについて理解しておく必要があります。

このチュートリアルでは、次のインターフェイス定義を実装します。 これらのインターフェイスとその実装は、次の例を示します。

  • COM/.NET 境界を越えた型のマーシャル化とアンマーシャル化。
  • .NET でネイティブ COM オブジェクトを使用するための 2 つの異なる方法。
  • .NET 5 以降でカスタム COM 相互運用を有効にするための推奨パターン。

このチュートリアルで使用されるすべてのソース コードは、 dotnet/samples リポジトリで入手できます。

.NET 8 SDK 以降のバージョンでは、 ComWrappers API 実装を自動的に生成するためのソース ジェネレーターが提供されています。 詳細については、ソース生成ComWrappers参照してください。

C# の定義

interface IDemoGetType
{
    string? GetString();
}

interface IDemoStoreType
{
    void StoreString(int len, string? str);
}

Win32 C++ 定義

MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
IDemoGetType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE GetString(_Outptr_ wchar_t** str) = 0;
};

MIDL_INTERFACE("30619FEA-E995-41EA-8C8B-9A610D32ADCB")
IDemoStoreType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE StoreString(int len, _In_z_ const wchar_t* str) = 0;
};

ComWrappers設計の概要

ComWrappers API は、.NET 5 以降のランタイムとの COM 相互運用を実現するために必要な最小限の操作を提供するように設計されています。 つまり、組み込みの COM 相互運用システムに存在する優れた機能の多くは存在せず、基本的な構成要素から構築する必要があります。 API の主な役割は次の 2 つあります。

  • 効率的なオブジェクト識別 (たとえば、 IUnknown* インスタンスとマネージド オブジェクトの間のマッピング)。
  • ガベージ コレクター (GC) の対話。

これらの効率は、 ComWrappers API を経由するためにラッパーの作成と取得を要求することで実現されます。

ComWrappers API には責任が非常に少ないため、相互運用作業の大部分をコンシューマーが処理する必要があると考えるのが現状です。これは事実です。 ただし、追加作業は主に機械的であり、ソース生成ソリューションによって実行できます。 たとえば、 C#/WinRT ツール チェーンは、WinRT 相互運用機能のサポートを提供するために ComWrappers 上に構築されたソース生成ソリューションです。

ComWrappers サブクラスを実装する

ComWrappers サブクラスを指定すると、.NET ランタイムに十分な情報を提供して、COM オブジェクトと .NET に投影される COM オブジェクトに投影されるマネージド オブジェクトのラッパーを作成および記録できます。 サブクラスの概要を見る前に、いくつかの用語を定義する必要があります。

マネージド オブジェクト ラッパー – マネージド .NET オブジェクトには、non-.NET 環境からの使用を有効にするためにラッパーが必要です。 これらのラッパーは、従来、COM 呼び出し可能ラッパー (CCW) と呼ばれます。

ネイティブ オブジェクト ラッパー – non-.NET 言語で実装される COM オブジェクトでは、.NET からの使用を有効にするためにラッパーが必要です。 これらのラッパーは、従来はランタイム呼び出し可能ラッパー (RCW) と呼ばれます。

手順 1 – その意図を実装して理解するメソッドを定義する

ComWrappers型を拡張するには、次の 3 つのメソッドを実装する必要があります。 これらの各メソッドは、ラッパーの種類の作成または削除へのユーザーの参加を表します。 ComputeVtables()メソッドとCreateObject() メソッドは、それぞれマネージド オブジェクト ラッパーとネイティブ オブジェクト ラッパーを作成します。 ReleaseObjects() メソッドは、基になるネイティブ オブジェクトから指定されたラッパーのコレクションを "解放" するように要求するためにランタイムによって使用されます。 ほとんどの場合、ReleaseObjects() メソッドの本体は、NotImplementedExceptionを使用する高度なシナリオでのみ呼び出されるため、単にをスローするだけで十分です。

// See referenced sample for implementation.
class DemoComWrappers : ComWrappers
{
    protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) =>
        throw new NotImplementedException();

    protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags) =>
        throw new NotImplementedException();

    protected override void ReleaseObjects(IEnumerable objects) =>
        throw new NotImplementedException();
}

ComputeVtables() メソッドを実装するには、サポートするマネージド型を決定します。 このチュートリアルでは、以前に定義した 2 つのインターフェイス (IDemoGetTypeIDemoStoreType) と、2 つのインターフェイス (DemoImpl) を実装するマネージド型をサポートします。

class DemoImpl : IDemoGetType, IDemoStoreType
{
    string? _string;
    public string? GetString() => _string;
    public void StoreString(int _, string? str) => _string = str;
}

CreateObject()メソッドの場合は、サポートする内容を決定する必要もあります。 ただし、この場合は、COM クラスではなく、関心のある COM インターフェイスのみが認識されます。 COM 側から使用されるインターフェイスは、.NET 側から投影するインターフェイス (つまり、 IDemoGetTypeIDemoStoreType) と同じです。

このチュートリアルでは、 ReleaseObjects() を実装しません。

手順 2 – 実装 ComputeVtables()

マネージド オブジェクト ラッパーから始めましょう。これらのラッパーの方が簡単です。 COM 環境に投影するために、インターフェイスごとに 仮想メソッド テーブル ( vtable) を作成します。 このチュートリアルでは、vtable をポインターのシーケンスとして定義します。ここでは、各ポインターがインターフェイス上の関数の実装を表します。順序は 非常に 重要です。 COM では、すべてのインターフェイスが IUnknownから継承されます。 IUnknown型には、QueryInterface()AddRef()Release()の 3 つのメソッドが定義されています。 IUnknownメソッドの後に、特定のインターフェイス メソッドが提供されます。 たとえば、 IDemoGetTypeIDemoStoreTypeについて考えてみましょう。 概念的には、型の vtable は次のようになります。

IDemoGetType    | IDemoStoreType
==================================
QueryInterface  | QueryInterface
AddRef          | AddRef
Release         | Release
GetString       | StoreString

DemoImplを見ると、既にGetString()StoreString()の実装がありますが、IUnknown関数はどうでしょうか。 IUnknown インスタンスを実装する方法は、このチュートリアルの範囲外ですが、ComWrappersで手動で行うことができます。 ただし、このチュートリアルでは、ランタイムがその部分を処理できるようにします。 IUnknown メソッドを使用して、ComWrappers.GetIUnknownImpl()実装を取得できます。

すべてのメソッドを実装したように思えるかもしれませんが、残念ながら、COM vtable では IUnknown 関数のみが使用できます。 COM はランタイムの外部にないため、 DemoImpl 実装へのネイティブ関数ポインターを作成する必要があります。 これは、C# 関数ポインターと UnmanagedCallersOnlyAttributeを使用して行うことができます。 COM 関数シグネチャを模倣する static 関数を作成することで、vtable に挿入する関数を作成できます。 次に、 IDemoGetType.GetString() の COM シグネチャの例を示します。最初の引数がインスタンス自体であることを COM ABI から思い出してください。

[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);

IDemoGetType.GetString()のラッパー実装は、マーシャリング ロジックと、ラップされるマネージド オブジェクトへのディスパッチで構成されている必要があります。 ディスパッチのすべての状態は、指定された _this 引数内に含まれています。 _this引数は実際にはComInterfaceDispatch*型になります。 この型は、後で説明する 1 つのフィールド ( Vtable) を持つ下位レベルの構造体を表します。 この型とそのレイアウトの詳細は、ランタイムの実装の詳細であり、依存しないでください。 ComInterfaceDispatch* インスタンスからマネージド インスタンスを取得するには、次のコードを使用します。

IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);

vtable に挿入できる C# メソッドが作成されたので、vtable を構築できます。 RuntimeHelpers.AllocateTypeAssociatedMemory()アセンブリで動作する方法でメモリを割り当てるの使用に注意してください。

GetIUnknownImpl(
    out IntPtr fpQueryInterface,
    out IntPtr fpAddRef,
    out IntPtr fpRelease);

// Local variables with increment act as a guard against incorrect construction of
// the native vtable. It also enables a quick validation of final size.
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&ABI.IDemoGetTypeManagedWrapper.GetString;
Debug.Assert(tableCount == idx);
s_IDemoGetTypeVTable = (IntPtr)vtable;

vtable の割り当ては、 ComputeVtables()の実装の最初の部分です。 また、サポートする予定の型に対して包括的な COM 定義を作成する必要があります。 DemoImpl 考え、その一部を COM から使用できるようにする必要があります。 構築された vtable を使用して、COM のマネージド オブジェクトの完全なビューを表す一連の ComInterfaceEntry インスタンスを作成できるようになりました。

s_DemoImplDefinitionLen = 2;
int idx = 0;
var entries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    sizeof(ComInterfaceEntry) * s_DemoImplDefinitionLen);
entries[idx].IID = IDemoGetType.IID_IDemoGetType;
entries[idx++].Vtable = s_IDemoGetTypeVTable;
entries[idx].IID = IDemoStoreType.IID_IDemoStoreType;
entries[idx++].Vtable = s_IDemoStoreVTable;
Debug.Assert(s_DemoImplDefinitionLen == idx);
s_DemoImplDefinition = entries;

マネージド オブジェクト ラッパーの vtable とエントリの割り当ては、型のすべてのインスタンスでデータを使用できるため、事前に行うことができます。 ここでの作業は、 static コンストラクターまたはモジュール初期化子で実行できますが、 ComputeVtables() メソッドができるだけ簡単かつ迅速になるように、事前に行う必要があります。

protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags,
out int count)
{
    if (obj is DemoImpl)
    {
        count = s_DemoImplDefinitionLen;
        return s_DemoImplDefinition;
    }

    // Unknown type
    count = 0;
    return null;
}

ComputeVtables() メソッドを実装すると、ComWrappers サブクラスは、DemoImplのインスタンスのマネージド オブジェクト ラッパーを生成できるようになります。 GetOrCreateComInterfaceForObject()の呼び出しから返されるマネージド オブジェクト ラッパーはIUnknown*型であることに注意してください。 ラッパーに渡されるネイティブ API に別のインターフェイスが必要な場合は、そのインターフェイスの Marshal.QueryInterface() を実行する必要があります。

var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

手順 3 – 実装 CreateObject()

ネイティブ オブジェクト ラッパーの構築には、マネージド オブジェクト ラッパーを構築するよりも実装オプションが多く、非常に微妙です。 対処すべき最初の質問は、 ComWrappers サブクラスが COM 型をサポートする方法を制限することです。 可能なすべての COM 型をサポートするには、大量のコードを記述するか、 Reflection.Emitの巧妙な使用方法を採用する必要があります。 このチュートリアルでは、 IDemoGetTypeIDemoStoreTypeの両方を実装する COM インスタンスのみをサポートします。 有限のセットがあり、指定された COM インスタンスが両方のインターフェイスを実装する必要があることを制限しているため、静的に定義された単一のラッパーを提供できます。ただし、動的なケースは COM で十分に一般的であり、両方のオプションについて説明します。

静的ネイティブ オブジェクト ラッパー

まず静的実装を見てみましょう。 静的ネイティブ オブジェクト ラッパーには、.NET インターフェイスを実装し、マネージド型の呼び出しを COM インスタンスに転送できるマネージド型の定義が含まれます。 静的ラッパーの大まかな概要を次に示します。

// See referenced sample for implementation.
class DemoNativeStaticWrapper
    : IDemoGetType
    , IDemoStoreType
{
    public string? GetString() =>
        throw new NotImplementedException();

    public void StoreString(int len, string? str) =>
        throw new NotImplementedException();
}

このクラスのインスタンスを構築し、ラッパーとして提供するには、いくつかのポリシーを定義する必要があります。 この型がラッパーとして使用されている場合、両方のインターフェイスが実装されているため、基になる COM インスタンスも両方のインターフェイスを実装する必要があると思われます。 このポリシーを採用していることを考えると、COM インスタンスで Marshal.QueryInterface() を呼び出してこれを確認する必要があります。

int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != 0)
{
    return null;
}

hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != 0)
{
    Marshal.Release(IDemoGetTypeInst);
    return null;
}

return new DemoNativeStaticWrapper()
{
    IDemoGetTypeInst = IDemoGetTypeInst,
    IDemoStoreTypeInst = IDemoStoreTypeInst
};

動的ネイティブ オブジェクト ラッパー

動的ラッパーは、静的ではなく実行時に型を照会する方法を提供するため、柔軟性が高くなります。 このサポートを提供するには、 IDynamicInterfaceCastableを使用します。 DemoNativeDynamicWrapperがこのインターフェイスのみを実装していることを確認します。 インターフェイスが提供する機能は、実行時にサポートされる型を決定する機会です。 このチュートリアルのソースでは、作成時に静的チェックを実行しますが、これは単にコード共有用です。このチェックは、 DemoNativeDynamicWrapper.IsInterfaceImplemented()への呼び出しが行われるまで遅延される可能性があるためです。

// See referenced sample for implementation.
internal class DemoNativeDynamicWrapper
    : IDynamicInterfaceCastable
{
    public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) =>
        throw new NotImplementedException();

    public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) =>
        throw new NotImplementedException();
}

DemoNativeDynamicWrapperが動的にサポートするインターフェイスの 1 つを見てみましょう。 次のコードは、IDemoStoreType機能を使用したの実装を提供します。

[DynamicInterfaceCastableImplementation]
unsafe interface IDemoStoreTypeNativeWrapper : IDemoStoreType
{
    public static void StoreString(IntPtr inst, int len, string? str);

    void IDemoStoreType.StoreString(int len, string? str)
    {
        var inst = ((DemoNativeDynamicWrapper)this).IDemoStoreTypeInst;
        StoreString(inst, len, str);
    }
}

この例では、次の 2 つの重要な点に注意してください。

  1. DynamicInterfaceCastableImplementationAttribute属性。 この属性は、 IDynamicInterfaceCastable メソッドから返されるすべての型で必要です。 IL トリミングを容易にする利点が追加されています。つまり、AOT シナリオの信頼性が高くなります。
  2. DemoNativeDynamicWrapperへのキャスト。 これは、 IDynamicInterfaceCastableの動的な性質の一部です。 IDynamicInterfaceCastable.GetInterfaceImplementation()から返される型は、IDynamicInterfaceCastableを実装する型を "ブランケット" するために使用されます。 ここでの要点は、thisからDemoNativeDynamicWrapperへのケースを許可しているため、IDemoStoreTypeNativeWrapperポインターがふりをしているわけではありません。

COM インスタンスへの呼び出しの転送

使用されているネイティブ オブジェクト ラッパーに関係なく、COM インスタンスで関数を呼び出す機能が必要です。 IDemoStoreTypeNativeWrapper.StoreString()の実装は、unmanaged C# 関数ポインターを使用する例として機能します。

public static void StoreString(IntPtr inst, int len, string? str)
{
    IntPtr strLocal = Marshal.StringToCoTaskMemUni(str);
    int hr = ((delegate* unmanaged<IntPtr, int, IntPtr, int>)(*(*(void***)inst + 3 /* IDemoStoreType.StoreString slot */)))(inst, len, strLocal);
    if (hr != 0)
    {
        Marshal.FreeCoTaskMem(strLocal);
        Marshal.ThrowExceptionForHR(hr);
    }
}

COM インスタンスの逆参照を調べて、その vtable 実装にアクセスしてみましょう。 COM ABI は、オブジェクトの最初のポインターが型の vtable へのポインターであり、そこから目的のスロットにアクセスできることを定義します。 COM オブジェクトのアドレスが 0x10000されているとします。 最初のポインター サイズの値は vtable のアドレスである必要があります。この例では 0x20000。 vtable にアクセスしたら、4 番目のスロット (0 から始まるインデックス作成のインデックス 3) を探して、 StoreString() 実装にアクセスします。

COM instance
0x10000  0x20000

VTable for IDemoStoreType
0x20000  <Address of QueryInterface>
0x20008  <Address of AddRef>
0x20010  <Address of Release>
0x20018  <Address of StoreString>

関数ポインターを使用すると、オブジェクト インスタンスを最初のパラメーターとして渡すことによって、そのオブジェクトのメンバー関数にディスパッチできます。 このパターンは、マネージド オブジェクト ラッパー実装の関数定義に基づいて、使い慣れているはずです。

CreateObject() メソッドが実装されると、ComWrappers サブクラスは、IDemoGetTypeIDemoStoreTypeの両方を実装する COM インスタンスのネイティブ オブジェクト ラッパーを生成できるようになります。

IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);

手順 4 – ネイティブ オブジェクト ラッパーの有効期間の詳細を処理する

ComputeVtables()CreateObject()の実装では、ラッパーの有効期間の詳細について説明しましたが、その他の考慮事項もあります。 これは短い手順でもかまいませんが、 ComWrappers 設計の複雑さを大幅に増やすこともできます。

AddRef()メソッドとRelease()メソッドの呼び出しによって制御されるマネージド オブジェクト ラッパーとは異なり、ネイティブ オブジェクト ラッパーの有効期間は GC によって非決定的に処理されます。 ここでの問題は、Native Object Wrapper が、COM インスタンスを表すRelease()IntPtrを呼び出すタイミングはいつかですか。 一般的なバケットは 2 つあります。

  1. ネイティブ オブジェクト ラッパーのファイナライザーは、COM インスタンスの Release() メソッドを呼び出す役割を担います。 これは、このメソッドを安全に呼び出す唯一の時間です。 この時点で、GC によって、.NET ランタイムにネイティブ オブジェクト ラッパーへの他の参照がないことを正しく判断しました。 COM アパートメントを適切にサポートしている場合は、複雑になる可能性があります。詳細については、「 その他の考慮事項 」セクションを参照してください。

  2. Native Object Wrapper は、IDisposableRelease()を実装し、Dispose()を呼び出します。

IDisposable パターンは、CreateObject()呼び出し中にCreateObjectFlags.UniqueInstance フラグが渡された場合にのみサポートする必要があります。 この要件に従わない場合は、破棄されたネイティブ オブジェクト ラッパーを破棄後に再利用できます。

ComWrappers サブクラスの使用

これで、テストできる ComWrappers サブクラスが作成されました。 IDemoGetTypeIDemoStoreTypeを実装する COM インスタンスを返すネイティブ ライブラリを作成しないようにするには、マネージド オブジェクト ラッパーを使用して COM インスタンスとして扱います。これは、COM を渡すために可能である必要があります。

まず、マネージド オブジェクト ラッパーを作成しましょう。 DemoImpl インスタンスをインスタンス化し、現在の文字列状態を表示します。

var demo = new DemoImpl();

string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");

これで、 DemoComWrappers のインスタンスとマネージド オブジェクト ラッパーを作成し、COM 環境に渡すことができます。

var cw = new DemoComWrappers();

IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

マネージド オブジェクト ラッパーを COM 環境に渡す代わりに、この COM インスタンスを受け取ったふりをして、代わりにネイティブ オブジェクト ラッパーを作成します。

var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);

Native Object Wrapper を使用すると、目的のインターフェイスのいずれかにキャストし、通常のマネージド オブジェクトとして使用できます。 DemoImpl インスタンスを調べ、管理インスタンスをラップしているマネージド オブジェクト ラッパーをさらにラップしているネイティブ オブジェクト ラッパーへの操作の影響を確認できます。

var getter = (IDemoGetType)rcw;
var store = (IDemoStoreType)rcw;

string msg = "hello world!";
store.StoreString(msg.Length, msg);
Console.WriteLine($"Setting string through wrapper: {msg}");

value = demo.GetString();
Console.WriteLine($"Get string through managed object: {value}");

msg = msg.ToUpper();
demo.StoreString(msg.Length, msg.ToUpper());
Console.WriteLine($"Setting string through managed object: {msg}");

value = getter.GetString();
Console.WriteLine($"Get string through wrapper: {value}");

ComWrapper サブクラスはCreateObjectFlags.UniqueInstanceをサポートするように設計されているため、GC が発生するのを待たずに、ネイティブ オブジェクト ラッパーをすぐにクリーンアップできます。

(rcw as IDisposable)?.Dispose();

COM アクティベーション ComWrappers

COM オブジェクトの作成は、通常、このドキュメントの範囲外の複雑なシナリオである COM ライセンス認証を使用して実行されます。 従う概念パターンを提供するために、COM のアクティブ化に使用される CoCreateInstance() API を紹介し、 ComWrappersで使用する方法を示します。

アプリケーションに次の C# コードがあるとします。 次の例では、 CoCreateInstance() を使用して COM クラスと組み込みの COM 相互運用システムをアクティブ化し、COM インスタンスを適切なインターフェイスにマーシャリングします。 typeof(I).GUIDの使用はアサートに限定されており、コードが AOT に優しい場合に影響を与える可能性があるリフレクションを使用する場合に注意してください。

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out object obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)obj;
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    [MarshalAs(UnmanagedType.Interface)] out object ppObj);

ComWrappersを使用するように上記を変換するには、MarshalAs(UnmanagedType.Interface) P/Invoke からCoCreateInstance()を削除し、マーシャリングを手動で実行する必要があります。

static ComWrappers s_ComWrappers = ...;

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out IntPtr obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)s_ComWrappers.GetOrCreateObjectForComInstance(obj, CreateObjectFlags.None);
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    out IntPtr ppObj);

ネイティブ オブジェクト ラッパーのクラス コンストラクターにアクティブ化ロジックを含めることで、 ActivateClass<I> などのファクトリ スタイルの関数を抽象化することもできます。 コンストラクターは、 ComWrappers.GetOrRegisterObjectForComInstance() API を使用して、新しく構築されたマネージド オブジェクトをアクティブ化された COM インスタンスに関連付けることができます。

その他の考慮事項

ネイティブ AOT – 事前コンパイル (AOT) を使用すると、JIT コンパイルが回避されるため、スタートアップ コストが向上します。 一部のプラットフォームでは、JIT コンパイルの必要性を取り除くことが必要になることがよくあります。 AOT のサポートは ComWrappers API の目標でしたが、ラッパーの実装では、リフレクションの使用など、AOT が不注意で中断されるケースを誤って導入しないように注意する必要があります。 Type.GUID プロパティは、リフレクションを使用する一例ですが、明らかではありません。 Type.GUIDプロパティは、リフレクションを使用して型の属性を検査し、必要に応じて型の名前や含まれるアセンブリを調べ、その値を生成します。

ソース生成 – COM 相互運用と ComWrappers 実装に必要なほとんどのコードは、一部のツールによって自動生成される可能性があります。 適切な COM 定義 (タイプ ライブラリ (TLB)、IDL、プライマリ相互運用機能アセンブリ (PIA) など) を使用して、両方のタイプのラッパーのソースを生成できます。

グローバル登録ComWrappers API は COM 相互運用の新しいフェーズとして設計されているため、既存のシステムと部分的に統合する方法が必要でした。 ComWrappers API には、さまざまなサポートのためのグローバル インスタンスの登録を許可する、グローバルに影響を与える静的メソッドがあります。 これらのメソッドは、組み込みの COM 相互運用システムと同じ、すべてのケースで包括的な COM 相互運用機能のサポートを提供することを期待している ComWrappers インスタンス向けに設計されています。

リファレンス トラッカーのサポート – このサポートは、WinRT シナリオで主に使用され、高度なシナリオを表します。 ほとんどの ComWrapper 実装では、 CreateComInterfaceFlags.TrackerSupport または CreateObjectFlags.TrackerObject フラグが NotSupportedExceptionをスローする必要があります。 Windows または Windows 以外のプラットフォームでもこのサポートを有効にする場合は、 C#/WinRT ツール チェーンを参照することを強くお勧めします。

前に説明した有効期間、型システム、および機能機能とは別に、com に準拠した ComWrappers の実装には、追加の考慮事項が必要です。 Windows プラットフォームで使用される実装には、次の考慮事項があります。

  • アパートメント – スレッド処理のための COM の組織構造は "アパートメント" と呼ばれ、安定した運用のために従う必要がある厳格な規則があります。 このチュートリアルでは、アパートメント対応のネイティブ オブジェクト ラッパーは実装しませんが、運用環境に対応した実装はアパートメント対応にする必要があります。 これを実現するには、Windows 8 で導入された RoGetAgileReference API を使用することをお勧めします。 Windows 8 より前のバージョンでは、 グローバル インターフェイス テーブルを検討してください。

  • セキュリティ – COM は、クラスのアクティブ化とプロキシアクセス許可のための豊富なセキュリティ モデルを提供します。