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 innehålla en referens till CCW som exponerar det nya 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.
COM-anropsbara omslutningar är osynliga för andra klasser som körs i .NET-körningen. Deras främsta syfte är att konvertera anrop mellan hanterad och ohanterad kod. CcWs hanterar dock även objektidentiteten och objektlivslängden för de hanterade objekt som de omsluter.
Objektidentitet
Körningen allokerar minne för .NET-objektet från dess skräpinsamlings-heap, vilket gör att körningen kan flytta runt objektet i minnet efter behov. Körningen 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 omslutningen direkt.
Objektets livslängd
Till skillnad från .NET-klienten som den omsluter, refereras 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.
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 IUnknown - och IDispatch-gränssnitten .
Gränssnitt | beskrivning |
---|---|
Idispatch | Tillhandahåller en mekanism för att sen bindning ska skrivas. |
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 . |
Endast ITypeInfo (.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 som COM-klienten hanterar livslängden för CCW och tillhandahåller typtvång. |
En hanterad klass kan också tillhandahålla COM-gränssnitten som beskrivs i följande tabell.
Gränssnitt | beskrivning |
---|---|
Klassgränssnittet (_classname) | Gränssnitt, som exponeras av körningen och inte uttryckligen definierats, som exponerar alla offentliga gränssnitt, metoder, egenskaper och fält som uttryckligen exponeras på ett hanterat objekt. |
I Anslut ionPoint och I Anslut ionPointContainer | Gränssnitt för objekt som källdelegeringsbaserade händelser (ett gränssnitt för registrering av händelseprenumeranter). |
IDispatchEx (endast.NET Framework) | Gränssnitt som tillhandahålls av körningen 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. Det här gränssnittet kan vara ett dubbel- eller dispatch-only-gränssnitt. 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 automatiskt skapande av det här gränssnittet genom att tillämpa på ClassInterfaceAttribute klassen. Även om klassgränssnittet kan underlätta uppgiften att exponera hanterade klasser för COM, är dess användning begränsad.
Varning
Med hjälp av klassgränssnittet kan det i stället för att uttryckligen definiera din egen komplicera den framtida versionshantering av den hanterade klassen. 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 ett klassgränssnitt automatiskt kan ändringar efter versionen av klassen ändra layouten för klassgränssnittet som exponeras av den vanliga språkkörningen. 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 cachelagrar 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 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 senbundna klienter att cachelagrar 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 true
eller inkludera följande element i projektfilen:
<EmbedInteropTypes>True</EmbedInteropTypes>