Share via


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:

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

DemoImplKijkend 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, Vtabledie 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.Emitgebruiken. Voor deze zelfstudie biedt u alleen ondersteuning voor COM-exemplaren die zowel IDemoGetTypeIDemoStoreTypeals . 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:

  1. Het DynamicInterfaceCastableImplementationAttribute kenmerk. Dit kenmerk is vereist voor elk type dat wordt geretourneerd door een IDynamicInterfaceCastable methode. Het heeft het extra voordeel dat IL-trimming eenvoudiger wordt, wat betekent dat AOT-scenario's betrouwbaarder zijn.
  2. De cast naar DemoNativeDynamicWrapper. Dit maakt deel uit van de dynamische aard van IDynamicInterfaceCastable. Het type waaruit wordt geretourneerd IDynamicInterfaceCastable.GetInterfaceImplementation() , wordt gebruikt om het type dat wordt geïmplementeerd IDynamicInterfaceCastablete "deken". De gist hier is de this aanwijzer is niet wat het doet alsof het is omdat we een zaak van DemoNativeDynamicWrapper tot IDemoStoreTypeNativeWrapper.

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 IDemoGetTypeIDemoStoreTypeals .

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:

  1. 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.

  2. De systeemeigen object-wrapper implementeert en roept IDisposableRelease() in Dispose().

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.