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 IDemoStoreType
egy 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 IDemoGetType
IDemoStoreType
a ).
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, DemoImpl
hogy 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 IUnknown
ComWrappers.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, Vtable
amelyet 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:
- Az
DynamicInterfaceCastableImplementationAttribute
attribútum. Ez az attribútum minden olyan típushoz szükséges, amelyet egyIDynamicInterfaceCastable
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. - A leadott .
DemoNativeDynamicWrapper
Ez a dinamikus természetIDynamicInterfaceCastable
része. A visszaadottIDynamicInterfaceCastable.GetInterfaceImplementation()
típus a megvalósítóIDynamicInterfaceCastable
típus "takarására" szolgál. A gist itt athis
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:
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 .A Natív objektumburkoló implementálja és meghívja
IDisposable
Release()
a következőtDispose()
: .
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 IDemoStoreType
a 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.
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: