Delen via


Aanroepbare COM-wrapper

Wanneer een COM-client een .NET-object aanroept, maakt de algemene taalruntime het beheerde object en een COM-aanroepbare wrapper (CCW) voor het object. Com-clients kunnen niet rechtstreeks verwijzen naar een .NET-object en gebruiken de CCW als proxy voor het beheerde object.

De runtime maakt precies één CCW voor een beheerd object, ongeacht het aantal COM-clients dat de services aanvraagt. Zoals in de volgende afbeelding wordt weergegeven, kunnen meerdere COM-clients een verwijzing bevatten naar de CCW die de INew-interface beschikbaar maakt. De CCW bevat op zijn beurt één verwijzing naar het beheerde object dat de interface implementeert en afval wordt verzameld. Com- en .NET-clients kunnen tegelijkertijd aanvragen indienen voor hetzelfde beheerde object.

Multiple COM clients holding a reference to the CCW that exposes INew.

Aanroepbare COM-wrappers zijn onzichtbaar voor andere klassen die worden uitgevoerd in de .NET-runtime. Hun primaire doel is het marshal aanroepen tussen beheerde en onbeheerde code; CCW's beheren echter ook de objectidentiteit en levensduur van de beheerde objecten die ze verpakken.

Objectidentiteit

De runtime wijst geheugen toe voor het .NET-object van de door garbagecollection verzamelde heap, waardoor de runtime het object indien nodig in het geheugen kan verplaatsen. Daarentegen wijst de runtime geheugen toe voor de CCW vanuit een niet-opgeslagen heap, waardoor COM-clients rechtstreeks naar de wrapper kunnen verwijzen.

Levensduur van object

In tegenstelling tot de .NET-client wordt de CCW op traditionele COM-wijze geteld. Wanneer het aantal verwijzingen op de CCW nul bereikt, geeft de wrapper de verwijzing voor het beheerde object vrij. Een beheerd object zonder resterende verwijzingen wordt verzameld tijdens de volgende garbagecollectioncyclus.

COM-interfaces simuleren

CCW maakt alle openbare, com-zichtbare interfaces, gegevenstypen beschikbaar en retourneert waarden aan COM-clients op een manier die consistent is met het afdwingen van interactie op basis van een interface. Voor een COM-client is het aanroepen van methoden voor een .NET-object identiek aan het aanroepen van methoden op een COM-object.

Om deze naadloze aanpak te creëren, produceert de CCW traditionele COM-interfaces, zoals IUnknown en IDispatch. Zoals in de volgende afbeelding wordt weergegeven, onderhoudt de CCW één verwijzing op het .NET-object dat wordt verpakt. Zowel de COM-client als het .NET-object communiceren met elkaar via de proxy en stub-constructie van de CCW.

Diagram that shows how CCW manufactures COM interfaces.

Naast het beschikbaar maken van de interfaces die expliciet zijn geïmplementeerd door een klasse in de beheerde omgeving, levert de .NET-runtime implementaties van de COM-interfaces die namens het object worden vermeld in de volgende tabel. Een .NET-klasse kan het standaardgedrag overschrijven door een eigen implementatie van deze interfaces te bieden. De runtime biedt echter altijd de implementatie voor de IUnknown - en IDispatch-interfaces .

Interface Beschrijving
Idispatch Biedt een mechanisme voor late binding om te typen.
IErrorInfo Biedt een tekstuele beschrijving van de fout, de bron, een Help-bestand, de Help-context en de GUID van de interface die de fout heeft gedefinieerd (altijd GUID_NULL voor .NET-klassen).
IProvideClassInfo Hiermee kunnen COM-clients toegang krijgen tot de ITypeInfo-interface die door een beheerde klasse is geïmplementeerd. Retourneert COR_E_NOTSUPPORTED .NET Core voor typen die niet zijn geïmporteerd uit COM.
ISupportErrorInfo Hiermee kan een COM-client bepalen of het beheerde object de IErrorInfo-interface ondersteunt. Zo ja, dan kan de client een aanwijzer ophalen naar het meest recente uitzonderingsobject. Alle beheerde typen ondersteunen de IErrorInfo-interface .
ITypeInfo (alleen.NET Framework) Bevat typegegevens voor een klasse die precies hetzelfde is als de typegegevens die door Tlbexp.exe worden geproduceerd.
Iunknown Biedt de standaard implementatie van de IUnknown-interface waarmee de COM-client de levensduur van de CCW beheert en type coercion biedt.

