Megosztás a következőn keresztül:


Oktatóanyag: Az ComWrappers API használata

Ebben az oktatóanyagban megtanulhatja, hogyan oszthatja be megfelelően a ComWrappers típust, hogy optimalizált és AOT-barát COM interop megoldást biztosítson. Az oktatóanyag megkezdése előtt ismernie kell a COM-t, annak architektúráját és a meglévő COM interop-megoldásokat.

Ebben az oktatóanyagban a következő felületdefiníciókat fogja implementálni. Ezek az interfészek és azok implementációi a következőkről lesznek tanúi:

  • Rendezési és leválasztási típusok a COM/.NET határán.
  • A natív COM-objektumok .NET-ben való felhasználásának két különböző megközelítése.
  • Ajánlott minta az egyéni COM-együttműködés engedélyezéséhez a .NET 5-ben és azon túl.

Az oktatóanyagban használt összes forráskód elérhető a dotnet/samples adattárban.

Feljegyzés

A .NET 8 SDK és újabb verziókban forrásgenerátort biztosítunk, amely automatikusan létrehoz egy API-implementációt ComWrappers . További információ: ComWrappers forráslétrehozás.

C#-definíciók

interface IDemoGetType
{
    string? GetString();
}

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

Win32 C++ definíciók

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 A terv áttekintése

Az ComWrappers API úgy lett kialakítva, hogy minimális interakciót biztosítson a COM-együttműködéshez a .NET 5+ futtatókörnyezettel. Ez azt jelenti, hogy a beépített COM interop rendszer számos szépsége nincs jelen, és alapszintű építőelemekből kell felépíteni. Az API két elsődleges feladata:

  • Hatékony objektumazonosítás (például egy példány és egy IUnknown* felügyelt objektum közötti leképezés).
  • Szemétgyűjtő (GC) interakciója.

Ezeket a hatékonysági feltételeket úgy érheti el, hogy burkoló létrehozását és beszerzését az API-nak kell elvégeznie ComWrappers .

Mivel az ComWrappers API-nak ilyen kevés felelőssége van, indokolt, hogy az interop-munka nagy részét a fogyasztónak kell kezelnie – ez igaz. A további munka azonban nagyrészt mechanikus, és egy forrásgenerációs megoldással is elvégezhető. A C#/WinRT eszközlánc például egy forrásgenerációs megoldás, amely a WinRT interop-támogatásának ComWrappers biztosítására épül.

ComWrappers Alosztály implementálása

ComWrappers Az alosztály megadása azt jelenti, hogy elegendő információt ad a .NET-futtatókörnyezetnek ahhoz, hogy burkolókat hozzon létre és rögzítsen a COM-ba és COM-objektumokba kivetített felügyelt objektumok burkolóinak létrehozásához és rögzítéséhez. Mielőtt áttekintenénk az alosztály vázlatát, meg kell határoznunk néhány kifejezést.

Felügyelt objektumburkoló – A felügyelt .NET-objektumok használatához burkolókra van szükség a non-.NET környezet használatának engedélyezéséhez. Ezeket a burkolókat korábban COM Callable Burkolónak (CCW) nevezik.

Natív objektumburkoló – A non-.NET nyelven implementált COM-objektumok használatához a burkolóknak engedélyezni kell a használatot a .NET-ből. Ezeket a burkolókat korábbi nevén Runtime Callable Burkolónak (RCW) nevezik.

1. lépés – A szándékuk megvalósítására és megértésére szolgáló módszerek meghatározása

A ComWrappers típus kiterjesztéséhez a következő három módszert kell implementálnia. Ezen módszerek mindegyike azt jelzi, hogy a felhasználó résztvesz-e egy burkolótípus létrehozásában vagy törlésében. A ComputeVtables() metódusok és CreateObject() a metódusok létrehoznak egy felügyelt objektumburkolót és egy natív objektumburkolót. A ReleaseObjects() futtatókörnyezet a megadott burkológyűjtemény "felszabadítását" kéri a mögöttes natív objektumból. A legtöbb esetben a ReleaseObjects() metódus törzse egyszerűen eldobhatóNotImplementedException, mivel ez csak a referenciakövető keretrendszert érintő speciális forgatókönyvben van meghívva.

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

A ComputeVtables() metódus implementálásához döntse el, hogy mely felügyelt típusokat szeretné támogatni. Ebben az oktatóanyagban támogatjuk a két korábban definiált felületet (IDemoGetType és ) és IDemoStoreTypeegy felügyelt típust, amely implementálja a két interfészt (DemoImpl).

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

CreateObject() A metódushoz azt is meg kell határoznia, hogy mit szeretne támogatni. Ebben az esetben azonban csak azokat a COM-felületeket tudjuk, amelyek érdekelnek minket, nem pedig a COM-osztályokat. A COM-oldalról felhasznált felületek megegyeznek a .NET oldalról (vagyis IDemoGetTypeIDemoStoreTypea ).

Ebben az oktatóanyagban nem implementáljuk ReleaseObjects() .

2. lépés – Implementálás ComputeVtables()

Kezdjük a felügyelt objektumburkolóval – ezek a burkolók egyszerűbbek. Az egyes felületekhez virtuálismetódus-táblázatot vagy virtuális táblát fog létrehozni, hogy azokat a COM-környezetbe kivetítse. Ebben az oktatóanyagban egy vtable-t fog mutatósorozatként definiálni, ahol minden mutató egy függvény implementációját jelöli egy felületen – itt nagyon fontos a sorrend. A COM-ban minden felület a következőtől IUnknownöröklődik: . A IUnknown típus három metódust határoz meg a következő sorrendben: QueryInterface(), AddRef()és Release(). Miután a IUnknown metódusok jöttek az adott felületi metódusok. Vegyük például a megfontolandó IDemoGetType és a IDemoStoreType. Elméletileg a típusok virtuális táblái a következőképpen néznek ki:

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

Tekintve, DemoImplhogy már van implementációnk GetString() , és StoreString(), de mi a helyzet a IUnknown függvényekkel? A IUnknown példányok implementálása meghaladja az oktatóanyag hatókörét, de manuálisan is elvégezhető.ComWrappers Ebben az oktatóanyagban azonban hagyja, hogy a futtatókörnyezet kezelje ezt a részt. A implementációt a IUnknownComWrappers.GetIUnknownImpl() metódussal szerezheti be.

Úgy tűnhet, hogy az összes metódust implementálta, de sajnos csak a IUnknown függvények használhatók a COM virtuális táblában. Mivel a COM kívül esik a futtatókörnyezeten, natív függvénymutatókat kell létrehoznia a DemoImpl megvalósításhoz. Ez c# függvénymutatókkal és a UnmanagedCallersOnlyAttribute. A com-függvény aláírását utánzó függvény létrehozásával létrehozhat egy static függvényt, amely beszúrható a virtuális táblába. Az alábbiakban egy példa látható a COM-aláírásra IDemoGetType.GetString() – a COM ABI-ból való visszahívásra, hogy az első argumentum maga a példány.

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

A burkoló implementációjának IDemoGetType.GetString() a rendezési logikából, majd a burkolt felügyelt objektumra való küldésből kell állnia. A küldés összes állapota a megadott _this argumentumban található. Az _this argumentum valójában típus ComInterfaceDispatch*. Ez a típus egy alacsony szintű struktúrát jelöl egyetlen mezővel, Vtableamelyet később tárgyalunk. Ennek a típusnak és elrendezésének további részletei a futtatókörnyezet implementálási részletei, és nem szabad függenie. A felügyelt példány példányból ComInterfaceDispatch* való lekéréséhez használja a következő kódot:

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

Most, hogy már rendelkezik egy C# metódussal, amely beszúrható egy virtuális táblába, létrehozhatja a virtuális táblát. Figyelje meg, hogy a memória kiosztásához olyan módon kell használniRuntimeHelpers.AllocateTypeAssociatedMemory(), amely a kiüríthető szerelvényekkel működik.

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;

A virtuálistáblák kiosztása a végrehajtás ComputeVtables()első része. Átfogó COM-definíciókat is létre kell készítenie olyan típusokhoz, amelyeket támogatni szeretne – gondolja át DemoImpl , hogy milyen részei legyenek használhatóak a COM-ból. A létrehozott virtuálistáblák használatával mostantól létrehozhat egy példánysorozatot ComInterfaceEntry , amely a COM-ban a felügyelt objektum teljes nézetét képviseli.

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;

A felügyelt objektumburkoló virtuális tábláinak és bejegyzéseinek lefoglalása előre elvégezhető, mivel az adatok a típus összes példányához felhasználhatók. Az itt végzett munka konstruktorban vagy modul inicializálóban static is elvégezhető, de előre el kell végezni, hogy a ComputeVtables() módszer a lehető legegyszerűbb és leggyorsabb legyen.

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

