Delen via


In aanmerking komende .NET-typen voor COM-interoperation

.NET-typen beschikbaar maken voor COM

Als u typen in een assembly beschikbaar wilt maken voor COM-toepassingen, moet u rekening houden met de vereisten van COM-interop tijdens het ontwerp. Beheerde typen (klasse, interface, structuur en opsomming) kunnen naadloos worden geïntegreerd met COM-typen wanneer u voldoet aan de volgende richtlijnen:

  • Klassen moeten expliciet interfaces implementeren.

    Hoewel COM-interoperabiliteit een mechanisme biedt voor het automatisch genereren van een interface met alle leden van de klasse en de leden van de basisklasse, is het veel beter om expliciete interfaces te bieden. De automatisch gegenereerde interface wordt de klasse-interface genoemd. Zie Inleiding tot de klasse-interface voor richtlijnen.

    U kunt Visual Basic, C# en C++ gebruiken om interfacedefinities in uw code op te nemen in plaats van dat u Interface Definition Language (IDL) of het equivalent ervan moet gebruiken. Zie de taaldocumentatie voor syntaxisdetails.

  • Beheerde typen moeten openbaar zijn.

    Alleen publieke typen in een assembly worden geregistreerd en geëxporteerd naar de typebibliotheek. Hierdoor zijn alleen openbare typen zichtbaar voor COM.

    Beheerde typen maken functies beschikbaar voor andere beheerde code die mogelijk niet beschikbaar zijn voor COM. Zo worden geparameteriseerde constructors, statische methoden en constante velden niet blootgesteld aan COM-clients. Verder, wanneer de runtime gegevens in en uit een type doorgeeft, kunnen de gegevens worden gekopieerd of getransformeerd.

  • Methoden, eigenschappen, velden en gebeurtenissen moeten openbaar zijn.

    Leden van publieke typen moeten ook publiek zijn als ze zichtbaar zijn voor COM. U kunt de zichtbaarheid van een assembly, een openbaar type of openbare leden van een openbaar type beperken door de ComVisibleAttribute. Standaard zijn alle publieke typen en leden zichtbaar.

  • Typen moeten een openbare parameterloze constructor hebben die moet worden geactiveerd vanuit COM.

    Beheerde, openbare typen zijn zichtbaar voor COM. Zonder een openbare parameterloze constructor (een constructor zonder argumenten) kunnen COM-clients het type echter niet maken. COM-clients kunnen het type nog steeds gebruiken als het op een andere manier wordt geactiveerd.

  • Typen kunnen niet abstract zijn.

    COM-clients en .NET-clients kunnen geen abstracte typen maken.

Wanneer deze wordt geëxporteerd naar COM, wordt de overervingshiërarchie van een beheerd type afgevlakt. Versiebeheer verschilt ook tussen beheerde en onbeheerde omgevingen. Typen die aan COM worden blootgesteld, hebben niet dezelfde versiebeheerkenmerken als andere beheerde typen.

COM-typen van .NET gebruiken

Als u COM-typen van .NET wilt gebruiken en u geen hulpprogramma's zoals Tlbimp.exe (Type Library Importer) wilt gebruiken, moet u de volgende richtlijnen volgen:

  • Interfaces moeten de ComImportAttribute toegepast hebben.
  • Interfaces moet GuidAttribute worden toegepast met de Interface-ID voor de COM-interface.
  • Interfaces moeten de InterfaceTypeAttribute toegewezen krijgen om het basisinterfacetype van deze interface speciferen (IUnknown, IDispatch, of IInspectable).
    • nl-NL: De standaardoptie is om het basistype IDispatch te gebruiken en de gedeclareerde methoden toe te voegen aan de verwachte virtuele functietabel voor de interface.
    • Alleen .NET Framework biedt ondersteuning voor het opgeven van een basistype.IInspectable

Deze richtlijnen bieden de minimale vereisten voor veelvoorkomende scenario's. Er zijn nog veel meer aanpassingsopties en worden beschreven in Interop-kenmerken toepassen.

COM-interfaces definiëren in .NET

Wanneer .NET-code een methode op een COM-object probeert aan te roepen via een interface met het ComImportAttribute kenmerk, moet er een virtuele functietabel (ook wel vtable of vftable genoemd) worden gemaakt om de .NET-definitie van de interface te vormen om de systeemeigen code te bepalen die moet worden aangeroepen. Dit proces is complex. In de volgende voorbeelden ziet u enkele eenvoudige gevallen.

Overweeg een COM-interface met een paar methoden:

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

Voor deze interface beschrijft de volgende tabel de indeling van de virtuele functietabel:

IComInterface virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

Elke methode wordt toegevoegd aan de virtuele functietabel in de volgorde waarin deze is gedeclareerd. De specifieke volgorde wordt gedefinieerd door de C++-compiler, maar voor eenvoudige gevallen zonder overbelasting definieert de declaratievolgorde de volgorde in de tabel.

Declareer als volgt een .NET-interface die overeenkomt met deze interface:

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

