Kurz: Použití ComWrappers rozhraní API

V tomto kurzu se dozvíte, jak správně podtřídu typu ComWrappers poskytnout optimalizované řešení pro spolupráci s COM, které je vhodné pro AOT. Než začnete s tímto kurzem, měli byste být obeznámeni s COM, jeho architekturou a existujícími řešeními pro spolupráci s COM.

V tomto kurzu implementujete následující definice rozhraní. Tato rozhraní a jejich implementace ukazují:

  • Zařazování a odřazování typů přes hranice COM/.NET
  • Dva odlišné přístupy k využívání nativních objektů MODELU COM v .NET
  • Doporučený vzor pro povolení vlastního zprostředkovatele komunikace s objekty COM v .NET 5 a novějších verzích

Veškerý zdrojový kód použitý v tomto kurzu je k dispozici v úložišti dotnet/samples.

Poznámka:

V sadě .NET 8 SDK a novějších verzích je k dispozici generátor zdrojů, který vám automaticky vygeneruje implementaci ComWrappers rozhraní API. Další informace najdete v tématu ComWrappers generování zdroje.

Definice jazyka C#

interface IDemoGetType
{
    string? GetString();
}

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

Definice C++ pro Win32

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;
};

Přehled návrhu ComWrappers

Rozhraní ComWrappers API bylo navrženo tak, aby poskytovalo minimální interakci potřebnou k realizaci interakce s modelem COM v prostředí .NET 5+. To znamená, že mnoho vymožeností, které jsou přítomny v integrovaném systému COM, chybí a musí být vytvořeny ze základních stavebních bloků. Mezi dvě hlavní zodpovědnosti rozhraní API patří:

  • Efektivní identifikace objektů (například mapování mezi IUnknown* instancí a spravovaným objektem)
  • Interakce Garbage Collector (GC).

Těchto efektivit je dosahováno tím, že se vyžaduje vytvoření a získání obalu prostřednictvím rozhraní ComWrappers API.

ComWrappers Vzhledem k tomu, že rozhraní API má tak málo zodpovědností, znamená to, že většina práce spolupráce by měla být zpracována spotřebitelem – to je pravda. Dodatečná práce je však z velké části mechanická a lze ji provést pomocí řešení zdrojové generace. Například řetěz nástrojů C#/WinRT je řešení zdrojové generace, které je postavené na ComWrappers poskytování podpory vzájemné spolupráce WinRT.

Implementace podtřídy ComWrappers

Poskytnutí podtřídy ComWrappers znamená poskytnout modulu runtime .NET dostatek informací pro vytváření a záznam obálek pro spravované objekty, které se promítají do COM, a objekty COM, které se projektují do .NET. Než se podíváme na osnovu podtřídy, měli bychom definovat některé termíny.

Obálka spravovaného objektu – Spravované objekty .NET vyžadují obálky, které umožňují použití z non-.NET prostředí. Tyto obálky se historicky označují jako obálky COM s možností volání (CCW).

Obal nativních objektů – COM objekty implementované v non-.NET jazyce vyžadují obaly, které umožňují jejich použití z .NET. Tyto obálky se historicky nazývají obálky s možností volání modulu runtime (RCW).

Krok 1 – Definování metod pro implementaci a pochopení jejich záměru

Chcete-li rozšířit ComWrappers typ, musíte implementovat následující tři metody. Každá z těchto metod představuje účast uživatele při vytváření nebo odstraňování typu obálky. Metody ComputeVtables() a CreateObject() vytvářejí obálku spravovaného objektu a obálku nativního objektu, v uvedeném pořadí. Metoda ReleaseObjects() je používána modulem runtime k tomu, aby vytvořila požadavek na zadanou kolekci obalů, které mají být "uvolněny" ze základního nativního objektu. Ve většině případů může tělo ReleaseObjects() metody jednoduše vyvolat NotImplementedException, protože je volána pouze v pokročilém scénáři zahrnujícím architekturu Reference Tracker.

// 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();
}

Pokud chcete metodu ComputeVtables() implementovat, rozhodněte se, které spravované typy chcete podporovat. V tomto kurzu budeme podporovat dvě dříve definovaná rozhraní (IDemoGetType a IDemoStoreType) a spravovaný typ, který implementuje dvě rozhraní (DemoImpl).

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

Pro tuto metodu CreateObject() budete také muset určit, co chcete podporovat. V tomto případě ale známe pouze rozhraní COM, o která máme zájem, a nikoli třídy COM. Rozhraní používaná na straně modelu COM jsou stejná jako rozhraní, která promítáme ze strany .NET (tj. IDemoGetType a IDemoStoreType).

V tomto kurzu nebudeme implementovat ReleaseObjects() .

Krok 2 – implementace ComputeVtables()

Začněme obálkou spravovaného objektu – tyto obálky jsou jednodušší. Pro každé rozhraní vytvoříte tabulku virtuálních metod nebo tabulku virtuálních metod, abyste je mohli promítat do prostředí COM. Pro účely tohoto kurzu definujete tabulku vtable jako posloupnost ukazatelů, kde každý ukazatel představuje implementaci funkce v rozhraní – pořadí je zde velmi důležité. V modelu COM každé rozhraní dědí z IUnknown. Typ IUnknown má tři metody definované v následujícím pořadí: QueryInterface(), AddRef()a Release(). Po metodách IUnknown přicházejí specifické metody rozhraní. Například zvažte IDemoGetType a IDemoStoreType. Vtabulky pro typy by koncepčně vypadaly takto:

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

Díváme se na DemoImpl, už máme implementaci pro GetString() a StoreString(), ale co ty funkce IUnknown? Jak implementovat IUnknown instanci je nad rámec tohoto kurzu, ale lze ji provést ručně v ComWrappers. V tomto kurzu ale necháte modul runtime zpracovat tuto část. Implementaci IUnknown můžete získat pomocí ComWrappers.GetIUnknownImpl() metody.

Může to vypadat, že jste implementovali všechny metody, ale bohužel jsou v COM vtable přístupné pouze IUnknown funkce. Vzhledem k tomu, že objekt COM není součástí modulu runtime, budete muset vytvořit nativní ukazatele funkce na vaši DemoImpl implementaci. To lze provést pomocí ukazatelů funkce jazyka C# a UnmanagedCallersOnlyAttribute. Funkci, kterou lze vložit do virtuální tabulky, můžete vytvořit tak, že vytvoříte static funkci, která napodobuje podpis funkce COM. Následuje příklad podpisu COM pro IDemoGetType.GetString() – vzpomeňte si z COM ABI, že prvním argumentem je samotná instance.

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

Implementace obálky IDemoGetType.GetString() by se měla skládat z logiky předávání dat a následného předání řízení spravovanému objektu, který je obalen. Veškerý stav pro odeslání je obsažen v zadaném _this argumentu. Argument _this bude ve skutečnosti typu ComInterfaceDispatch*. Tento typ představuje strukturu nízké úrovně s jedním polem, Vtablekteré bude popsáno později. Další podrobnosti o tomto typu a jeho rozložení jsou podrobnosti implementace běhového prostředí a nesmí se na ně spoléhat. Chcete-li získat spravovanou instanci z instance ComInterfaceDispatch*, použijte následující kód:

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

Teď, když máte metodu jazyka C#, kterou lze vložit do virtuální tabulky, můžete vytvořit vtable. Všimněte si použití RuntimeHelpers.AllocateTypeAssociatedMemory() pro přidělování paměti způsobem, který funguje s nenačítitelnými sestaveními.

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;

Přidělení virtuálních tabulek je první částí implementace ComputeVtables(). Měli byste také vytvořit komplexní definice modelu COM pro typy, které plánujete podporovat – přemýšlejte o DemoImpl a o tom, jaké části by měly být použitelné z modelu COM. Pomocí vytvořených virtuálních tabulek teď můžete vytvořit řadu ComInterfaceEntry instancí, které představují kompletní zobrazení spravovaného objektu v modelu COM.

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;

Přidělení virtuálních tabulek a položek pro obálku spravovaného objektu může a mělo by být provedeno předem, protože data lze použít pro všechny instance typu. Práce zde může být provedena v konstruktoru static nebo inicializátoru modulu, ale měla by být provedena předem, takže ComputeVtables() metoda je co nejjednodušší a nejrychlejší.

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;
}