A metódus implementálása ComputeVtables() után az ComWrappers alosztály képes lesz felügyelt objektumburkolókat létrehozni a példányokhoz DemoImpl. Vegye figyelembe, hogy a hívásból GetOrCreateComInterfaceForObject() visszaadott felügyelt objektumburkoló típusa típus IUnknown*. Ha a burkolónak átadott natív API-nak más interfészre van szüksége, akkor ehhez a felülethez el kell végezni egy Marshal.QueryInterface() műveletet.

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

3. lépés – Implementálás CreateObject()

A natív objektumburkolók létrehozása több megvalósítási lehetőséggel és sokkal árnyaltabb megoldásokkal rendelkezik, mint egy felügyelt objektumburkoló létrehozása. Az első kérdés az, hogy az alosztály mennyire lesz megengedő a ComWrappers COM-típusok támogatásában. Az összes COM-típus támogatásához, ami lehetséges, jelentős mennyiségű kódot kell írnia, vagy okosan kell használnia a Reflection.Emit. Ebben az oktatóanyagban csak azokat a COM-példányokat fogja támogatni, amelyek mindkettőt IDemoGetType és IDemoStoreType. Mivel tudja, hogy van véges készlet, és korlátozva van, hogy minden megadott COM-példánynak mindkét felületet implementálnia kell, egyetlen, statikusan definiált burkolót is megadhat; azonban a dinamikus esetek elég gyakoriak a COM-ban, hogy mindkét lehetőséget megvizsgáljuk.

Statikus natív objektumburkoló

Először nézzük meg a statikus implementációt. A statikus natív objektumburkoló magában foglalja egy felügyelt típus meghatározását, amely implementálja a .NET-interfészeket, és képes továbbítani a felügyelt típus hívásait a COM-példánynak. A statikus burkoló durva körvonala következik.

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

Az osztály egy példányának létrehozásához és burkolóként való biztosításához meg kell határoznia néhány szabályzatot. Ha ezt a típust burkolóként használják, úgy tűnik, hogy mivel mindkét felületet implementálja, a mögöttes COM-példánynak mindkét felületet is implementálnia kell. Mivel ezt a szabályzatot alkalmazza, ezt a COM-példányra Marshal.QueryInterface() irányuló hívásokon keresztül kell megerősítenie.

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

Dinamikus natív objektumburkoló

A dinamikus burkoló rugalmasabb, mert lehetővé teszik a típusok lekérését futásidőben a statikus helyett. Ennek a támogatásnak a biztosításához hasznosíthatja IDynamicInterfaceCastable a további részleteket itt. Figyelje meg, hogy DemoNativeDynamicWrapper csak ezt a felületet implementálja. Az interfész által biztosított funkciók lehetőséget biztosítanak annak meghatározására, hogy a futtatáskor milyen típus támogatott. Az oktatóanyag forrása statikus ellenőrzést végez a létrehozás során, de ez egyszerűen a kódmegosztásra vonatkozik, mivel az ellenőrzés elhalasztható, amíg hívás nem történik.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();
}

Vizsgáljuk meg az egyik olyan felületet, amely DemoNativeDynamicWrapper dinamikusan támogatja a felületet. Az alábbi kód az IDemoStoreType alapértelmezett felületi metódusok funkció használatát biztosítja.

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

Ebben a példában két fontos dolgot érdemes megjegyezni:

  1. Az DynamicInterfaceCastableImplementationAttribute attribútum. Ez az attribútum minden olyan típushoz szükséges, amelyet egy IDynamicInterfaceCastable metódusból ad vissza. Ez a megoldás további előnye, hogy egyszerűbbé teszi az IL vágást, ami azt jelenti, hogy az AOT-forgatókönyvek megbízhatóbbak.
  2. A leadott .DemoNativeDynamicWrapper Ez a dinamikus természet IDynamicInterfaceCastablerésze. A visszaadott IDynamicInterfaceCastable.GetInterfaceImplementation() típus a megvalósító IDynamicInterfaceCastabletípus "takarására" szolgál. A gist itt a this mutató nem az, amit úgy tesz, mintha azért, mert engedélyezzük az esetet aDemoNativeDynamicWrapper.IDemoStoreTypeNativeWrapper

Hívások továbbítása a COM-példányra

Függetlenül attól, hogy melyik natív objektumburkolót használja, szükség van a függvények meghívására egy COM-példányon. A megvalósítás IDemoStoreTypeNativeWrapper.StoreString() példaként szolgálhat a C#-függvénymutatók alkalmazására unmanaged .

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

