Dela via


COM-anropsbar omslutning

När en COM-klient anropar ett .NET-objekt skapar den gemensamma språkkörningen det hanterade objektet och en COM-anropsbar omslutning (CCW) för objektet. Det går inte att referera till ett .NET-objekt direkt, COM-klienter använder CCW som proxy för det hanterade objektet.

Körningen skapar exakt en CCW för ett hanterat objekt, oavsett antalet COM-klienter som begär dess tjänster. Som följande bild visar kan flera COM-klienter ha en referens till CCW som exponerar INew-gränssnittet. CCW innehåller i sin tur en enda referens till det hanterade objektet som implementerar gränssnittet och är skräpinsamling. Både COM- och .NET-klienter kan göra begäranden på samma hanterade objekt samtidigt.

Flera COM-klienter med en referens till CCW som exponerar INew.

COM-anropsbara omslutningar är osynliga för andra klasser som körs i .NET-runtime. Deras främsta syfte är att hantera anrop mellan hanterad och ohanterad kod; dock hanterar CCWs även objektidentiteten och objektlivslängden för de hanterade objekt som de omsluter.

Objektidentitet

Körmiljön allokerar minne för .NET-objektet från sin skräpinsamlande heap, vilket gör att körmiljön kan flytta objektet i minnet efter behov. Körmiljön allokerar däremot minne för CCW från en icke-samlad heap, vilket gör det möjligt för COM-klienter att referera till wrappern direkt.

Objektets livslängd

Till skillnad från .NET-klienten som den omsluter, räknas referenserna till CCW på traditionellt COM-sätt. När referensantalet på CCW når noll släpper omslutningen sin referens för det hanterade objektet. Ett hanterat objekt utan återstående referenser samlas in under nästa skräpinsamlingscykel.

Simulera COM-gränssnitt

CCW exponerar alla offentliga, COM-synliga gränssnitt, datatyper och returnerar värden till COM-klienter på ett sätt som är förenligt med COM:s tillämpning av gränssnittsbaserad interaktion. För en COM-klient är anropande metoder på ett .NET-objekt identiskt med att anropa metoder på ett COM-objekt.

För att skapa den här sömlösa metoden tillverkar CCW traditionella COM-gränssnitt, till exempel IUnknown och IDispatch. Som följande bild visar behåller CCW en enda referens för det .NET-objekt som den omsluter. Både COM-klienten och .NET-objektet interagerar med varandra via proxy- och stub-konstruktionen av CCW.

Diagram som visar hur CCW tillverkar COM-gränssnitt.

Förutom att exponera de gränssnitt som uttryckligen implementeras av en klass i den hanterade miljön tillhandahåller .NET-körningen implementeringar av COM-gränssnitten som anges i följande tabell för objektets räkning. En .NET-klass kan åsidosätta standardbeteendet genom att tillhandahålla en egen implementering av dessa gränssnitt. Körningen tillhandahåller dock alltid implementeringen för gränssnitten IUnknown och IDispatch.

Gränssnitt Beskrivning
IDispatch Tillhandahåller en mekanism för sen bindning till typer.
IErrorInfo Innehåller en textbeskrivning av felet, dess källa, en hjälpfil, hjälpkontext och GUID för gränssnittet som definierade felet (alltid GUID_NULL för .NET-klasser).
IProvideClassInfo Gör att COM-klienter kan få åtkomst till ITypeInfo-gränssnittet som implementeras av en hanterad klass. Returnerar COR_E_NOTSUPPORTED på .NET Core för typer som inte importerats från COM.
ISupportErrorInfo Gör att en COM-klient kan avgöra om det hanterade objektet stöder IErrorInfo-gränssnittet . I så fall kan klienten hämta en pekare till det senaste undantagsobjektet. Alla hanterade typer stöder IErrorInfo-gränssnittet .
ITypeInfo (endast .NET Framework) Innehåller typinformation för en klass som är exakt samma som typinformationen som produceras av Tlbexp.exe.
IUnknown Tillhandahåller standardimplementeringen av IUnknown-gränssnittet med vilken COM-klienten hanterar livscykeln för CCW och möjliggör typomvandling.

