Sdílet prostřednictvím


Obálka pro volání COM

Když klient COM volá objekt .NET, běžné jazykové prostředí (CLR) vytvoří spravovaný objekt a volatelnou obálku pro COM (CCW) pro objekt. Na objekt .NET nelze odkazovat přímo, COM klienti používají CCW jako proxy pro spravovaný objekt.

Modul runtime vytvoří pro spravovaný objekt přesně jeden objekt CCW bez ohledu na počet klientů modelu COM, kteří požadují své služby. Jak ukazuje následující obrázek, více klientů modelu COM může mít odkaz na CCW, který poskytuje rozhraní INew. CCW naopak drží jediný odkaz na spravovaný objekt, který implementuje rozhraní, a ten je uvolňován garbage collectorem. Klienti COM a .NET mohou provádět požadavky na stejný spravovaný objekt současně.

Více klientů modelu COM, kteří mají odkaz na CCW, který zpřístupňuje INew.

Obálky volatelné prostřednictvím COM jsou neviditelné pro jiné třídy spuštěné v rámci modulu runtime .NET. Jejich primárním účelem je směrování volání mezi spravovaným a nespravovaným kódem; CCWs však také spravují identitu a životnost spravovaných objektů, které jsou zabalené.

Identita objektu

Modul runtime přiděluje paměť objektu .NET z haldy, která umožňuje modulu runtime podle potřeby přesouvat objekt v paměti. Naproti tomu modul runtime přiděluje paměť pro CCW z nekompletované haldy, což umožňuje klientům MODELU COM odkazovat přímo na obálku.

Životnost objektu

Na rozdíl od klienta .NET, který obaluje, je CCW počítán tradičním způsobem modelu COM. Když počet odkazů na CCW dosáhne nuly, obálka uvolní svůj odkaz na spravovaný objekt. Objekt spravovaný bez zbývajících odkazů je sebrán během příštího cyklu sběru odpadu.

Simulace rozhraní COM

CCW zveřejňuje všechna veřejná rozhraní viditelná pro COM, datové typy a vrací hodnoty klientům COM způsobem, který je konzistentní s vynucováním interakce založené na rozhraní v COM. Pro klienta MODELU COM je vyvolání metod objektu .NET stejné jako vyvolání metod v objektu COM.

Pro vytvoření tohoto bezproblémového přístupu CCW vyrábí tradiční COM rozhraní, jako jsou IUnknown a IDispatch. Jak ukazuje následující obrázek, CCW udržuje odkaz na jeden objekt .NET, který obklopuje. COM klient a objekt .NET vzájemně komunikují prostřednictvím konstrukce proxy a zástupné procedury CCW.

Diagram znázorňující, jak CCW vytváří rozhraní COM

Kromě zveřejnění rozhraní, která jsou explicitně implementována třídou ve spravovaném prostředí, modul runtime .NET poskytuje implementace rozhraní COM uvedené v následující tabulce jménem objektu. Třída .NET může přepsat výchozí chování tím, že poskytuje vlastní implementaci těchto rozhraní. Modul runtime však vždy poskytuje implementaci pro rozhraní IUnknown a IDispatch .

Rozhraní Popis
IDispatch Poskytuje mechanismus pro pozdní vazbu k typu.
IErrorInfo Poskytuje textový popis chyby, jeho zdroj, soubor nápovědy, kontext nápovědy a identifikátor GUID rozhraní, které definovalo chybu (vždy GUID_NULL pro třídy .NET).
IProvideClassInfo Umožňuje klientům modelu COM získat přístup k rozhraní ITypeInfo implementované spravovanou třídou. Vrátí COR_E_NOTSUPPORTED v prostředí .NET Core pro typy, které nejsou importovány z COM.
ISupportErrorInfo Umožňuje klientovi modelu COM určit, zda spravovaný objekt podporuje rozhraní IErrorInfo . Pokud ano, umožní klientovi získat ukazatel na nejnovější objekt výjimky. Všechny spravované typy podporují rozhraní IErrorInfo .
ITypeInfo (pouze .NET Framework) Poskytuje informace o typu pro třídu, která je přesně stejná jako informace o typu vytvořené Tlbexp.exe.
IUnknown Poskytuje standardní implementaci rozhraní IUnknown, pomocí kterého klient COM spravuje životnost CCW a zajišťuje transformaci typů.