Een beheerde klasse kan ook de COM-interfaces bieden die in de volgende tabel worden beschreven.

Interface Beschrijving
De klasse-interface (_classname) Interface, beschikbaar gemaakt door de runtime en niet expliciet gedefinieerd, waarmee alle openbare interfaces, methoden, eigenschappen en velden worden weergegeven die expliciet worden weergegeven op een beheerd object.
I Verbinding maken ionPoint en I Verbinding maken ionPointContainer Interface voor objecten die op gedelegeerden gebaseerde gebeurtenissen genereren (een interface voor het registreren van gebeurtenisabonnees).
IDispatchEx (alleen.NET Framework) Interface die wordt geleverd door de runtime als de klasse IExpando implementeert. De IDispatchEx-interface is een uitbreiding van de IDispatch-interface die, in tegenstelling tot IDispatch, opsomming, toevoeging, verwijdering en hoofdlettergevoelige aanroepen van leden mogelijk maakt.
IEnumVARIANT Interface voor verzamelingstypeklassen, waarmee de objecten in de verzameling worden opgesomd als de klasse IEnumerable implementeert.

Inleiding tot de klasse-interface

De klasse-interface, die niet expliciet is gedefinieerd in beheerde code, is een interface waarmee alle openbare methoden, eigenschappen, velden en gebeurtenissen worden weergegeven die expliciet worden weergegeven op het .NET-object. Deze interface kan een dual- of dispatch-only interface zijn. De klasse-interface ontvangt de naam van de .NET-klasse zelf, voorafgegaan door een onderstrepingsteken. Voor klasse Zoogdieren is de klasse-interface bijvoorbeeld _Mammal.

Voor afgeleide klassen bevat de klasse-interface ook alle openbare methoden, eigenschappen en velden van de basisklasse. De afgeleide klasse maakt ook een klasse-interface beschikbaar voor elke basisklasse. Als klasse Zoogdieren bijvoorbeeld klasse MammalSuperclass, die zichzelf uitbreidt System.Object, wordt het .NET-object blootgesteld aan COM-clients drie klasseinterfaces met de naam _Mammal, _MammalSuperclass en _Object.

Denk bijvoorbeeld aan de volgende .NET-klasse:

' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
    Sub Eat()
    Sub Breathe()
    Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
    public void Eat() {}
    public void Breathe() {}
    public void Sleep() {}
}

De COM-client kan een aanwijzer verkrijgen naar een klasse-interface met de naam _Mammal. In .NET Framework kunt u het hulpprogramma Type Library Exporter (Tlbexp.exe) gebruiken om een typebibliotheek met de _Mammal interfacedefinitie te genereren. De typebibliotheekexporteur wordt niet ondersteund op .NET Core. Als de Mammal klasse een of meer interfaces heeft geïmplementeerd, worden de interfaces weergegeven onder de coklasse.

[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
    [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
        pRetVal);
    [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
        VARIANT_BOOL* pRetVal);
    [id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
    [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x6002000d)] HRESULT Eat();
    [id(0x6002000e)] HRESULT Breathe();
    [id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
    [default] interface _Mammal;
}

Het genereren van de klasse-interface is optioneel. Com-interoperabiliteit genereert standaard een interface voor alleen-verzenden voor elke klasse die u naar een typebibliotheek exporteert. U kunt het automatisch maken van deze interface voorkomen of wijzigen door de ClassInterfaceAttribute interface toe te passen op uw klas. Hoewel de klasse-interface de taak van het blootstellen van beheerde klassen aan COM kan vereenvoudigen, zijn de toepassingen beperkt.

Let op

Het gebruik van de klasse-interface kan in plaats van expliciet uw eigen versiebeheer te definiëren, de toekomstige versiebeheer van uw beheerde klasse bemoeilijken. Lees de volgende richtlijnen voordat u de klasse-interface gebruikt.

Definieer een expliciete interface voor COM-clients die moeten worden gebruikt in plaats van de klasse-interface te genereren.

Omdat COM-interoperabiliteit automatisch een klasse-interface genereert, kunnen wijzigingen na de versie in uw klasse de indeling van de klasse-interface wijzigen die wordt weergegeven door de algemene taalruntime. Omdat COM-clients doorgaans niet voorbereid zijn om wijzigingen in de indeling van een interface te verwerken, breken ze af als u de indeling van de klasse wijzigt.

Deze richtlijn versterkt het idee dat interfaces die worden blootgesteld aan COM-clients onveranderbaar moeten blijven. Als u het risico wilt beperken dat COM-clients worden onderbroken door de interface-indeling per ongeluk opnieuw te ordenen, moet u alle wijzigingen in de klasse isoleren van de interface-indeling door expliciet interfaces te definiëren.

Gebruik classInterfaceAttribute om de automatische generatie van de klasse-interface los te koppelen en een expliciete interface voor de klasse te implementeren, zoals in het volgende codefragment wordt weergegeven:

<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
    Implements IExplicit
    Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
    int IExplicit.M() { return 0; }
}

