Zelfstudie: De ComWrappers
API gebruiken
In deze zelfstudie leert u hoe u het ComWrappers
type correct kunt subklassen om een geoptimaliseerde en AOT-vriendelijke COM-interoperabiliteitsoplossing te bieden. Voordat u aan deze zelfstudie begint, moet u bekend zijn met COM, de architectuur en de bestaande COM-interoperabiliteitsoplossingen.
In deze zelfstudie implementeert u de volgende interfacedefinities. Deze interfaces en hun implementaties tonen het volgende aan:
- Marshalling en unmarshalling types over de COM/.NET grens.
- Twee verschillende benaderingen voor het verbruik van systeemeigen COM-objecten in .NET.
- Een aanbevolen patroon voor het inschakelen van aangepaste COM-interoperabiliteit in .NET 5 en hoger.
Alle broncode die in deze zelfstudie wordt gebruikt, is beschikbaar in de opslagplaats dotnet/samples.
Notitie
In de .NET 8 SDK en latere versies wordt er een brongenerator geleverd om automatisch een ComWrappers
API-implementatie voor u te genereren. Zie brongeneratie voor meer informatie.ComWrappers
C#-definities
interface IDemoGetType
{
string? GetString();
}
interface IDemoStoreType
{
void StoreString(int len, string? str);
}
Win32 C++-definities
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;
};
Overzicht van het ComWrappers
ontwerp
De ComWrappers
API is ontworpen om de minimale interactie te bieden die nodig is om COM-interoperabiliteit met de .NET 5+ runtime uit te voeren. Dit betekent dat veel van de mooie eigenschappen die bestaan met het ingebouwde COM-interoperabiliteitssysteem niet aanwezig zijn en moeten worden opgebouwd uit basisbouwstenen. De twee primaire verantwoordelijkheden van de API zijn:
- Efficiënte objectidentificatie (bijvoorbeeld toewijzing tussen een
IUnknown*
exemplaar en een beheerd object). - Interactie garbage collector (GC).
Deze efficiëntie wordt bereikt door het maken en verkrijgen van wrappers te vereisen om de ComWrappers
API te doorlopen.
Aangezien de ComWrappers
API zo weinig verantwoordelijkheden heeft, is het de reden dat het grootste deel van het interoperabiliteitswerk door de consument moet worden afgehandeld. Dit is waar. Het extra werk is echter grotendeels mechanisch en kan worden uitgevoerd door een oplossing voor het genereren van bronnen. De C#/WinRT-hulpprogrammaketen is bijvoorbeeld een oplossing voor het genereren van ComWrappers
bronnen die is gebouwd om WinRT-interop-ondersteuning te bieden.
ComWrappers
Een subklasse implementeren
Als u een ComWrappers
subklasse opgeeft, geeft u voldoende informatie aan de .NET-runtime om wrappers te maken en vast te leggen voor beheerde objecten die worden geprojecteerd in COM- en COM-objecten die in .NET worden geprojecteerd. Voordat we een overzicht van de subklasse bekijken, moeten we enkele termen definiëren.
Managed Object Wrapper : beheerde .NET-objecten vereisen wrappers om gebruik vanuit een non-.NET omgeving mogelijk te maken. Deze wrappers worden in het verleden COM Callable Wrappers (CCW) genoemd.
Systeemeigen object wrapper : COM-objecten die zijn geïmplementeerd in een non-.NET taal vereisen wrappers om gebruik vanuit .NET mogelijk te maken. Deze wrappers worden in het verleden Runtime Callable Wrappers (RCW) genoemd.
Stap 1: methoden definiëren om hun intentie te implementeren en te begrijpen
Als u het ComWrappers
type wilt uitbreiden, moet u de volgende drie methoden implementeren. Elk van deze methoden vertegenwoordigt de deelname van de gebruiker aan het maken of verwijderen van een type wrapper. De ComputeVtables()
en CreateObject()
methoden maken respectievelijk een Managed Object Wrapper en Native Object Wrapper. De ReleaseObjects()
methode wordt door de runtime gebruikt om een aanvraag in te dienen voor de opgegeven verzameling wrappers die moeten worden 'vrijgegeven' van het onderliggende systeemeigen object. In de meeste gevallen kan de hoofdtekst van de ReleaseObjects()
methode eenvoudigweg worden gegenereerd NotImplementedException, omdat deze alleen wordt aangeroepen in een geavanceerd scenario met het Referentietracker-framework.
// 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();
}
Als u de ComputeVtables()
methode wilt implementeren, moet u bepalen welke beheerde typen u wilt ondersteunen. Voor deze zelfstudie ondersteunen we de twee eerder gedefinieerde interfaces (IDemoGetType
en IDemoStoreType
) en een beheerd type waarmee de twee interfaces (DemoImpl
) worden geïmplementeerd.
class DemoImpl : IDemoGetType, IDemoStoreType
{
string? _string;
public string? GetString() => _string;
public void StoreString(int _, string? str) => _string = str;
}
Voor de CreateObject()
methode moet u ook bepalen wat u wilt ondersteunen. In dit geval kennen we echter alleen de COM-interfaces waarin we geïnteresseerd zijn, niet de COM-klassen. De interfaces die van de COM-zijde worden gebruikt, zijn hetzelfde als de interfaces die we van de .NET-zijde projecteren (dat wil IDemoGetType
wel en IDemoStoreType
).
Deze zelfstudie wordt niet geïmplementeerd ReleaseObjects()
.
Stap 2: implementeren ComputeVtables()
Laten we beginnen met de Managed Object Wrapper. Deze wrappers zijn eenvoudiger. U bouwt voor elke interface een virtuele methodetabel of vtable om deze te projecteren in de COM-omgeving. Voor deze zelfstudie definieert u een vtable als een reeks aanwijzers, waarbij elke aanwijzer een implementatie van een functie op een interface vertegenwoordigt. De volgorde is hier erg belangrijk. In COM neemt elke interface over van IUnknown
. Het IUnknown
type heeft drie methoden die in de volgende volgorde zijn gedefinieerd: QueryInterface()
, AddRef()
en Release()
. Nadat de IUnknown
methoden zijn geleverd, zijn de specifieke interfacemethoden beschikbaar. Denk bijvoorbeeld aan IDemoGetType
en IDemoStoreType
. Conceptueel ziet de vtables voor de typen er als volgt uit:
IDemoGetType | IDemoStoreType
==================================
QueryInterface | QueryInterface
AddRef | AddRef
Release | Release
GetString | StoreString
DemoImpl
Kijkend naar, hebben we al een implementatie voor GetString()
enStoreString()
, maar hoe zit het met de IUnknown
functies? Het implementeren van een IUnknown
exemplaar valt buiten het bereik van deze zelfstudie, maar dit kan handmatig worden gedaan.ComWrappers
In deze zelfstudie laat u de runtime dit deel echter afhandelen. U kunt de IUnknown
implementatie ophalen met behulp van de ComWrappers.GetIUnknownImpl()
methode.
Het lijkt misschien alsof u alle methoden hebt geïmplementeerd, maar helaas zijn alleen de IUnknown
functies bruikbaar in een COM-vtable. Omdat COM zich buiten de runtime bevindt, moet u systeemeigen functiepointers maken voor uw DemoImpl
implementatie. Dit kan worden gedaan met C#-functiepointers en de UnmanagedCallersOnlyAttribute
. U kunt een functie maken die in de vtable moet worden ingevoegd door een static
functie te maken die de COM-functiehandtekening nabootst. Hieronder volgt een voorbeeld van de COM-handtekening voor IDemoGetType.GetString()
: intrekken uit de COM-ABI dat het eerste argument het exemplaar zelf is.
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);
De wrapper-implementatie moet IDemoGetType.GetString()
bestaan uit marshallinglogica en vervolgens een verzending naar het beheerde object dat wordt verpakt. Alle statussen voor verzending zijn opgenomen in het opgegeven _this
argument. Het _this
argument is eigenlijk van het type ComInterfaceDispatch*
. Dit type vertegenwoordigt een structuur op laag niveau met één veld, Vtable
die later wordt besproken. Verdere details van dit type en de indeling zijn een implementatiedetails van de runtime en mogen niet afhankelijk zijn van. Gebruik de volgende code om het beheerde exemplaar op te halen uit een ComInterfaceDispatch*
exemplaar:
IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);
Nu u een C#-methode hebt die in een vtable kan worden ingevoegd, kunt u de vtable maken. Let op het gebruik van het toewijzen van RuntimeHelpers.AllocateTypeAssociatedMemory()
geheugen op een manier die werkt met niet-laadbare assembly's.
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;
De toewijzing van vtables is het eerste deel van de implementatie ComputeVtables()
. U moet ook uitgebreide COM-definities maken voor typen die u van plan bent te ondersteunen. Denk en DemoImpl
welke onderdelen ervan moeten worden gebruikt vanuit COM. Met behulp van ComInterfaceEntry
de samengestelde vtables kunt u nu een reeks exemplaren maken die de volledige weergave van het beheerde object in COM vertegenwoordigen.
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;
De toewijzing van vtables en vermeldingen voor de Managed Object Wrapper kan en moet van tevoren worden uitgevoerd omdat de gegevens kunnen worden gebruikt voor alle exemplaren van het type. Het werk hier kan worden uitgevoerd in een static
constructor of een module-initialisatiefunctie, maar dit moet van tevoren worden uitgevoerd, zodat de ComputeVtables()
methode zo eenvoudig en snel mogelijk is.
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;
}
Zodra u de ComputeVtables()
methode hebt geïmplementeerd, kan de ComWrappers
subklasse beheerde objectwikkelaars produceren voor exemplaren van DemoImpl
. Houd er rekening mee dat de geretourneerde Managed Object Wrapper van de aanroep GetOrCreateComInterfaceForObject()
van het type IUnknown*
is. Als de systeemeigen API die wordt doorgegeven aan de wrapper een andere interface vereist, moet een Marshal.QueryInterface()
voor die interface worden uitgevoerd.
var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
Stap 3: implementeren CreateObject()
Het bouwen van een systeemeigen object wrapper heeft meer implementatieopties en veel meer nuance dan het maken van een Managed Object Wrapper. De eerste vraag die moet worden beantwoord, is de wijze waarop de subklasse moet worden ondersteund in het ComWrappers
ondersteunen van COM-typen. Als u alle COM-typen wilt ondersteunen, wat mogelijk is, moet u een aanzienlijke hoeveelheid code schrijven of een aantal slimme toepassingen van Reflection.Emit
gebruiken. Voor deze zelfstudie biedt u alleen ondersteuning voor COM-exemplaren die zowel IDemoGetType
IDemoStoreType
als . Aangezien u weet dat er een eindige set is en u hebt beperkt dat elk opgegeven COM-exemplaar beide interfaces moet implementeren, kunt u één, statisch gedefinieerde wrapper bieden; dynamische gevallen zijn echter gebruikelijk genoeg in COM dat we beide opties zullen verkennen.
Statische systeemeigen objectwikkelaar
Laten we eerst eens kijken naar de statische implementatie. De statische systeemeigen objectwikkelaar omvat het definiëren van een beheerd type dat de .NET-interfaces implementeert en de aanroepen van het beheerde type naar het COM-exemplaar kan doorsturen. Een ruw overzicht van de statische wrapper volgt.
// 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();
}
Als u een exemplaar van deze klasse wilt maken en als wrapper wilt opgeven, moet u een bepaald beleid definiëren. Als dit type wordt gebruikt als een wrapper, lijkt het erop dat omdat het beide interfaces implementeert, het onderliggende COM-exemplaar beide interfaces ook moet implementeren. Gezien het feit dat u dit beleid aanneemt, moet u dit bevestigen via aanroepen naar Marshal.QueryInterface()
het COM-exemplaar.
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
};
Dynamische systeemeigen objectwikkelaar
Dynamische wrappers zijn flexibeler omdat ze een manier bieden om tijdens runtime query's uit te voeren op typen in plaats van statisch. Als u deze ondersteuning wilt bieden, kunt u deze gebruiken IDynamicInterfaceCastable
. Meer informatie vindt u hier. U ziet dat DemoNativeDynamicWrapper
deze interface alleen wordt geïmplementeerd. De functionaliteit die de interface biedt, is een kans om te bepalen welk type tijdens de runtime wordt ondersteund. De bron voor deze zelfstudie voert een statische controle uit tijdens het maken, maar dat is gewoon voor het delen van code omdat de controle kan worden uitgesteld totdat er een aanroep naar DemoNativeDynamicWrapper.IsInterfaceImplemented()
wordt gedaan.
// 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();
}
Laten we eens kijken naar een van de interfaces die DemoNativeDynamicWrapper
dynamisch worden ondersteund. De volgende code biedt de implementatie van het gebruik van IDemoStoreType
de functie standaardinterfacemethoden .
[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);
}
}
Er zijn twee belangrijke dingen die u in dit voorbeeld moet noteren:
- Het
DynamicInterfaceCastableImplementationAttribute
kenmerk. Dit kenmerk is vereist voor elk type dat wordt geretourneerd door eenIDynamicInterfaceCastable
methode. Het heeft het extra voordeel dat IL-trimming eenvoudiger wordt, wat betekent dat AOT-scenario's betrouwbaarder zijn. - De cast naar
DemoNativeDynamicWrapper
. Dit maakt deel uit van de dynamische aard vanIDynamicInterfaceCastable
. Het type waaruit wordt geretourneerdIDynamicInterfaceCastable.GetInterfaceImplementation()
, wordt gebruikt om het type dat wordt geïmplementeerdIDynamicInterfaceCastable
te "deken". De gist hier is dethis
aanwijzer is niet wat het doet alsof het is omdat we een zaak vanDemoNativeDynamicWrapper
totIDemoStoreTypeNativeWrapper
.
Oproepen doorschakelen naar het COM-exemplaar
Ongeacht welke systeemeigen objectwikkelaar wordt gebruikt, hebt u de mogelijkheid nodig om functies op een COM-exemplaar aan te roepen. De implementatie van IDemoStoreTypeNativeWrapper.StoreString()
kan dienen als voorbeeld van het unmanaged
gebruik van C#-functieaanwijzers.
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);
}
}
Laten we eens kijken naar de uitsteling van het COM-exemplaar voor toegang tot de vtable-implementatie. De COM ABI definieert dat de eerste aanwijzer van een object is naar de vtable van het type en van daaruit kan de gewenste site worden geopend. Stel dat het adres van het COM-object is 0x10000
. De eerste waarde van de aanwijzer moet het adres van de vtable zijn, in dit voorbeeld 0x20000
. Wanneer u bij de vtable bent, zoekt u naar de vierde site (index 3 in indexering op basis van nul) voor toegang tot de StoreString()
implementatie.
COM instance
0x10000 0x20000
VTable for IDemoStoreType
0x20000 <Address of QueryInterface>
0x20008 <Address of AddRef>
0x20010 <Address of Release>
0x20018 <Address of StoreString>
Als u de functiepointer hebt, kunt u vervolgens naar die lidfunctie op dat object verzenden door het objectexemplaren door te geven als de eerste parameter. Dit patroon moet er bekend uitzien op basis van de functiedefinities van de implementatie van managed object wrapper.
Zodra de CreateObject()
methode is geïmplementeerd, kan de ComWrappers
subklasse systeemeigen objectwikkelaars produceren voor COM-exemplaren die zowel IDemoGetType
IDemoStoreType
als .
IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);
Stap 4: details van de levensduur van systeemeigen object-wrapper verwerken
De ComputeVtables()
en CreateObject()
implementaties hebben betrekking op enkele details over de levensduur van wrappers, maar er zijn verdere overwegingen. Hoewel dit een korte stap kan zijn, kan het ook de complexiteit van het ComWrappers
ontwerp aanzienlijk verhogen.
In tegenstelling tot de Managed Object Wrapper, die wordt beheerd door aanroepen naar AddRef()
en Release()
methoden, wordt de levensduur van een systeemeigen objectwikkelaar niet-deterministisch afgehandeld door de GC. De vraag hier is: wanneer roept Release()
de systeemeigen objectwikkelaar de IntPtr
COM-instantie aan? Er zijn twee algemene buckets:
De Finalizer van de Native Object Wrapper is verantwoordelijk voor het aanroepen van de methode van
Release()
het COM-exemplaar. Dit is de enige keer dat het veilig is om deze methode aan te roepen. Op dit moment is het correct bepaald door de GC dat er geen andere verwijzingen zijn naar de native object wrapper in de .NET-runtime. Er kan hier complexiteit zijn als u COM Apartments goed ondersteunt; Zie de sectie Aanvullende overwegingen voor meer informatie.De systeemeigen object-wrapper implementeert en roept
IDisposable
Release()
inDispose()
.
Notitie
Het IDisposable
patroon moet alleen worden ondersteund als tijdens de CreateObject()
aanroep de CreateObjectFlags.UniqueInstance
vlag is doorgegeven. Als deze vereiste niet wordt gevolgd, is het mogelijk dat systeemeigen objectwikkelaars na verwijdering opnieuw worden gebruikt.
ComWrappers
De subklasse gebruiken
U hebt nu een ComWrappers
subklasse die kan worden getest. Om te voorkomen dat u een systeemeigen bibliotheek maakt die een COM-exemplaar retourneert dat wordt geïmplementeerd IDemoGetType
en IDemoStoreType
, gebruikt u de Managed Object Wrapper en behandelt u deze als een COM-exemplaar. Dit moet mogelijk zijn om het COM toch door te geven.
Laten we eerst een Managed Object Wrapper maken. Instantie instantiëren DemoImpl
en de huidige tekenreeksstatus weergeven.
var demo = new DemoImpl();
string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");
U kunt nu een exemplaar van DemoComWrappers
en een Managed Object Wrapper maken die u vervolgens kunt doorgeven aan een COM-omgeving.
var cw = new DemoComWrappers();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
In plaats van de Managed Object Wrapper door te geven aan een COM-omgeving, doet u alsof u dit COM-exemplaar zojuist hebt ontvangen. Daarom maakt u in plaats daarvan een systeemeigen objectwikkelaar.
var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);
Met de systeemeigen objectwikkelaar moet u deze kunnen casten naar een van de gewenste interfaces en deze gebruiken als een normaal beheerd object. U kunt het DemoImpl
exemplaar onderzoeken en de impact bekijken van bewerkingen op de systeemeigen objectwikkelaar die een Managed Object Wrapper verpakt die het beheerde exemplaar op zijn beurt verpakt.
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}");
Omdat uw ComWrapper
subklasse is ontworpen ter ondersteuning CreateObjectFlags.UniqueInstance
, kunt u de systeemeigen objectwikkelaar onmiddellijk opschonen in plaats van te wachten tot er een GC plaatsvindt.
(rcw as IDisposable)?.Dispose();
COM-activering met ComWrappers
Het maken van COM-objecten wordt doorgaans uitgevoerd via COM-activering: een complex scenario buiten het bereik van dit document. Om een conceptueel patroon te bieden dat moet worden gevolgd, introduceren we de CoCreateInstance()
API, die wordt gebruikt voor COM-activering en laten we zien hoe deze kunnen worden gebruikt met ComWrappers
.
Stel dat u de volgende C#-code in uw toepassing hebt. In het onderstaande voorbeeld wordt gebruikgemaakt CoCreateInstance()
van het activeren van een COM-klasse en het ingebouwde COM-interoperabiliteitssysteem om het COM-exemplaar te marshalen naar de juiste interface. Let op: het gebruik van typeof(I).GUID
is beperkt tot een assert en is een geval van reflectie die van invloed kan zijn als de code AOT-vriendelijk is.
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);
Het converteren van het bovenstaande om te gebruiken ComWrappers
omvat het verwijderen van de MarshalAs(UnmanagedType.Interface)
CoCreateInstance()
P/Invoke en het handmatig uitvoeren van de marshalling.
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);
Het is ook mogelijk om fabrieksfuncties te abstraheren, zoals ActivateClass<I>
het opnemen van de activeringslogica in de klasseconstructor voor een systeemeigen objectwikkelaar. De constructor kan de ComWrappers.GetOrRegisterObjectForComInstance()
API gebruiken om het zojuist gebouwde beheerde object te koppelen aan het geactiveerde COM-exemplaar.
Aanvullende overwegingen
Systeemeigen AOT: AOT-compilatie (Ahead-of-Time) biedt verbeterde opstartkosten, omdat JIT-compilatie wordt vermeden. Het verwijderen van de noodzaak voor JIT-compilatie is ook vaak vereist op sommige platforms. Het ondersteunen van AOT was een doel van de ComWrappers
API, maar elke wrapper-implementatie moet voorzichtig zijn om onbedoeld gevallen in te voeren waarin AOT wordt opgesplitst, zoals het gebruik van weerspiegeling. De Type.GUID
eigenschap is een voorbeeld van waar weerspiegeling wordt gebruikt, maar op een niet-voor de hand liggende manier. De Type.GUID
eigenschap gebruikt weerspiegeling om de kenmerken van het type te inspecteren en vervolgens mogelijk de naam van het type en de assembly te bevatten om de waarde ervan te genereren.
Brongeneratie : de meeste code die nodig is voor COM-interoperabiliteit en een ComWrappers
implementatie, kan waarschijnlijk automatisch worden gegenereerd door een aantal hulpprogramma's. De bron voor beide typen wrappers kan worden gegenereerd op basis van de juiste COM-definities, bijvoorbeeld Type Library (TLB), IDL of een Primary Interop Assembly (PIA).
Globale registratie : omdat de ComWrappers
API is ontworpen als een nieuwe fase van COM-interoperabiliteit, moest de API gedeeltelijk worden geïntegreerd met het bestaande systeem. Er zijn wereldwijd invloed op statische methoden op de ComWrappers
API die registratie van een globaal exemplaar toestaan voor verschillende ondersteuning. Deze methoden zijn ontworpen voor ComWrappers
exemplaren die verwachten dat ze in alle gevallen uitgebreide COM-interoperabiliteitsondersteuning bieden, vergelijkbaar met het ingebouwde COM-interoperabiliteitssysteem.
Referentietracker-ondersteuning : deze ondersteuning wordt primair gebruikt voor WinRT-scenario's en vertegenwoordigt een geavanceerd scenario. Voor de meeste ComWrapper
implementaties moet een CreateComInterfaceFlags.TrackerSupport
of CreateObjectFlags.TrackerObject
vlag een NotSupportedException. Als u deze ondersteuning wilt inschakelen, mogelijk op een Windows- of zelfs niet-Windows-platform, wordt het ten zeerste aanbevolen om te verwijzen naar de C#/WinRT-hulpprogrammaketen.
Afgezien van de levensduur, typesysteem en functionele functies die eerder worden besproken, vereist een COM-compatibele implementatie van ComWrappers
aanvullende overwegingen. Voor elke implementatie die wordt gebruikt op het Windows-platform, zijn er de volgende overwegingen:
Appartementen – DE organisatiestructuur van COM voor threading wordt "Appartementen" genoemd en heeft strikte regels die moeten worden gevolgd voor stabiele activiteiten. In deze zelfstudie wordt geen systeemeigen object wrappers geïmplementeerd die op de hoogte zijn van het appartement, maar elke implementatie die gereed is voor productie, moet op de hoogte zijn van het appartement. Hiervoor raden we u aan de
RoGetAgileReference
API te gebruiken die is geïntroduceerd in Windows 8. Voor versies vóór Windows 8 kunt u de algemene interfacetabel overwegen.Beveiliging : COM biedt een uitgebreid beveiligingsmodel voor klasseactivering en geproxiede machtigingen.