Vizsgáljuk meg a COM-példány elhalasztását a virtuális tábla implementációjának eléréséhez. A COM ABI azt határozza meg, hogy egy objektum első mutatója a típus virtuális táblájához tartozik, és onnan elérhető a kívánt pont. Tegyük fel, hogy a COM-objektum címe .0x10000 Az első mutatóméretű értéknek a virtuális tábla címe kell lennie – ebben a példában 0x20000. Ha már a virtuális táblában van, a negyedik pontot (a 3. indexet nulla alapú indexelésben) kell keresnie a StoreString() megvalósítás eléréséhez.

COM instance
0x10000  0x20000

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

A függvénymutatóval ezután elküldheti az adott tagfüggvényt az objektumon az objektumpéldány első paraméterként való átadásával. Ennek a mintának ismerősnek kell lennie a Felügyelt objektumburkoló implementáció függvénydefiníciói alapján.

A CreateObject() metódus implementálása után az ComWrappers alosztály képes lesz natív objektumburkolókat létrehozni olyan COM-példányokhoz, amelyek mindkettőt IDemoGetType és IDemoStoreType.

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

4. lépés – A natív objektumburkoló élettartamának részleteinek kezelése

Az ComputeVtables() implementációk a CreateObject() burkoló élettartamának néhány részletét ismertetik, de további szempontokat is figyelembe kell venni. Bár ez egy rövid lépés lehet, jelentősen növelheti a ComWrappers tervezés összetettségét is.

A felügyelt objektumburkolóval ellentétben, amelyet a metódusok és Release() a hozzájuk AddRef() intézett hívások vezérelnek, a Natív objektumburkoló élettartamát nem a GC kezeli. A kérdés itt az, hogy mikor hívja Release() meg a natív objektumburkoló a IntPtr COM-példányt képviselőt? Két általános gyűjtő van:

  1. A Natív objektumburkoló véglegesítője felelős a COM-példány metódusának meghívásáért Release() . Ez az egyetlen alkalom, amikor biztonságosan meghívhatja ezt a metódust. Ezen a ponton a GC helyesen állapította meg, hogy a .NET-futtatókörnyezetben nincs más hivatkozás a natív objektumburkolóra. Itt összetettség lehet, ha megfelelően támogatja a COM Apartmentst; további információkért lásd a További szempontok című szakaszt .

  2. A Natív objektumburkoló implementálja és meghívja IDisposableRelease() a következőt Dispose(): .

Feljegyzés

A IDisposable minta csak akkor támogatott, ha a hívás során a CreateObject()CreateObjectFlags.UniqueInstance jelölő átadása megtörtént. Ha ezt a követelményt nem követi, lehetséges, hogy az ártalmatlanított natív objektumburkolók újra felhasználhatók az elidegenítés után.

Az ComWrappers alosztály használata

Most már rendelkezik egy ComWrappers tesztelhető alosztálysal. Annak érdekében, hogy ne hozzon létre olyan natív kódtárat, amely egy olyan COM-példányt ad vissza, amely implementálja IDemoGetType , és IDemoStoreTypea felügyelt objektumburkolót fogja használni, és COM-példányként fogja kezelni – ennek mindenképpen lehetővé kell tennie a COM átadását.

Először hozzunk létre egy felügyelt objektumburkolót. Példányosít egy példányt, és megjeleníti annak aktuális sztringállapotát DemoImpl .

var demo = new DemoImpl();

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

Most létrehozhat egy példányt DemoComWrappers és egy felügyelt objektumburkolót, amelyet aztán átadhat egy COM-környezetnek.

var cw = new DemoComWrappers();

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

Ahelyett, hogy átadta volna a felügyelt objektumburkolót egy COM-környezetnek, tegye úgy, mintha most kapta volna meg ezt a COM-példányt, ezért helyette létrehoz egy natív objektumburkolót.

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

A natív objektumburkolóval képesnek kell lennie arra, hogy az egyik kívánt felületre leadja, és normál felügyelt objektumként használja. Megvizsgálhatja a DemoImpl példányt, és megfigyelheti a műveletek hatását a felügyelt példányt burkoló felügyelt objektumburkolót burkoló natív objektumburkolóra.

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

Mivel az ComWrapper alosztályt úgy tervezték, hogy támogassa CreateObjectFlags.UniqueInstance, azonnal törölheti a natív objektumburkolót ahelyett, hogy várnia kell a GC bekövetkezésére.

(rcw as IDisposable)?.Dispose();

COM-aktiválás a ComWrappers

A COM-objektumok létrehozását általában COM-aktiválással hajtják végre – ez a dokumentum hatókörén kívüli összetett forgatókönyv. A követendő fogalmi minta érdekében bemutatjuk a CoCreateInstance() COM-aktiváláshoz használt API-t, és bemutatjuk, hogyan használható.ComWrappers

Tegyük fel, hogy az alkalmazásban a következő C#-kód található. Az alábbi példa egy COM-osztály és a beépített COM-interop rendszer aktiválását használja CoCreateInstance() a COM-példány megfelelő felületre való irányításához. Vegye figyelembe, hogy a használat typeof(I).GUID csak egy állításra korlátozódik, és a tükrözés használata, amely hatással lehet, ha a kód AOT-barát.

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

A fentiek használatbavétele ComWrappers a P/Invoke rendszerből való eltávolításával MarshalAs(UnmanagedType.Interface)CoCreateInstance() és a rendezés manuális végrehajtásával jár.

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

A gyári stílusú függvények absztrakciója is lehetséges, például ActivateClass<I> az aktiválási logika natív objektumburkoló osztálykonstruktorba való beépítésével. A konstruktor az ComWrappers.GetOrRegisterObjectForComInstance() API használatával társíthatja az újonnan létrehozott felügyelt objektumot az aktivált COM-példányhoz.

További szempontok

Natív AOT – Az idő előtti (AOT) összeállítás továbbfejlesztett indítási költséget biztosít, mivel a JIT-fordítás elkerülhető. Bizonyos platformokon gyakran szükség van a JIT-fordítás szükségességének megszüntetésére is. Az AOT támogatása az ComWrappers API célja volt, de minden burkoló implementációnak óvatosnak kell lennie, hogy ne vezessen be véletlenül olyan eseteket, amikor az AOT lebomlik, például tükrözés használatával. A Type.GUID tulajdonság egy példa a tükröződés használatára, de nem nyilvánvaló módon. A Type.GUID tulajdonság tükröződéssel vizsgálja meg a típus attribútumait, majd a típus nevét, és tartalmazza a szerelvényt az érték létrehozásához.

Forráslétrehozás – A COM-együttműködéshez és a ComWrappers megvalósításhoz szükséges kód nagy része valószínűleg valamilyen eszközzel automatikusan generálható. Mindkét típusú burkoló forrását a megfelelő COM-definíciók – például típustár (TLB), IDL vagy elsődleges interop szerelvény (PIA) alapján lehet létrehozni.

Globális regisztráció – Mivel az API-t a ComWrappers COM interop új fázisaként tervezték, valamilyen módon részlegesen integrálni kellett a meglévő rendszerrel. Az API-n globálisan hatással vannak a ComWrappers statikus metódusok, amelyek lehetővé teszik egy globális példány regisztrálását a különböző támogatáshoz. Ezeket a módszereket olyan példányokhoz tervezték ComWrappers , amelyek minden esetben átfogó COM-interop-támogatást várnak – a beépített COM interop rendszerhez hasonló módon.

Referenciakövető támogatása – Ez a támogatás elsődlegesen WinRT-forgatókönyvekhez használatos, és egy speciális forgatókönyvet jelöl. A legtöbb ComWrapper implementáció esetében egy vagy CreateObjectFlags.TrackerObject egy CreateComInterfaceFlags.TrackerSupport jelölőnek egy NotSupportedException. Ha engedélyezni szeretné ezt a támogatást, például Windows vagy nem Windows platformon, erősen ajánlott hivatkozni a C#/WinRT eszközláncra.

A korábban tárgyalt élettartamon, típusrendszeren és funkcionális funkciókon kívül a COM-kompatibilis implementáció ComWrappers további szempontokat igényel. A Windows platformon használt implementációk esetében a következő szempontokat kell figyelembe venni:

  • Apartmanok – A COM szervezeti struktúráját "Apartments" néven nevezik, és szigorú szabályokkal rendelkezik, amelyeket a stabil működés érdekében be kell tartani. Ez az oktatóanyag nem valósít meg lakásérzékeny natív objektumburkolókat, de minden éles használatra kész megvalósításnak lakásérzékenynek kell lennie. Ehhez a Windows 8-ban bevezetett API használatát RoGetAgileReference javasoljuk. A Windows 8 előtti verziók esetében vegye figyelembe a Globális felület táblát.

  • Biztonság – A COM gazdag biztonsági modellt biztosít az osztályaktiváláshoz és a kiképzett engedélyekhez.