De waarde ClassInterfaceType.None voorkomt dat de klasse-interface wordt gegenereerd wanneer de klassemetagegevens worden geëxporteerd naar een typebibliotheek. In het voorgaande voorbeeld hebben COM-clients alleen toegang tot de LoanApp klasse via de IExplicit interface.

Vermijd verzendings-id's in cache (DispIds)

Het gebruik van de klasse-interface is een acceptabele optie voor gescripte clients, Microsoft Visual Basic 6.0-clients of een late gebonden client die de DispIds van interfaceleden niet in de cache opgeslagen. DispIds identificeren interfaceleden om late binding in te schakelen.

Voor de klasse-interface is het genereren van DispIds gebaseerd op de positie van het lid in de interface. Als u de volgorde van het lid wijzigt en de klasse exporteert naar een typebibliotheek, wijzigt u de DispIds die zijn gegenereerd in de klasse-interface.

Pas de ClassInterfaceAttribute toe met de classInterfaceType.AutoDispatch-waarde om te voorkomen dat com-clients met late gebonden COM-clients worden onderbroken wanneer u de klasse-interface gebruikt. Met deze waarde wordt een interface voor alleen-verzenden geïmplementeerd, maar wordt de interfacebeschrijving uit de typebibliotheek weggelaten. Zonder een interfacebeschrijving kunnen clients dispIds niet opslaan tijdens het compileren. Hoewel dit het standaardinterfacetype voor de klasse-interface is, kunt u de kenmerkwaarde expliciet toepassen.

<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
    Implements IAnother
    Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
    public int M() { return 0; }
}

Com-clients kunnen IDispatch.GetIdsOfNames aanroepen om de DispId van een interfacelid tijdens runtime op te halen. Als u een methode op de interface wilt aanroepen, geeft u de geretourneerde DispId door als een argument aan IDispatch.Invoke.

Beperk het gebruik van de optie voor dubbele interface voor de klasse-interface.

Dubbele interfaces maken vroege en late binding mogelijk voor interfaceleden door COM-clients. Tijdens het ontwerpen en testen kan het handig zijn om de klasse-interface in te stellen op dual. Voor een beheerde klasse (en de basisklassen) die nooit worden gewijzigd, is deze optie ook acceptabel. Vermijd in alle andere gevallen dat u de klasse-interface instelt op dual.

Een automatisch gegenereerde dubbele interface kan in zeldzame gevallen geschikt zijn; echter, vaker maakt het versiegerelateerde complexiteit. COM-clients die de klasse-interface van een afgeleide klasse gebruiken, kunnen bijvoorbeeld eenvoudig breken met wijzigingen in de basisklasse. Wanneer een derde partij de basisklasse levert, valt de indeling van de klasse-interface buiten uw beheer. In tegenstelling tot een interface voor alleen verzenden biedt een dual-interface (ClassInterfaceType.AutoDual) een beschrijving van de klasse-interface in de geëxporteerde typebibliotheek. Een dergelijke beschrijving moedigt late-gebonden clients aan om DispIds tijdens het compileren in de cache op te cachen.

Zorg ervoor dat alle COM-gebeurtenismeldingen te laat zijn gebonden.

Com-typegegevens worden standaard rechtstreeks ingesloten in beheerde assembly's, waardoor primaire interop-assembly's (PIA's) niet meer nodig zijn. Een van de beperkingen van ingesloten typegegevens is echter dat het geen ondersteuning biedt voor de levering van COM-gebeurtenismeldingen door vroegtijdige vtable-aanroepen, maar alleen ondersteuning biedt voor late aanroepen IDispatch::Invoke .

Als voor uw toepassing vroege aanroepen naar com-gebeurtenisinterfacemethoden zijn vereist, kunt u de eigenschap Insluiten in Visual Studio trueinstellen op , of het volgende element in uw projectbestand opnemen:

<EmbedInteropTypes>True</EmbedInteropTypes>

Zie ook