Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Oktatóanyag: Az
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:
- Sorosítási és visszaállítá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.
Megjegyzé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 a
IUnknown*példány és egy felügyelt objektum közötti leképezés). - Szemétgyűjtő (GC) interakciója.
Ezeket a hatékonyságokat úgy érjük el, hogy a burkoló létrehozását és beszerzését az ComWrappers API-n keresztül kell elvégezni.
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 adunk a .NET futtatókörnyezet számára, hogy létrehozza és rögzítse a burkolókat, amikor a felügyelt objektumokat a COM-ba, illetve a COM objektumokat a .NET-be vetítjük át. 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 történelmileg COM Hívható Burkolónak (CCW) nevezték.
Natív objektumburkoló – A non-.NET nyelven implementált COM-objektumok használatához burkolókra van szükség a .NET-ből való használat engedélyezéséhez. Ezeket a burkolókat történelmileg Runtime Callable Burkolóknak (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() és a CreateObject() metódus egy felügyelt objektumburkolót, illetve egy natív objektumburkolót hoz létre. A ReleaseObjects() módszert a futtatókörnyezet arra használja, hogy kérést intézzen a megadott burkológyűjtemény "felszabadítására" a mögöttes natív objektumhoz. A legtöbb esetben a ReleaseObjects() metódus törzsében egyszerűen dobni lehet NotImplementedException, mivel csak egy fejlett forgatókönyvben van meghívva, amely a referenciakövető keretrendszert érinti.
// 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 vetített (vagyis IDemoGetType és IDemoStoreType) felületekkel.
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 interfész a IUnknown-tól öröklődik. A IUnknown típus három metódust határoz meg a következő sorrendben: QueryInterface(), AddRef()és Release(). A IUnknown metódusok után jönnek a specifikus interfész metódusok. Vegyük például a 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 DemoImpl, már van megvalósításunk GetString() és StoreString() esetében, 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 elvégezhető C# függvénymutatókkal és a UnmanagedCallersOnlyAttribute használatával. 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() – emlékezzen, a COM ABI szerint az első argumentum maga az aktuális 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 a(z) ComInterfaceDispatch* típusú lesz. Ez a típus egy alacsony szintű struktúrát jelöl egyetlen mezővel, Vtableamelyet később tárgyalunk. A típus és elrendezésének további részletei a futási idő implementációs részletei, és nem szabad rájuk támaszkodni. A felügyelt példány lekéréséhez egy ComInterfaceDispatch* példányból 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. Megjegyezheti, hogy a RuntimeHelpers.AllocateTypeAssociatedMemory() használatával úgy kell memóriát kiosztani, hogy az működjön a kiüríthető assembly-kkel.
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 Objektum Burkoló virtuális tábláinak és bejegyzéseinek lefoglalását előre el kell végezni, mivel ezek 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;
}
Miután implementálta a ComputeVtables() metódust, az ComWrappers alosztály képes lesz felügyelt objektumburkolókat létrehozni a DemoImpl példányaihoz. 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 engedékeny 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 mind a IDemoGetType, mind a IDemoStoreType-et megvalósítják. 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 elfogadja ezt a szabályzatot, meg kell erősítenie ezt a COM-példányra Marshal.QueryInterface() irányuló hívásokkal.
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 objektumcsomagoló
A dinamikus burkolók rugalmasabbak, 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 a következőt fogja használni: IDynamicInterfaceCastable 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ókörnyezetben 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áljunk meg egy olyan felületet, amelyet DemoNativeDynamicWrapper dinamikusan támogat. Az alábbi kód az IDemoStoreTypealapé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:
- Az
DynamicInterfaceCastableImplementationAttributeattribútum. Ez az attribútum minden olyan típushoz szükséges, amelyet egyIDynamicInterfaceCastablemetó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. - A leadott .
DemoNativeDynamicWrapperEz a dinamikus természetIDynamicInterfaceCastablerésze. AIDynamicInterfaceCastable.GetInterfaceImplementation()által visszaadott típus arra szolgál, hogy betakarja azt a típust, amelyik megvalósítja aIDynamicInterfaceCastable-et. A lényeg itt az, hogy athismutató nem az, aminek látszik, mert engedélyezünk egy esetetDemoNativeDynamicWrapper-tőlIDemoStoreTypeNativeWrapper-ig.
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 IDemoStoreTypeNativeWrapper.StoreString() megvalósítása példaként szolgálhat a unmanaged C#-függvénymutatók alkalmazására.
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 hivatkozásának feloldását a vtable implementációjának eléréséhez. A COM ABI azt határozza meg, hogy egy objektum első mutatója a típus vtable-jére mutat, és onnan elérhető a kívánt hely. 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. Amint a vtable-nél van, meg kell keresnie a negyedik helyet (a 3. indexet nulla alapú indexelésben), hogy hozzáférjen a StoreString() megvalósításhoz.
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 ezzel meghívhatja 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 mind a IDemoGetType-t, mind a IDemoStoreType-t implementálják.
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() és CreateObject() implementációk néhány burkoló élettartam részletet tartalmaztak, de további szempontokat is érdemes figyelembe 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 AddRef() és Release() metódusokhoz intézett hívások vezérelnek, a natív objektumburkoló élettartamát a GC nem determinisztikusan kezeli. A kérdés itt az, hogy a natív objektumburkoló mikor hívja meg a Release()-et, amely a COM-példányt képviseli, a IntPtr-on. Két általános kategória van
A Native Object Wrapper 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 .A Natív objektumburkoló megvalósítja a
IDisposable-t és meghívja aRelease()aDispose().
Megjegyzés:
A IDisposable minta csak akkor támogatott, ha a CreateObject() hívás során a CreateObjectFlags.UniqueInstance jelölő át lett adva. Ha ezt a követelményt nem követik, akkor lehetséges, hogy a már ártalmatlanított natív objektumburkolók újra használatba kerülnek az ártalmatlanítás után.
Az ComWrappers alosztály használata
Most már rendelkezik egy ComWrappers tesztelhető alosztállyal. Annak elkerülése érdekében, hogy ne hozzon létre olyan natív kódtárat, amely egy olyan COM-példányt ad vissza, amely megvalósítja IDemoGetType, és IDemoStoreType-t, használni fogja a felügyelt objektumburkolót, és COM-példányként fogja kezelni – ez feltétlenül szükséges ahhoz, hogy a COM átadása lehetséges legyen.
Először hozzunk létre egy felügyelt objektumburkolót. Instanciál egy DemoImpl példányt, és megjeleníti annak aktuális szövegállapotát.
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, hogy az egyik kívánt interfészre konvertálja, é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ó natív objektumburkolóra, amely egy felügyelt objektumburkolót foglal magába.
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 ComWrappers segítségével
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() API-t, amelyet COM-aktiváláshoz használunk, és illusztráljuk, hogyan alkalmazható a ComWrappers-sel.
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 typeof(I).GUID használata egy assert műveletre korlátozódik, és a reflexió használata hatással lehet arra, hogy a kód AOT-barát-e.
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 ComWrappers átalakítása a MarshalAs(UnmanagedType.Interface) eltávolítását a CoCreateInstance() P/Invoke-ból és az adatformátum-átalakítás manuális végrehajtását jelenti.
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 előzetes (AOT) fordítás javított indítási teljesítményt 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 reflexióval vizsgálja meg a típus attribútumait, majd szükség szerint a típus nevét és a tartalmazó szerelvényt is felhasználja az érték generálá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 a CreateComInterfaceFlags.TrackerSupport vagy CreateObjectFlags.TrackerObject jelölőnek egy NotSupportedException-t kellene dobnia. 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 komponensmodellel kompatibilis natív objektumburkolókat, de minden termelésre kész megvalósításnak komponensmodellel kompatibilisnek kell lennie. Ehhez a Windows 8-ban bevezetett API használatát
RoGetAgileReferencejavasoljuk. 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 közvetített engedélyekhez.