Partager via


Qualifier les types .NET pour l’interopérabilité COM

Exposer les types .NET à COM

Si vous envisagez d’exposer des types dans un assembly à des applications COM, tenez compte des exigences de l’interopérabilité COM au moment du design. Les types managés (classe, interface, structure et énumération) s’intègrent en toute transparence aux types COM lorsque vous respectez les instructions suivantes :

  • Les classes doivent implémenter explicitement des interfaces.

    Bien que l’interopérabilité COM fournit un mécanisme permettant de générer automatiquement une interface contenant tous les membres de la classe et les membres de sa classe de base, il est bien préférable de fournir des interfaces explicites. L’interface générée automatiquement est appelée interface de classe. Pour obtenir des instructions, consultez Présentation de l’interface de classe.

    Vous pouvez utiliser Visual Basic, C# et C++ pour incorporer des définitions d’interface dans votre code, au lieu d’utiliser IDL (Interface Definition Language) ou son équivalent. Pour plus d’informations sur la syntaxe, consultez la documentation de votre langue.

  • Les types managés doivent être publics.

    Seuls les types publics d'un assembly sont inscrits et exportés vers la bibliothèque de types. Par conséquent, seuls les types publics sont visibles par COM.

    Les types managés exposent des fonctionnalités à d’autres codes managés qui peuvent ne pas être exposés à COM. Par exemple, les constructeurs paramétrables, les méthodes statiques et les champs constants ne sont pas exposés aux clients COM. De plus, à mesure que l'environnement d'exécution gère les données à l'intérieur et à l'extérieur d'un type, celles-ci peuvent être copiées ou transformées.

  • Les méthodes, les propriétés, les champs et les événements doivent être publics.

    Les membres des types publics doivent également être publics s’ils doivent être visibles par COM. Vous pouvez restreindre la visibilité d’un assembly, d’un type public ou de membres publics d’un type public en appliquant le ComVisibleAttribute. Par défaut, tous les types et membres publics sont visibles.

  • Les types doivent avoir un constructeur sans paramètre public à activer à partir de COM.

    Les types gérés et publics sont visibles par COM. Toutefois, sans constructeur sans paramètre public (constructeur sans argument), les clients COM ne peuvent pas créer le type. Les clients COM peuvent toujours utiliser le type s’il est activé par d’autres moyens.

  • Les types ne peuvent pas être abstraits.

    Ni les clients COM ni les clients .NET ne peuvent créer de types abstraits.

En cas d’exportation vers COM, la hiérarchie d’héritage d’un type managé est aplatie. Le contrôle de version diffère également entre les environnements managés et non managés. Les types exposés à COM n’ont pas les mêmes caractéristiques de contrôle de version que d’autres types managés.

Consommer des types COM à partir de .NET

Si vous envisagez d’utiliser des types COM à partir de .NET et que vous ne souhaitez pas utiliser d’outils comme Tlbimp.exe (Importateur de bibliothèque de types), vous devez suivre ces instructions :

  • Les interfaces doivent avoir ComImportAttribute appliqué.
  • Les interfaces doivent être appliquées GuidAttribute avec l’ID d’interface de l’interface COM.
  • Les interfaces doivent avoir l’application InterfaceTypeAttribute pour spécifier le type d’interface de base de cette interface (IUnknownou IDispatchIInspectable).
    • L'option par défaut consiste à avoir le type de base IDispatch et à ajouter les méthodes déclarées à la table de fonctions virtuelles attendue pour l'interface.
    • Seul .NET Framework prend en charge la spécification d’un type de base de IInspectable.

Ces instructions fournissent les conditions minimales requises pour les scénarios courants. De nombreuses autres options de personnalisation existent et sont décrites dans Application d’attributs d’interopérabilité.

Définir des interfaces COM dans .NET

Lorsque le code .NET tente d’appeler une méthode sur un objet COM via une interface avec l’attribut ComImportAttribute , il doit créer une table de fonctions virtuelles (également appelée vtable ou vftable) pour former la définition .NET de l’interface pour déterminer le code natif à appeler. Ce processus est complexe. Les exemples suivants montrent quelques cas simples.

Considérez une interface COM avec quelques méthodes :

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

Pour cette interface, le tableau suivant décrit sa disposition de table de fonctions virtuelles :

IComInterface emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2

Chaque méthode est ajoutée à la table de fonctions virtuelles dans l’ordre dans lequel elle a été déclarée. L’ordre particulier est défini par le compilateur C++, mais pour des cas simples sans surcharges, l’ordre de déclaration définit l’ordre dans la table.

Déclarez une interface .NET qui correspond à cette interface comme suit :

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

Spécifie InterfaceTypeAttribute l’interface de base. Il fournit quelques options :