Po implementaci metody ComputeVtables() bude podtřída ComWrappers schopna vytvářet obaly pro instance spravovaného objektu DemoImpl. Mějte na paměti, že obálka spravovaného objektu vrácená z volání na GetOrCreateComInterfaceForObject() je typu IUnknown*. Pokud nativní rozhraní API, které se předává obálkě, vyžaduje jiné rozhraní, musí být provedena konverze pro toto rozhraní.

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

Krok 3 – implementace CreateObject()

Vytváření nativní obálky objektu má více možností implementace a mnohem více nuancí než vytvoření obálky spravovaného objektu. První otázka, kterou je potřeba vyřešit, je, jak permisivní bude ComWrappers podtřída při podpoře typů modelu COM. Pokud chcete podporovat všechny typy modelu COM, což je možné, budete muset napsat značné množství kódu nebo použít chytré způsoby využití Reflection.Emit. V tomto kurzu budete podporovat pouze instance COM, které implementují jak IDemoGetType, tak IDemoStoreType. Vzhledem k tomu, že víte, že existuje konečná sada a omezili jste, že každá poskytnutá instance COM musí implementovat obě rozhraní, můžete poskytnout jednu staticky definovanou obálku; avšak dynamické případy jsou v modelu COM natolik běžné, že prozkoumáme obě možnosti.

Statický obal nativního objektu

Nejprve se podíváme na statickou implementaci. Obálka statického nativního objektu zahrnuje definování spravovaného typu, který implementuje rozhraní .NET a může přesměrovávat volání spravovaného typu do instance modelu COM. Následuje hrubý obrys statické obálky.

// 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();
}

Chcete-li vytvořit instanci této třídy a poskytnout ji jako obálku, musíte definovat některé zásady. Pokud se tento typ používá jako obálka, zdá se, že protože implementuje obě rozhraní, základní instance modelu COM by měla implementovat obě rozhraní také. Protože tuto zásadu přijímáte, budete ji muset potvrdit prostřednictvím volání na Marshal.QueryInterface() instanci modelu COM.

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
};

Obal dynamického nativního objektu

Dynamické obálky jsou flexibilnější, protože poskytují způsob, jak se na typy dotazovat za běhu místo staticky. K poskytnutí této podpory použijete IDynamicInterfaceCastable. Všimněte si, že DemoNativeDynamicWrapper toto rozhraní implementuje pouze. Funkce, které rozhraní poskytuje, je šance určit, jaký typ je podporován za běhu. Zdroj pro tento návod provádí statickou kontrolu během vytváření, ale to je čistě pro účel sdílení kódu, protože kontrola může být odložena, než bude provedeno volání na 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();
}

Pojďme se podívat na jedno z rozhraní, která DemoNativeDynamicWrapper budou dynamicky podporovat. Následující kód poskytuje implementaci IDemoStoreType pomocí funkce výchozích metod rozhraní .

[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);
    }
}

V tomto příkladu je potřeba poznamenat dvě důležité věci:

  1. Atribut DynamicInterfaceCastableImplementationAttribute . Tento atribut je vyžadován u jakéhokoli typu, který je vrácen z IDynamicInterfaceCastable metody. Má přidanou výhodu, že trimmování IL je jednodušší, což znamená, že scénáře AOT jsou spolehlivější.
  2. Přetypování na DemoNativeDynamicWrapper. To je součástí dynamické povahy IDynamicInterfaceCastable. Vrácený typ z IDynamicInterfaceCastable.GetInterfaceImplementation() se používá k obálkování typu, který implementuje IDynamicInterfaceCastable. Podstata zde je, že this ukazatel není to, co předstírá, že je, protože umožňujeme případ z DemoNativeDynamicWrapper do IDemoStoreTypeNativeWrapper.

Přesměrujte volání do instance COM

Bez ohledu na to, který nativní object Wrapper se používá, potřebujete možnost vyvolat funkce v instanci modelu COM. Implementace IDemoStoreTypeNativeWrapper.StoreString() může sloužit jako příklad použití unmanaged ukazatelů funkcí jazyka 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);
    }
}

