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änssnitt 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 offentliga.

    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örningen konverterar data in och ut ur en typ kan data dessutom 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 ComVisibleAttributetillämpa . Som standard visas alla offentliga typer och medlemmar.

  • Typer måste ha en offentlig parameterlös konstruktor som ska 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 den exporteras till COM plattas arvshierarkin av en hanterad typ ut. 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änssnitten måste ha tillämpats ComImportAttribute .
  • 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 och IDispatch 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;
};

I det här gränssnittet beskriver följande tabell layout för den virtuella funktionen:

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 IUnknowni och sedan 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 IDispatchi och sedan medlemmarna i det här gränssnittet i deklarationsordning.
InterfaceIsIInspectable IInspectable Den virtuella funktionstabellen har först medlemmarna IInspectablei och sedan 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 icke-bakåtkompatibel ä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 anrop av en metod i ett basgränssnitt inget anrop till för att QueryInterface få en pekare till basgränssnittet. 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.

Kommentar

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 ser ut som gränssnittsarv faktiskt inte gränssnittsarv. Ta följande kod som exempel:

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." Den här skillnaden leder till det grundläggande designbeslutet som gör gränssnittsarv i ComImportAttribute-based interop unergonomic. 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örning. 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 redeclared.

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

COM-källgeneratorn som utlöses av GeneratedComInterfaceAttribute implementerar C#-gränssnittsarv som COM-gränssnittsarv, så de virtuella funktionstabellerna anges 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 redeclared och bör inte redeclared. 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