Valeur ComInterfaceType Type d’interface de base Comportement des membres sur l’interface attribuée
InterfaceIsIUnknown IUnknown La table de fonctions virtuelles possède d’abord les membres de IUnknown, puis les membres de cette interface dans l’ordre de déclaration.
InterfaceIsIDispatch IDispatch Les membres ne sont pas ajoutés à la table de fonctions virtuelles. Ils ne sont accessibles qu’à l’aide de IDispatch.
InterfaceIsDual IDispatch La table de fonctions virtuelles possède d’abord les membres de IDispatch, puis les membres de cette interface dans l’ordre de déclaration.
InterfaceIsIInspectable IInspectable La table de fonctions virtuelles possède d’abord les membres de IInspectable, puis les membres de cette interface dans l’ordre de déclaration. Prise en charge uniquement sur .NET Framework.

Héritage d’interface COM et .NET

Le système COM Interop qui utilise ComImportAttribute n'interagit pas avec l'héritage d'interface, ce qui peut entraîner un comportement inattendu, à moins que certaines mesures d'atténuation appropriées ne soient mises en place.

Le générateur de source COM qui utilise l’attribut interagit avec l’héritage System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute de l’interface, de sorte qu’il se comporte plus comme prévu.

Héritage d’interface COM en C++

En C++, les développeurs peuvent déclarer des interfaces COM qui dérivent d’autres interfaces COM comme suit :

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

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

Ce style de déclaration est régulièrement utilisé comme mécanisme pour ajouter des méthodes à des objets COM sans modifier les interfaces existantes, ce qui constituerait un changement perturbateur. Ce mécanisme d’héritage entraîne les dispositions de la table de fonctions virtuelles suivantes :

IComInterface emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Par conséquent, il est facile d’appeler une méthode définie à IComInterface partir d’un IComInterface2*. Plus précisément, l'appel d'une méthode sur une interface de base ne nécessite pas d'utiliser QueryInterface pour obtenir un pointeur vers l'interface de base. En outre, C++ permet une conversion implicite de IComInterface2* vers IComInterface*, qui est bien définie et vous permet d’éviter d’appeler une QueryInterface nouvelle fois. Par conséquent, en C ou C++, vous n’avez jamais besoin d’appeler QueryInterface pour accéder au type de base si vous ne le souhaitez pas, ce qui peut permettre d’améliorer les performances.

Remarque

Les interfaces WinRT ne suivent pas ce modèle d’héritage. Ils sont définis pour suivre le même modèle que le [ComImport]modèle COM Interop basé dans .NET.

Héritage de l'interface avec ComImportAttribute

Dans .NET, le code C# qui ressemble à l’héritage d’interface n’est pas réellement l’héritage de l’interface. Considérez le code suivant :

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

Ce code ne dit pas « J implémente I ». Le code dit en fait « tout type qui implémente J doit également implémenter I ». Cette différence conduit à la décision de conception fondamentale qui rend l’héritage d’interface dans l’interopérabilité basée sur ComImportAttribute peu ergonomique. Les interfaces sont toujours considérées par elles-mêmes ; La liste d’interfaces de base d’une interface n’a aucun impact sur les calculs pour déterminer une table de fonctions virtuelles pour une interface .NET donnée.

Par conséquent, l’équivalent naturel de l’exemple d’interface COM C++ précédent conduit à une autre disposition de table de fonctions virtuelles.

Code C# :

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

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

Dispositions de la table de fonctions virtuelles :

IComInterface emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface2::Method3

Comme ces tables de fonctions virtuelles diffèrent de l’exemple C++, cela entraîne des problèmes graves au moment de l’exécution. La définition correcte de ces interfaces dans .NET est ComImportAttribute la suivante :

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

Au niveau des métadonnées, IComInterface2 n’implémente pas IComInterface mais spécifie uniquement que les implémenteurs de IComInterface2 doivent également implémenter IComInterface. Ainsi, chaque méthode des types d’interface de base doit être redéclarée.

Héritage d’interface avec GeneratedComInterfaceAttribute (.NET 8 et versions ultérieures)

Le générateur de source COM déclenché par GeneratedComInterfaceAttribute implémente l'héritage d'interfaces C# en tant qu'héritage d'interfaces COM, de sorte que les tables de fonctions virtuelles sont disposées comme prévu. Si vous prenez l’exemple précédent, la définition correcte de ces interfaces dans .NET est System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute la suivante :

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

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

Les méthodes des interfaces de base n'ont pas besoin d'être redéclarées et ne doivent pas être redéclarées. Le tableau suivant décrit les tables de fonctions virtuelles résultantes :

IComInterface emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
IComInterface2 emplacement de table de fonctions virtuelles Nom de la méthode
0 IUnknown::QueryInterface
1 IUnknown::AddRef
2 IUnknown::Release
3 IComInterface::Method
4 IComInterface::Method2
5 IComInterface2::Method3

Comme vous pouvez le voir, ces tables correspondent à l’exemple C++, de sorte que ces interfaces fonctionnent correctement.

Voir aussi