Pojďme se podívat na dereferencování instance COM pro přístup k implementaci jeho vtable. COM ABI definuje, že první ukazatel objektu vede k vtabulce typu a z ní lze získat přístup k požadovanému slotu. Předpokládejme, že adresa objektu COM je 0x10000. První hodnota velikosti ukazatele by měla být adresa virtuální tabulky – v tomto příkladu 0x20000. Jakmile budete ve virtuální tabulce, vyhledáte čtvrtý slot (index 3 v indexování založeném na nule) pro přístup k implementaci StoreString() .

COM instance
0x10000  0x20000

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

Jakmile máte ukazatel na funkci, můžete volat tuto členskou funkci na tomto objektu tím, že předáte instanci objektu jako první parametr. Tento vzor by měl vypadat dobře na základě definic funkcí implementace Managed Object Wrapper.

Po implementaci metody CreateObject() bude podtřída ComWrappers schopna vytvořit nativní obálky objektů (Native Object Wrappers) pro instance modelu COM, které implementují jak IDemoGetType, tak IDemoStoreType.

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

Krok 4 – Zpracování podrobností o životnosti obálky nativního objektu

Implementace ComputeVtables() a CreateObject() pokrývaly některé podrobnosti o životnosti obálky, ale existují další úvahy. I když to může být krátký krok, může také výrazně zvýšit složitost návrhu ComWrappers .

Na rozdíl od obálky spravovaného objektu, který je řízen voláním jeho AddRef() a Release() metod, doba života Nativní object Wrapper je nedeterministicky zpracována GC. Otázka zní, kdy nativní obálka objektu volá Release() na IntPtr, která představuje instanci COM? Existují dva obecné kontejnery:

  1. Finalizátor nativní obálky objektu je zodpovědný za volání metody Release() COM instance. Je to jediný čas, kdy je bezpečné volat tuto metodu. V tomto okamžiku bylo správně určeno GC, že v modulu runtime .NET neexistují žádné další odkazy na obálku nativního objektu. Zde může být složitost, pokud správně podporujete COM Apartments; Další informace najdete v části Další důležité informace .

  2. Nativní obálka objektu implementuje IDisposable a volá Release() v Dispose().

Poznámka:

Vzor IDisposable by měl být podporován pouze tehdy, pokud byl během CreateObject() volání předán příznak CreateObjectFlags.UniqueInstance. Pokud tento požadavek není dodržen, je možné po vyřazení znovu použít nativní obálky objektů.

Použití podtřídy ComWrappers

Teď máte podtřídu ComWrappers , kterou můžete testovat. Abyste se vyhnuli vytváření nativní knihovny, která vrací instanci COM, jež implementuje IDemoGetType a IDemoStoreType, použijete Obálku spravovaného objektu a budete s ní zacházet jako s instancí COM. To musí být možné, aby bylo možné ji předat dále jako instanci COM.

Nejprve vytvoříme obálku spravovaného objektu. Vytvořte instanci DemoImpl a zobrazte její aktuální stav řetězce.

var demo = new DemoImpl();

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

Teď můžete vytvořit instanci DemoComWrappers a obálku spravovaného objektu, kterou pak můžete předat do prostředí COM.

var cw = new DemoComWrappers();

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

Místo předání obálky spravovaného objektu do prostředí COM předstírejte, že jste právě obdrželi tuto instanci COM, a proto místo toho vytvoříte nativní obálku objektu.

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

Pomocí obálky nativního objektu byste jej měli být schopni přetypovat na jedno z požadovaných rozhraní a použít jako normální spravovaný objekt. Můžete prozkoumat DemoImpl instanci a sledovat dopad operací na obálku nativního objektu, která obaluje obálku spravovaného objektu, jež dále obaluje spravovanou instanci.

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}");

Vzhledem k tomu, že vaše ComWrapper podtřída byla navržena tak, aby podporovala CreateObjectFlags.UniqueInstance, můžete okamžitě vyčistit nativní obálku objektů namísto čekání na výskyt GC.

(rcw as IDisposable)?.Dispose();

Aktivace COM s využitím ComWrappers

Vytváření objektů MODELU COM se obvykle provádí prostřednictvím aktivace modelu COM – složitý scénář mimo rozsah tohoto dokumentu. Abychom mohli poskytnout koncepční vzor, který se má sledovat, představujeme rozhraní API CoCreateInstance(), používané k aktivaci modelu COM, a ukážeme, jak se dá použít s ComWrappers.