Spravovaná třída může také poskytovat rozhraní COM popsaná v následující tabulce.

Rozhraní Popis
Rozhraní třídy (_classname) Rozhraní vystavené modulem runtime, které není explicitně definováno, které zveřejňuje všechna veřejná rozhraní, metody, vlastnosti a pole explicitně vystavená u spravovaného objektu.
IConnectionPoint a IConnectionPointContainer Rozhraní pro objekty, které zpracovávají události založené na delegátech (rozhraní pro registraci odběratelů událostí).
IDispatchEx (pouze .NET Framework) Rozhraní dodané modulem runtime, pokud třída implementuje IExpando. Rozhraní IDispatchEx je rozšířením rozhraní IDispatch, které, na rozdíl od IDispatch, umožňuje výčet, přidání, odstranění a volání členů rozlišující malá a velká písmena.
IEnumVARIANT Rozhraní pro třídy typu kolekce, které očísluje objekty v kolekci, pokud třída implementuje IEnumerable.

Představujeme rozhraní třídy

Rozhraní třídy, které není explicitně definováno ve spravovaném kódu, je rozhraní, které zveřejňuje všechny veřejné metody, vlastnosti, pole a události, které jsou explicitně vystaveny v objektu .NET. Toto rozhraní může být duální nebo pouze-dispečerské rozhraní. Rozhraní třídy obdrží název samotné třídy .NET s podtržítkem na začátku. Například u třídy Savce je rozhraní třídy _Mammal.

Pro odvozené třídy rozhraní třídy také zveřejňuje všechny veřejné metody, vlastnosti a pole základní třídy. Odvozená třída také zveřejňuje rozhraní třídy pro každou základní třídu. Pokud například třída Savce rozšiřuje třídu SavceSuperclass, která sama rozšiřuje System.Object, objekt .NET zveřejňuje klientům MODELU COM tři rozhraní třídy s názvem _Mammal, _MammalSuperclass a _Object.

Představte si například následující třídu .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() {}
}

COM klient může získat ukazatel na rozhraní třídy nazvané _Mammal. V rozhraní .NET Framework můžete pomocí nástroje Pro export typů knihovny (Tlbexp.exe) vygenerovat knihovnu typů obsahující definici _Mammal rozhraní. Exportér knihovny typů není v .NET Core podporován. Mammal Pokud třída implementovala jedno nebo více rozhraní, rozhraní by se zobrazila v rámci třídy coclass.

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

Generování rozhraní třídy je volitelné. Ve výchozím nastavení generuje COM interop pro každou třídu, kterou exportujete do knihovny typů, rozhraní pouze pro odesílání zpráv. Automatické vytváření tohoto rozhraní můžete zabránit nebo upravit použitím třídy ClassInterfaceAttribute . I když rozhraní třídy může usnadnit úlohu vystavení spravovaných tříd modelu COM, jeho použití jsou omezené.

Upozornění

Použití rozhraní třídy, místo explicitního definování vlastní, může komplikovat budoucí správu verzí spravované třídy. Před použitím rozhraní třídy si přečtěte následující pokyny.

Definujte explicitní rozhraní pro klienty MODELU COM, které se mají použít místo generování rozhraní třídy.

Vzhledem k tomu, že interop modelu COM generuje rozhraní třídy automaticky, změny po verzi třídy mohou změnit rozložení rozhraní třídy vystavené modulem CLR (Common Language Runtime). Vzhledem k tomu, že klienti modelu COM jsou obvykle nepřipraveni ke zpracování změn v rozložení rozhraní, přeruší se, pokud změníte rozložení člena třídy.

