Dela via


Kvalificera .NET-typer för COM-interoperation

Exponera .NET-typer för COM

Om du tänker exponera typer i en sammansättning för COM-program bör du överväga kraven för COM-interop vid designtillfället. Hanterade typer (klass, gränssnitt, struktur och uppräkning) integreras sömlöst med COM-typer när du följer följande riktlinjer:

  • Klasser bör implementera gränssnittet explicit.

    Även om COM interop tillhandahåller en mekanism för att automatiskt generera ett gränssnitt som innehåller alla medlemmar i klassen och medlemmarna i dess basklass, är det mycket bättre att tillhandahålla explicita gränssnitt. Det automatiskt genererade gränssnittet kallas för klassgränssnittet. Riktlinjer finns i Introduktion till klassgränssnittet.

    Du kan använda Visual Basic, C# och C++ för att införliva gränssnittsdefinitioner i koden, i stället för att behöva använda Gränssnittsdefinitionsspråk (IDL) eller motsvarande. Mer information om syntax finns i språkdokumentationen.

  • Hanterade typer måste vara publika.

    Endast offentliga typer i en sammansättning registreras och exporteras till typbiblioteket. Därför är endast offentliga typer synliga för COM.

    Hanterade typer exponerar funktioner för annan hanterad kod som kanske inte exponeras för COM. Till exempel exponeras inte parametriserade konstruktorer, statiska metoder och konstanta fält för COM-klienter. Eftersom körtiden hanterar data in och ut ur en typ kan data kopieras eller transformeras.

  • Metoder, egenskaper, fält och händelser måste vara offentliga.

    Medlemmar av offentliga typer måste också vara offentliga om de ska vara synliga för COM. Du kan begränsa synligheten för en sammansättning, en offentlig typ eller offentliga medlemmar av en offentlig typ genom att tillämpa ComVisibleAttribute. Som standard visas alla offentliga typer och medlemmar.

  • Typer måste ha en offentlig parameterlös konstruktor för att aktiveras från COM.

    Hanterade, offentliga typer är synliga för COM. Men utan en offentlig parameterlös konstruktor (en konstruktor utan argument) kan COM-klienter inte skapa typen. COM-klienter kan fortfarande använda typen om den aktiveras på något annat sätt.

  • Typer kan inte vara abstrakta.

    Varken COM-klienter eller .NET-klienter kan skapa abstrakta typer.

När en hanterad typ exporteras till COM avplattas arvshierarkin. Versionshantering skiljer sig också mellan hanterade och ohanterade miljöer. Typer som exponeras för COM har inte samma versionsegenskaper som andra hanterade typer.

Använda COM-typer från .NET

Om du tänker använda COM-typer från .NET och inte vill använda verktyg som Tlbimp.exe (typbiblioteksimportör) måste du följa dessa riktlinjer:

  • Gränssnitt måste ha ComImportAttribute tillämpade.
  • Gränssnitt måste ha tillämpats GuidAttribute med gränssnitts-ID:t för COM-gränssnittet.
  • Gränssnitten ska ha InterfaceTypeAttribute tillämpats för att ange basgränssnittstypen för det här gränssnittet (IUnknown, IDispatch, eller IInspectable).
    • Standardalternativet är att ha bastypen IDispatch och lägga till de deklarerade metoderna i den förväntade virtuella funktionstabellen för gränssnittet.
    • Endast .NET Framework har stöd för att ange en bastyp av IInspectable.

De här riktlinjerna innehåller minimikraven för vanliga scenarier. Det finns många fler anpassningsalternativ och beskrivs i Tillämpa Interop-attribut.

Definiera COM-gränssnitt i .NET

När .NET-kod försöker anropa en metod på ett COM-objekt via ett gränssnitt med ComImportAttribute attributet måste den bygga upp en virtuell funktionstabell (kallas även vtable eller vftable) för att bilda .NET-definitionen för gränssnittet för att fastställa den interna koden som ska anropas. Den här processen är komplex. I följande exempel visas några enkla fall.

Överväg ett COM-gränssnitt med några metoder:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

För detta gränssnitt beskriver följande tabell dess virtuella funktionstabells layout:

IComInterface virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

Varje metod läggs till i den virtuella funktionstabellen i den ordning den deklarerades. Den specifika ordningen definieras av C++-kompilatorn, men för enkla fall utan överlagringar definierar deklarationsordningen ordningen i tabellen.

Deklarera ett .NET-gränssnitt som motsvarar det här gränssnittet enligt följande:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid(/* The IID for IComInterface */)]
interface IComInterface
{
    void Method();
    void Method2();
}

InterfaceTypeAttribute Anger basgränssnittet. Den innehåller några alternativ:

