Udostępnij za pośrednictwem


Obiekt obsługujący wywołania COM

Gdy klient COM wywołuje obiekt platformy .NET, środowisko uruchomieniowe języka wspólnego tworzy obiekt zarządzany i opakowanie dostępne dla COM (CCW) dla obiektu. Nie można bezpośrednio odwoływać się do obiektu platformy .NET, klienci COM używają CCW jako pełnomocnika dla obiektu zarządzanego.

Środowisko uruchomieniowe tworzy dokładnie jeden CCW dla obiektu zarządzanego, niezależnie od liczby klientów COM żądających ich usług. Jak pokazano na poniższej ilustracji, wielu klientów COM może przechowywać odwołanie do CCW, który udostępnia interfejs INew. Z kolei CCW przechowuje pojedyncze odwołanie do zarządzanego obiektu, który implementuje interfejs i jest bezużyteczny. Zarówno klienci COM, jak i .NET mogą wysyłać żądania do tego samego obiektu zarządzanego jednocześnie.

Wielu klientów COM posiadających odwołanie do CCW, który ujawnia INew.

Opakowania wywoływalne COM są niewidoczne dla innych klas działających w .NET. Ich głównym celem jest przekazywanie wywołań między kodem zarządzanym a niezarządzanym; jednak CCWs zarządzają również tożsamością obiektów i czasem ich życia dla zarządzanych obiektów, które opakowują.

Tożsamość obiektu

Środowisko uruchomieniowe przydziela pamięć dla obiektu platformy .NET ze sterty oczyszczanej przez mechanizm zbierający śmieci, co umożliwia przenoszenie obiektu w pamięci, gdy jest to konieczne. Natomiast środowisko uruchomieniowe przydziela pamięć dla CCW z niepodlegającej zbieraniu sterty, dzięki czemu klienci COM mogą bezpośrednio odwoływać się do opakowania.

Okres istnienia obiektu

W przeciwieństwie do opakowywanego klienta .NET, CCW jest oparty na liczniku odwołań w tradycyjny sposób COM. Gdy liczba odwołań w CCW osiągnie zero, opakowanie zwalnia odwołanie do obiektu zarządzanego. Obiekt zarządzany bez pozostałych odwołań jest zbierany podczas następnego cyklu zbierania śmieci.

Symulowanie interfejsów COM

CCW udostępnia wszystkie publiczne, widoczne dla modelu COM interfejsy, typy danych i wartości zwracane klientom COM w sposób zgodny z wymuszaniem 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 przedstawiono na poniższej ilustracji, CCW utrzymuje pojedyncze odwołanie do obiektu .NET, który opakowuje. Zarówno klient COM, jak i obiekt .NET komunikują się za pośrednictwem serwera proxy oraz konstrukcji zdalnych wywołań CCW.

Diagram pokazujący, jak CCW produkuje interfejsy COM.

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 COR_E_NOTSUPPORTED na platformie .NET Core dla typów niepochodzących z 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 dla platformy .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.
IConnectionPoint i IConnectionPointContainer Interfejs dla obiektów emitujących zdarzenia delegowane (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 podwójnym lub tylko dyspozytorskim. 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 Eksporter 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.

Ostrzeżenie

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 wprowadzone po aktualizacji wersji w klasie mogą zmienić układ interfejsu klasy ujawnianego przez wspólne środowisko wykonawcze języka. Ponieważ klienci COM są zazwyczaj nieprzygotowani do obsługi zmian w układzie interfejsu, ulegają awarii, jeśli zmienisz układ członków 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 Visual Basic 6.0 lub dowolnego klienta z późnym wiązaniem, który nie przechowuje DispId elementów członkowskich interfejsu. DispIds identyfikują członków interfejsu, umożliwiając późne wiązanie.

W przypadku interfejsu klasy generowanie identyfikatorów DispId opiera się na pozycji członu w interfejsie. Jeśli zmienisz kolejność elementów i wyeksportujesz klasę do biblioteki typów, zmienisz wygenerowane identyfikatory DispIds 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 podczas 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ć DispId elementu członkowskiego interfejsu w czasie wykonywania, klienci COM mogą wywołać 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 powiązanie z elementami 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.

Automatycznie wygenerowany podwójny interfejs może być odpowiedni w rzadkich przypadkach; jednak częściej powoduje skomplikowanie związane z wersjami. Na przykład klienci COM korzystający z interfejsu klasy pochodnej mogą łatwo napotkać problemy, gdy nastąpią 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 wywołania z wczesnym wiązaniem przez tablicę wirtualną (vtable), ale obsługuje tylko połączenia z późnym wiązaniem 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>

Zobacz także