En hanterad klass kan också tillhandahålla COM-gränssnitten som beskrivs i följande tabell.

Gränssnitt Beskrivning
Klassgränssnittet (_classname) Ett gränssnitt som exponeras av körningsmiljön och inte uttryckligen definierats, och som kommer åt alla offentliga gränssnitt, metoder, egenskaper och fält som uttryckligen är tillgängliga på hanterade objekt.
IConnectionPoint och IConnectionPointContainer Gränssnitt för objekt som hanterar händelser baserade på delegering (ett gränssnitt för registrering av händelseprenumeranter).
IDispatchEx (.NET Framework endast) Gränssnitt som tillhandahålls av runtime-miljön om klassen implementerar IExpando. IDispatchEx-gränssnittet är en förlängning av IDispatch-gränssnittet som, till skillnad från IDispatch, möjliggör uppräkning, tillägg, borttagning och skiftlägeskänsliga anrop av medlemmar.
IEnumVARIANT Gränssnitt för klasser av samlingstyp, som räknar upp objekten i samlingen om klassen implementerar IEnumerable.

Introduktion till klassgränssnittet

Klassgränssnittet, som inte uttryckligen definieras i hanterad kod, är ett gränssnitt som exponerar alla offentliga metoder, egenskaper, fält och händelser som uttryckligen exponeras för .NET-objektet. Gränssnittet kan vara antingen ett dubbelgränssnitt eller endast avsett för utsändning. Klassgränssnittet tar emot namnet på själva .NET-klassen, som föregås av ett understreck. För klassen Däggdjur är till exempel klassgränssnittet _Mammal.

För härledda klasser exponerar klassgränssnittet även alla offentliga metoder, egenskaper och fält i basklassen. Den härledda klassen exponerar också ett klassgränssnitt för varje basklass. Om klassen Mammal till exempel utökar klassen MammalSuperclass, som i sig utökar System.Object, exponerar .NET-objektet för COM-klienterna tre klassgränssnitt med namnet _Mammal, _MammalSuperclass och _Object.

Tänk till exempel på följande .NET-klass:

' 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() {}
}

COM-klienten kan hämta en pekare till ett klassgränssnitt med namnet _Mammal. På .NET Framework kan du använda verktyget Typbiblioteksexportör (Tlbexp.exe) för att generera ett typbibliotek som innehåller gränssnittsdefinitionen _Mammal . Typbiblioteksexportören stöds inte på .NET Core. Mammal Om klassen implementerade ett eller flera gränssnitt visas gränssnitten under samklassen.

[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;
}

Det är valfritt att generera klassgränssnittet. Som standard genererar COM interop ett dispatch-only-gränssnitt för varje klass som du exporterar till ett typbibliotek. Du kan förhindra eller ändra det automatiska skapandet av det här gränssnittet genom att tillämpa ClassInterfaceAttribute på klassen. Även om klassgränssnittet kan underlätta uppgiften att exponera hanterade klasser för COM, är dess användning begränsad.

Försiktighet

Att använda klassgränssnittet istället för att uttryckligen definiera ditt eget kan komplicera den framtida versioneringen av din hanterade klass. Läs följande riktlinjer innan du använder klassgränssnittet.

Definiera ett explicit gränssnitt för COM-klienter som ska användas i stället för att generera klassgränssnittet.

Eftersom COM interop genererar klassens gränssnitt automatiskt kan ändringar i klassen efter versionen ändra layouten för det klassgränssnitt som exponeras av Common Language Runtime (CLR). Eftersom COM-klienter vanligtvis är oförberedda för att hantera ändringar i layouten för ett gränssnitt bryts de om du ändrar klassens medlemslayout.