Toto vodítko posiluje pojem, že rozhraní vystavená klientům modelu COM musí zůstat neměnná. Chcete-li snížit riziko narušení klientů modelu COM neúmyslným přeuspořádání rozložení rozhraní, izolujte všechny změny třídy od rozložení rozhraní explicitním definováním rozhraní.

Použijte ClassInterfaceAttribute k odpojování automatického generování rozhraní třídy a implementaci explicitního rozhraní pro třídu, jak ukazuje následující fragment kódu:

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

Hodnota ClassInterfaceType.None zabraňuje generování rozhraní třídy při exportu metadat třídy do knihovny typů. V předchozím příkladu mají klienti MODELU COM přístup ke LoanApp třídě pouze prostřednictvím IExplicit rozhraní.

Vyhněte se ukládání identifikátorů odesílání do mezipaměti (DispId)

Použití rozhraní třídy je přijatelná možnost pro skriptované klienty, klienty Microsoft Visual Basic 6.0 nebo jakéhokoli opožděného klienta, který neukládá do mezipaměti DispId členů rozhraní. Identifikátory DispId identifikují členy rozhraní pro umožnění pozdní vazby.

Pro rozhraní třídy je generování DispIds založeno na pozici člena v rozhraní. Pokud změníte pořadí člena a exportujete třídu do knihovny typů, změníte DispIds vygenerované v rozhraní třídy.

Chcete-li zabránit přerušení pozdně vázaných COM klientů při použití rozhraní třídy, použijte ClassInterfaceAttribute s hodnotou ClassInterfaceType.AutoDispatch. Tato hodnota implementuje rozhraní třídy pouze pro odesílání, ale vynechá popis rozhraní z knihovny typů. Bez popisu rozhraní klienti nemůžou v době kompilace ukládat identifikátory DispId do mezipaměti. I když se jedná o výchozí typ rozhraní pro rozhraní třídy, můžete hodnotu atributu použít explicitně.

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

Chcete-li získat DispId členu rozhraní v době běhu, mohou klienti COM volat IDispatch.GetIdsOfNames. Chcete-li vyvolat metodu v rozhraní, předejte vrácený DispId jako argument IDispatch.Invoke.

Omezte použití možnosti duálního rozhraní pro rozhraní třídy.

Duální rozhraní umožňují klientům COM včasné i pozdní vazby na členy rozhraní. Při návrhu a během testování může být užitečné nastavit rozhraní třídy na duální. Pro spravovanou třídu (a její základní třídy), která se nikdy nezmění, je tato možnost také přijatelná. Ve všech ostatních případech se vyhněte nastavení rozhraní třídy na duální.

Automaticky generované duální rozhraní může být vhodné ve výjimečných případech; ale častěji vytváří složitost související s verzí. Například klienti modelu COM používající rozhraní odvozené třídy mohou snadno selhat kvůli změnám v základní třídě. Pokud třetí strana poskytuje základní třídu, rozložení rozhraní třídy je mimo vaši kontrolu. Na rozdíl od rozhraní pouze pro odesílání poskytuje duální rozhraní (ClassInterfaceType.AutoDual) popis rozhraní třídy v exportované knihovně typů. Takový popis podporuje klienty s pozdní vazbou, aby v době kompilace ukládaly DispIds.

Ujistěte se, že všechna oznámení událostí modelu COM jsou zpožděná.

Ve výchozím nastavení jsou informace o typu modelu COM vloženy přímo do spravovaných sestavení, což eliminuje potřebu primárních sestavení vzájemné spolupráce (PIA). Jedním z omezení informací o vloženém typu je, že nepodporuje doručování oznámení událostí modelu COM voláními vtable s časnou vazbou, ale podporuje pouze opožděná IDispatch::Invoke volání.

Pokud vaše aplikace vyžaduje časně vázaná volání metod rozhraní událostí modelu COM, můžete v sadě Visual Studio nastavit vlastnost Embed Interop Types na true, nebo do souboru projektu zahrnout tento prvek:

<EmbedInteropTypes>True</EmbedInteropTypes>

Viz také