Wywoływana otoka COM
Gdy klient modelu COM wywołuje obiekt środowiska .NET, środowisko uruchomieniowe języka wspólnego tworzy zarządzany obiekt oraz otokę wywoływaną z modelu COM (CCW) dla tego obiektu. Klienci modelu COM nie potrafią się odwoływać bezpośrednio do obiektu środowiska .NET, dlatego używają otoki CCW jako pośrednika umożliwiającego dostęp do obiektu zarządzanego.
Środowisko uruchomieniowe tworzy dla obiektu zarządzanego dokładnie jedną otokę CCW, niezależnie od liczby klientów modelu COM żądających jego usług. Jak pokazano na poniższej ilustracji, wielu klientów modelu COM może zawierać odwołanie do otoki CCW, która udostępnia interfejs INew. Z kolei otoka CCW zawiera jedno odwołanie do obiektu zarządzanego, który implementuje interfejs i podlega działaniu modułu odśmiecania pamięci. Klienci modelu COM i środowiska .NET mogą wykonywać żądania do tego samego zarządzanego obiektu równocześnie.
Zawijane otoki COM są niewidoczne dla innych klas działających w środowisku uruchomieniowym platformy .NET. Ich głównym celem jest kierowanie wywołań między kodem zarządzanym i niezarządzanym. Otoki CCW mogą jednak również zarządzać tożsamościami i okresem istnienia zarządzanych obiektów, które opakowują.
Tożsamość obiektu
Środowisko uruchomieniowe przydziela pamięć obiektowi środowiska .NET ze swojego stosu odśmieconej pamięci, co umożliwia przenoszenie obiektu w pamięci zgodnie z potrzebami. Natomiast otoce CCW środowisko uruchomieniowe przydziela pamięć ze stosu niepodlegającego odśmiecaniu pamięci, dzięki czemu klienci COM mogą się odwoływać bezpośrednio do otoki.
Okres istnienia obiektu
W odróżnieniu od klienta środowiska .NET, którego opakowuje, otoka CCW podlega zliczaniu odwołań w sposób tradycyjny dla modelu COM. Gdy liczba odwołań do otoki CCW osiągnie zero, otoka zwalnia swoje odwołanie do zarządzanego obiektu. Pamięć zajmowana przez zarządzany obiekt, do którego już nie ma żadnych odwołań, jest odzyskiwana podczas następnego cyklu wyrzucania elementów bezużytecznych.
Symulowanie interfejsów COM
CCW uwidacznia wszystkie publiczne, widoczne dla modelu COM interfejsy, typy danych i zwraca wartości do klientów COM w sposób zgodny z wymuszanie interakcji opartej na interfejsie modelu COM. W przypadku klienta COM wywoływanie metod na obiekcie platformy .NET jest identyczne z wywoływaniem metod w obiekcie COM.
Aby stworzyć to bezproblemowe podejście, CCW produkuje tradycyjne interfejsy COM, takie jak IUnknown i IDispatch. Jak pokazano na poniższej ilustracji, CCW utrzymuje pojedyncze odwołanie do obiektu platformy .NET, który opakowuje. Zarówno klient COM, jak i obiekt platformy .NET współdziałają ze sobą za pośrednictwem serwera proxy i konstrukcji wycinków CCW.
Oprócz uwidaczniania interfejsów, które są jawnie implementowane przez klasę w środowisku zarządzanym, środowisko uruchomieniowe platformy .NET dostarcza implementacje interfejsów COM wymienionych w poniższej tabeli w imieniu obiektu. Klasa .NET może zastąpić domyślne zachowanie, zapewniając własną implementację tych interfejsów. Jednak środowisko uruchomieniowe zawsze udostępnia implementację interfejsów IUnknown i IDispatch .
Interfejs | opis |
---|---|
Idispatch | Zapewnia mechanizm opóźnionego powiązania z typem. |
Ierrorinfo | Zawiera tekstowy opis błędu, jego źródła, pliku Pomocy, kontekstu Pomocy i identyfikatora GUID interfejsu, który zdefiniował błąd (zawsze GUID_NULL dla klas platformy .NET). |
IProvideClassInfo | Umożliwia klientom COM uzyskanie dostępu do interfejsu ITypeInfo zaimplementowanego przez klasę zarządzaną. Zwraca wartość COR_E_NOTSUPPORTED na platformie .NET Core dla typów, które nie są importowane z modelu COM. |
Isupporterrorinfo | Umożliwia klientowi COM określenie, czy obiekt zarządzany obsługuje interfejs IErrorInfo . Jeśli tak, umożliwia klientowi uzyskanie wskaźnika do najnowszego obiektu wyjątku. Wszystkie typy zarządzane obsługują interfejs IErrorInfo . |
ITypeInfo (tylko program .NET Framework) | Zawiera informacje o typie dla klasy, która jest dokładnie taka sama jak informacje o typie generowane przez Tlbexp.exe. |
IUnknown | Zapewnia standardową implementację interfejsu IUnknown , za pomocą którego klient COM zarządza okresem istnienia CCW i zapewnia przymus typu. |
Klasa zarządzana może również udostępnić interfejsy COM opisane w poniższej tabeli.
Interfejs | opis |
---|---|
Interfejs klasy (_classname) | Interfejs, uwidoczniony przez środowisko uruchomieniowe i niezdefiniowany, który uwidacznia wszystkie interfejsy publiczne, metody, właściwości i pola jawnie uwidocznione w obiekcie zarządzanym. |
I Połączenie ionPoint i I Połączenie ionPointContainer | Interfejs dla obiektów źródłowych zdarzeń delegowanych (interfejs do rejestrowania subskrybentów zdarzeń). |
IDispatchEx (tylko.NET Framework) | Interfejs dostarczony przez środowisko uruchomieniowe, jeśli klasa implementuje IExpando. Interfejs IDispatchEx jest rozszerzeniem interfejsu IDispatch, który w przeciwieństwie do interfejsu IDispatch umożliwia wyliczanie, dodawanie, usuwanie i wywoływanie elementów członkowskich z uwzględnieniem wielkości liter. |
Ienumvariant | Interfejs dla klas typów kolekcji, który wylicza obiekty w kolekcji, jeśli klasa implementuje IEnumerable. |
Wprowadzenie do interfejsu klasy
Interfejs klasy, który nie jest jawnie zdefiniowany w kodzie zarządzanym, to interfejs, który uwidacznia wszystkie metody publiczne, właściwości, pola i zdarzenia jawnie uwidocznione w obiekcie .NET. Ten interfejs może być interfejsem tylko z dwoma lub dyspozytorami. Interfejs klasy otrzymuje nazwę samej klasy .NET poprzedzoną podkreśleniami. Na przykład dla klasy Ssak interfejs klasy jest _Mammal.
W przypadku klas pochodnych interfejs klasy uwidacznia również wszystkie metody publiczne, właściwości i pola klasy bazowej. Klasa pochodna uwidacznia również interfejs klasy dla każdej klasy bazowej. Jeśli na przykład klasa Ssak rozszerza klasę MammalSuperclass, która rozszerza obiekt System.Object, obiekt .NET uwidacznia klientom COM trzy interfejsy klas o nazwie _Mammal, _MammalSuperclass i _Object.
Rozważmy na przykład następującą klasę .NET:
' 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() {}
}
Klient COM może uzyskać wskaźnik do interfejsu klasy o nazwie _Mammal
. W programie .NET Framework można użyć narzędzia eksportera biblioteki typów (Tlbexp.exe), aby wygenerować bibliotekę typów zawierającą definicję interfejsu _Mammal
. Eksporter biblioteki typów nie jest obsługiwany na platformie .NET Core. Mammal
Jeśli klasa zaimplementowała co najmniej jeden interfejs, interfejsy będą wyświetlane w coklasie.
[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;
}
Generowanie interfejsu klasy jest opcjonalne. Domyślnie międzyoperacyjna com generuje interfejs tylko do wysyłania dla każdej klasy eksportowanej do biblioteki typów. Możesz uniemożliwić lub zmodyfikować automatyczne tworzenie tego interfejsu, stosując element ClassInterfaceAttribute do klasy. Mimo że interfejs klasy może ułatwić zadanie uwidaczniania klas zarządzanych w modelu COM, jego zastosowania są ograniczone.
Uwaga
Użycie interfejsu klasy zamiast jawnego definiowania własnych może komplikować przyszłe przechowywanie wersji klasy zarządzanej. Przed użyciem interfejsu klasy zapoznaj się z poniższymi wskazówkami.
Zdefiniuj jawny interfejs dla klientów COM do użycia zamiast generowania interfejsu klasy.
Ponieważ interop COM automatycznie generuje interfejs klasy, zmiany po wersji w klasie mogą zmienić układ interfejsu klasy uwidacznianego przez środowisko uruchomieniowe języka wspólnego. Ponieważ klienci MODELU COM są zwykle nieprzygotowani do obsługi zmian w układzie interfejsu, przerywają one zmianę układu składowego klasy.
Wytyczne te wzmacniają pojęcie, że interfejsy narażone na klientów COM muszą pozostać niezmienione. Aby zmniejszyć ryzyko przerwania obsługi klientów COM przez przypadkowo zmianę kolejności układu interfejsu, należy wyizolować wszystkie zmiany w klasie z układu interfejsu przez jawne zdefiniowanie interfejsów.
Użyj atrybutu ClassInterfaceAttribute, aby odłączyć automatyczne generowanie interfejsu klasy i zaimplementować jawny interfejs dla klasy, jak pokazuje następujący fragment kodu:
<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; }
}
Wartość ClassInterfaceType.None uniemożliwia generowanie interfejsu klasy, gdy metadane klasy są eksportowane do biblioteki typów. W poprzednim przykładzie klienci COM mogą uzyskiwać dostęp do LoanApp
klasy tylko za pośrednictwem interfejsu IExplicit
.
Unikaj buforowania identyfikatorów wysyłania (DispIds)
Użycie interfejsu klasy jest akceptowalną opcją dla klientów skryptowych, klientów programu Microsoft Visual Basic 6.0 lub dowolnego klienta powiązanego z opóźnieniem, który nie buforuje identyfikatorów DispId elementów członkowskich interfejsu. DispIds identyfikują elementy członkowskie interfejsu w celu włączenia późnego powiązania.
W przypadku interfejsu klasy generowanie identyfikatorów DispId jest oparte na pozycji elementu członkowskiego w interfejsie. Jeśli zmienisz kolejność składowej i wyeksportujesz klasę do biblioteki typów, zmienisz identyfikatory DispId wygenerowane w interfejsie klasy.
Aby uniknąć przerywania obsługi klientów COM z późnym opóźnieniem podczas korzystania z interfejsu klasy, zastosuj atrybut ClassInterfaceAttribute z wartością ClassInterfaceType.AutoDispatch . Ta wartość implementuje interfejs klasy tylko do wysyłania, ale pomija opis interfejsu z biblioteki typów. Bez opisu interfejsu klienci nie mogą buforować identyfikatorów DispId w czasie kompilacji. Chociaż jest to domyślny typ interfejsu dla interfejsu klasy, można jawnie zastosować wartość atrybutu.
<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; }
}
Aby uzyskać identyfikator DispId elementu członkowskiego interfejsu w czasie wykonywania, klienci COM mogą wywołać interfejs IDispatch.GetIdsOfNames. Aby wywołać metodę w interfejsie, przekaż zwrócony identyfikator DispId jako argument do IDispatch.Invoke.
Ogranicz użycie opcji podwójnego interfejsu dla interfejsu klasy.
Podwójne interfejsy umożliwiają wczesne i późne wiązanie z elementami członkowskimi interfejsu przez klientów COM. W czasie projektowania i podczas testowania warto ustawić interfejs klasy na podwójne. W przypadku klasy zarządzanej (i jej klas bazowych), która nigdy nie zostanie zmodyfikowana, ta opcja jest również akceptowalna. We wszystkich innych przypadkach należy unikać ustawiania interfejsu klasy na podwójne.
W rzadkich przypadkach może być odpowiedni automatycznie wygenerowany podwójny interfejs; jednak częściej tworzy złożoność związaną z wersją. Na przykład klienci COM korzystający z interfejsu klasy pochodnej mogą łatwo przerwać zmiany w klasie bazowej. Gdy inna firma udostępnia klasę bazową, układ interfejsu klasy jest poza kontrolą. Ponadto, w przeciwieństwie do interfejsu tylko do wysyłania, podwójny interfejs (ClassInterfaceType.AutoDual) zawiera opis interfejsu klasy w wyeksportowanej bibliotece typów. Taki opis zachęca klientów z opóźnieniem do buforowania identyfikatorów DispId w czasie kompilacji.
Upewnij się, że wszystkie powiadomienia o zdarzeniach COM są ograniczone z opóźnieniem.
Domyślnie informacje o typie COM są osadzone bezpośrednio w zarządzanych zestawach, co eliminuje potrzebę podstawowych zestawów międzyoperacyjnych (PIA). Jednak jednym z ograniczeń osadzonych informacji o typie jest to, że nie obsługuje dostarczania powiadomień o zdarzeniach COM przez wczesne powiązane wywołania w formie tabeli wirtualnej, ale obsługuje tylko połączenia związane z opóźnieniem IDispatch::Invoke
.
Jeśli aplikacja wymaga wczesnych wywołań interfejsu zdarzeń COM, możesz ustawić właściwość Embed Interop Types w programie Visual Studio na true
, lub dołączyć następujący element do pliku projektu:
<EmbedInteropTypes>True</EmbedInteropTypes>