Předpokládejme, že máte v aplikaci následující kód jazyka C#. Následující příklad používá CoCreateInstance() k aktivaci třídy COM a systému pro spolupráci s COM k zpřístupnění instance COM do příslušného rozhraní. Všimněte si, že použití typeof(I).GUID je omezeno na assertion a jedná se o příklad použití reflexe, který může ovlivnit přívětivost kódu pro 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);

Při převodu výše uvedeného na použití ComWrappers je potřeba odebrat MarshalAs(UnmanagedType.Interface) z CoCreateInstance() P/Invoke volání a provést přenos dat ručně.

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);

Je také možné abstrahovat funkce ve stylu továrny, jako ActivateClass<I>, zahrnující aktivační logiku do konstruktoru třídy pro Nativní Objekt Obal. Konstruktor může použít ComWrappers.GetOrRegisterObjectForComInstance() rozhraní API k přidružení nově vytvořeného spravovaného objektu k aktivované instanci modelu COM.

Další důležité informace

Native AOT – Kompilace před časem (Ahead-of-time, AOT) poskytuje lepší startovací výkon, protože se vyhne kompilaci JIT. Odstranění potřeby kompilace JIT je také často vyžadováno na některých platformách. Podpora AOT byla cílem ComWrappers rozhraní API, ale jakákoli implementace obálky musí být opatrná, aby neúmyslně nezavedla případy, kdy AOT selže, například použitím reflexe. Vlastnost Type.GUID je příkladem toho, kde se reflexe používá, ale nejevným způsobem. Vlastnost Type.GUID používá reflexi ke kontrole atributů typu a poté také názvu typu a obsahujícího sestavení za účelem generování jeho hodnoty.

Generování zdrojového kódu – většina kódu, který je potřebný pro COM interop a ComWrappers implementaci, může být pravděpodobně automaticky vygenerována některými nástroji. Zdroj pro oba typy obalů lze vygenerovat, pokud jsou k dispozici správné definice COM – například Knihovna typů (TLB), IDL nebo Primární sestavení pro interoperabilitu (PIA).

Globální registrace – vzhledem k tomu, že ComWrappers rozhraní API bylo navrženo jako nová fáze zprostředkovatele komunikace modelu COM, bylo potřeba mít nějaký způsob, jak částečně integrovat se stávajícím systémem. Rozhraní API obsahuje statické metody ComWrappers, které mají globální dopad a umožňují registraci globální instance pro různé účely. Tyto metody jsou navržené pro ComWrappers instance, které očekávají komplexní podporu spolupráce modelu COM ve všech případech – je to podobné integrovanému systému komunikace modelu COM.

Podpora nástroje Reference Tracker – Tato podpora se používá především pro scénáře WinRT a představuje pokročilý scénář. U většiny ComWrapper implementací by měl buď příznak CreateComInterfaceFlags.TrackerSupport nebo CreateObjectFlags.TrackerObject vyvolat NotSupportedException. Pokud chcete tuto podporu povolit, třeba na platformě Windows nebo jiné platformě než Windows, důrazně doporučujeme odkazovat na řetěz nástrojů C#/WinRT.

Kromě trvání, systému typů a funkčních prvků, které jsou popsány dříve, vyžaduje implementace modelu COM kompatibilní s ComWrappers další zohlednění. Pro každou implementaci, která se bude používat na platformě Windows, je potřeba vzít v úvahu následující skutečnosti:

  • Apartments – organizační struktura COM pro vlákna se nazývá "Apartments" a má přísná pravidla, která je třeba dodržovat pro stabilní provoz. Tento kurz neimplementuje nativní obálky objektů s podporou apartmánu, ale jakákoli implementace použitá v produkčním prostředí by měla být s podporou apartmánu. K tomu doporučujeme použít RoGetAgileReference rozhraní API zavedené ve Windows 8. U verzí starších než Windows 8 zvažte tabulku globálního rozhraní.

  • Zabezpečení – COM poskytuje bohatý model zabezpečení pro aktivaci tříd a proxy oprávnění.