Den här riktlinjen förstärker uppfattningen att gränssnitt som exponeras för COM-klienter måste förbli oföränderliga. Om du vill minska risken för att com-klienter bryts genom att oavsiktligt ändra ordning på gränssnittslayouten isolerar du alla ändringar i klassen från gränssnittslayouten genom att uttryckligen definiera gränssnitt.

Använd ClassInterfaceAttribute för att koppla från den automatiska genereringen av klassgränssnittet och implementera ett explicit gränssnitt för klassen, som följande kodfragment visar:

<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; }
}

Värdet ClassInterfaceType.None förhindrar att klassgränssnittet genereras när klassmetadata exporteras till ett typbibliotek. I föregående exempel kan COM-klienter endast komma åt LoanApp klassen via IExplicit gränssnittet.

Undvik cachelagring av sändningsidentifierare (DispIds)

Att använda klassgränssnittet är ett acceptabelt alternativ för skriptklienter, Microsoft Visual Basic 6.0-klienter eller en sen bunden klient som inte cachelagrar DispId för gränssnittsmedlemmar. DispId identifierar gränssnittsmedlemmar för att aktivera sen bindning.

För klassgränssnittet baseras genereringen av DispIds på medlemmens position i gränssnittet. Om du ändrar ordningen på medlemmen och exporterar klassen till ett typbibliotek ändrar du dispId:erna som genereras i klassgränssnittet.

Om du vill undvika att bryta sent bundna COM-klienter när du använder klassgränssnittet använder du ClassInterfaceAttribute med värdet ClassInterfaceType.AutoDispatch . Det här värdet implementerar ett klassgränssnitt för endast dispatch, men utelämnar gränssnittsbeskrivningen från typbiblioteket. Utan en gränssnittsbeskrivning kan klienterna inte cachelagra DispIds vid kompileringstillfället. Även om det här är standardgränssnittstypen för klassgränssnittet kan du uttryckligen använda attributvärdet.

<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; }
}

För att hämta en DispId för en gränssnittsmedlem vid körning kan COM-klienter anropa IDispatch.GetIdsOfNames. Om du vill anropa en metod i gränssnittet skickar du det returnerade DispId som ett argument till IDispatch.Invoke.

Begränsa användningen av alternativet med dubbla gränssnitt för klassgränssnittet.

Dubbla gränssnitt möjliggör tidig och sen bindning till gränssnittsmedlemmar från COM-klienter. Vid designtillfället och under testningen kan det vara bra att ställa in klassgränssnittet på dubbla. För en hanterad klass (och dess basklasser) som aldrig kommer att ändras är det här alternativet också acceptabelt. I alla andra fall bör du undvika att ställa in klassgränssnittet på dubbla.

Ett automatiskt genererat dubbelgränssnitt kan vara lämpligt i sällsynta fall. Men oftare skapar det versionsrelaterad komplexitet. Till exempel kan COM-klienter som använder klassgränssnittet för en härledd klass enkelt bryta med ändringar i basklassen. När en tredje part tillhandahåller basklassen är layouten för klassgränssnittet utom din kontroll. Till skillnad från ett dispatch-only-gränssnitt innehåller dessutom ett dubbelt gränssnitt (ClassInterfaceType.AutoDual) en beskrivning av klassgränssnittet i det exporterade typbiblioteket. En sådan beskrivning uppmuntrar klienter med sen bindning att cachelagra DispIds vid kompileringstillfället.

Se till att alla COM-händelsemeddelanden är sena.

Som standard bäddas COM-typinformation in direkt i hanterade sammansättningar, vilket eliminerar behovet av primära interop-sammansättningar (PIA). En av begränsningarna med inbäddad typinformation är dock att den inte stöder leverans av COM-händelsemeddelanden via tidiga vtable-anrop, utan endast stöder sena IDispatch::Invoke anrop.

Om programmet kräver tidiga anrop till COM-händelsegränssnittsmetoder kan du ange egenskapen Embed Interop Types i Visual Studio till trueeller inkludera följande element i projektfilen:

<EmbedInteropTypes>True</EmbedInteropTypes>

Se även