ComInterfaceType värde Basgränssnittstyp Beteende för medlemmar i det tilldelade gränssnittet
InterfaceIsIUnknown IUnknown Den virtuella funktionstabellen har först medlemmarna av IUnknown, och därefter medlemmarna i det här gränssnittet i deklarationsordning.
InterfaceIsIDispatch IDispatch Medlemmar läggs inte till i den virtuella funktionstabellen. De är endast tillgängliga via IDispatch.
InterfaceIsDual IDispatch Den virtuella funktionstabellen har först medlemmarna av IDispatch, och därefter medlemmarna i det här gränssnittet i deklarationsordning.
InterfaceIsIInspectable IInspectable Den virtuella funktionstabellen har först medlemmarna av IInspectable, och därefter medlemmarna i det här gränssnittet i deklarationsordning. Stöds endast på .NET Framework.

COM-gränssnittsarv och .NET

COM-interopsystemet som använder ComImportAttribute interagerar inte med gränssnittsarv, så det kan orsaka oväntat beteende om inte vissa mildrande åtgärder vidtas.

COM-källgeneratorn som använder System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute attributet interagerar med gränssnittsarv, så den fungerar mer som förväntat.

COM-gränssnittsarv i C++

I C++kan utvecklare deklarera COM-gränssnitt som härleds från andra COM-gränssnitt på följande sätt:

struct IComInterface : public IUnknown
{
    STDMETHOD(Method)() = 0;
    STDMETHOD(Method2)() = 0;
};

struct IComInterface2 : public IComInterface
{
    STDMETHOD(Method3)() = 0;
};

Det här deklarationsformatet används regelbundet som en mekanism för att lägga till metoder i COM-objekt utan att ändra befintliga gränssnitt, vilket skulle vara en oförenlig ändring. Den här arvsmekanismen resulterar i följande tabelllayouter för virtuella funktioner:

IComInterface virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Därför är det enkelt att anropa en metod som definierats från IComInterface en IComInterface2*. Mer specifikt kräver det inte ett anrop till QueryInterface för att få en pekare till basgränssnittet när man anropar en metod på ett basgränssnitt. Dessutom tillåter C++ en implicit konvertering från IComInterface2* till IComInterface*, som är väldefinierad och gör att du kan undvika att anropa en QueryInterface igen. I C eller C++behöver du därför aldrig anropa QueryInterface för att komma till bastypen om du inte vill, vilket kan ge vissa prestandaförbättringar.

Anmärkning

WinRT-gränssnitt följer inte den här arvsmodellen. De definieras för att följa samma modell som den [ComImport]-baserade COM-interopmodellen i .NET.

Gränssnittsarv med ComImportAttribute

I .NET är C#-kod som liknar ärvning av gränssnitt faktiskt inte ärvning av gränssnitt. Överväg följande kod:

interface I
{
    void Method1();
}
interface J : I
{
    void Method2();
}

Den här koden säger inte "J implementerar I". Koden säger faktiskt, "alla typer som implementerar J måste också implementera I." Denna skillnad leder till det grundläggande designbeslutet som gör gränssnittsbaserat arv i ComImportAttribute-baserad interop oergonomiskt. Gränssnitt beaktas alltid på egen hand. Ett gränssnitts basgränssnittslista påverkar inte några beräkningar för att fastställa en virtuell funktionstabell för ett visst .NET-gränssnitt.

Därför leder den naturliga motsvarigheten till det tidigare exemplet med C++ COM-gränssnittet till en annan tabelllayout för virtuella funktioner.

C#-kod:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

Tabelllayouter för virtuella funktioner:

IComInterface virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

Eftersom dessa virtuella funktionstabeller skiljer sig från C++-exemplet leder detta till allvarliga problem vid körtid. Rätt definition av dessa gränssnitt i .NET med ComImportAttribute är följande:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    new void Method();
    new void Method2();
    void Method3();
}

På metadatanivå IComInterface2 implementerar IComInterface inte utan anger bara att implementerare av IComInterface2 också måste implementera IComInterface. Därför måste varje metod från basgränssnittstyperna omdeklareras.

Gränssnittsarv med GeneratedComInterfaceAttribute (.NET 8 och senare)

COM-källgeneratorn som aktiveras av GeneratedComInterfaceAttribute implementerar C#-interfacearv som COM-interfacearv, så de virtuella funktionstabellerna har angetts som förväntat. Om du tar det föregående exemplet är rätt definition av dessa gränssnitt i .NET med System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute följande:

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface
{
    void Method();
    void Method2();
}

[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IComInterface2 : IComInterface
{
    void Method3();
}

Metoderna i basgränssnitten behöver inte deklareras om och bör inte deklareras om. I följande tabell beskrivs de resulterande virtuella funktionstabellerna:

IComInterface virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuellt funktionstabellfack Metodnamn
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Som du ser matchar dessa tabeller C++-exemplet, så dessa gränssnitt fungerar korrekt.

Se även