Hiermee InterfaceTypeAttribute geeft u de basisinterface op. Het biedt een aantal opties:

ComInterfaceType waarde Basisinterfacetype Gedrag voor leden op de toegewezen interface
InterfaceIsIUnknown IUnknown De tabel met virtuele functies heeft eerst de leden van IUnknownen vervolgens de leden van deze interface in declaratievolgorde.
InterfaceIsIDispatch IDispatch Leden worden niet toegevoegd aan de virtuele functietabel. Ze zijn alleen toegankelijk via IDispatch.
InterfaceIsDual IDispatch De tabel met virtuele functies heeft eerst de leden van IDispatchen vervolgens de leden van deze interface in declaratievolgorde.
InterfaceIsIInspectable IInspectable De tabel met virtuele functies heeft eerst de leden van IInspectableen vervolgens de leden van deze interface in declaratievolgorde. Alleen ondersteund op .NET Framework.

COM-interface-erfenis en .NET

Het COM-interoperabiliteitssysteem dat de ComImportAttribute gebruikt, werkt niet samen met interface-erfenis, wat onverwacht gedrag kan veroorzaken, tenzij er enkele verzachtende stappen worden ondernomen.

De COM-brongenerator die gebruikmaakt van het System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute kenmerk communiceert wel met interfaceovername, dus gedraagt het zich meer zoals verwacht.

COM-interfaceovername in C++

In C++kunnen ontwikkelaars COM-interfaces declareren die zijn afgeleid van andere COM-interfaces:

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

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

Deze declaratiestijl wordt regelmatig gebruikt als mechanisme om methoden toe te voegen aan COM-objecten zonder bestaande interfaces te wijzigen, wat een belangrijke wijziging zou zijn. Dit overnamemechanisme resulteert in de volgende indelingen voor virtuele functietabellen:

IComInterface virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Hierdoor is het eenvoudig om een methode aan te roepen die is gedefinieerd IComInterface vanuit een IComInterface2*. Het aanroepen van een methode op een basisinterface vereist geen aanroep om QueryInterface een aanwijzer naar de basisinterface te verkrijgen. Daarnaast staat C++ een impliciete conversie toe van IComInterface2* naar IComInterface*, wat goed is gedefinieerd en waarmee u kunt voorkomen dat u een QueryInterface opnieuw aanroept. Als gevolg hiervan hoeft u in C of C++ nooit aan te roepen QueryInterface om naar het basistype te gaan als u dat niet wilt, wat prestatieverbeteringen kan toestaan.

Opmerking

WinRT-interfaces volgen dit overnamemodel niet. Ze zijn gedefinieerd om hetzelfde model te volgen als het [ComImport]-gebaseerde COM-interopmodel in .NET.

Erfelijkheid van interface met ComImportAttribute

In .NET lijkt C#-code die op interface-erfelijkheid lijkt, niet daadwerkelijk op interface-erfelijkheid. Houd rekening met de volgende code:

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

Deze code zegt niet: 'J implementeert I'. De code zegt eigenlijk: "elk type dat J implementeert, moet ook I implementeren." Dit verschil leidt tot de fundamentele ontwerpbeslissing die het erven van interfaces bij ComImportAttribute-gebaseerde interop onpraktisch maakt. Interfaces worden altijd zelfstandig beschouwd; De basisinterfacelijst van een interface heeft geen invloed op berekeningen om een virtuele functietabel voor een bepaalde .NET-interface te bepalen.

Als gevolg hiervan leidt het natuurlijke equivalent van het vorige C++ COM-interfacevoorbeeld tot een andere indeling voor virtuele functietabellen.

C#-code:

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

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

Indelingen voor virtuele functietabellen:

IComInterface virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

Aangezien deze virtuele functietabellen verschillen van het C++-voorbeeld, leidt dit tot ernstige problemen tijdens runtime. De juiste definitie van deze interfaces in .NET met ComImportAttribute is als volgt:

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

Op metagegevensniveau implementeert IComInterface2 niet IComInterface, maar geeft alleen aan dat uitvoerders van IComInterface2 ook IComInterface moeten implementeren. Daarom moet elke methode van de basisinterfacetypen opnieuw worden aangegeven.

Overname van interface met GeneratedComInterfaceAttribute (.NET 8 en hoger)

De COM-brongenerator die wordt geactiveerd door de GeneratedComInterfaceAttribute, implementeert C#-interface-overerving als COM-interface-overerving, zodat de virtuele functietabellen zoals verwacht worden ingedeeld. Als u het vorige voorbeeld gebruikt, is de juiste definitie van deze interfaces in .NET System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute als volgt:

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

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

De methoden van de basisinterfaces hoeven niet opnieuw te worden aangegeven en mogen niet opnieuw worden aangegeven. In de volgende tabel worden de resulterende virtuele functietabellen beschreven:

IComInterface virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 virtuele functietabelslot Methodenaam
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Zoals u ziet, komen deze tabellen overeen met het C++-voorbeeld, zodat deze interfaces correct werken.

Zie ook