Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de changer d’annuaire.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer d’annuaire.
Lorsqu’un client COM appelle un objet .NET, le Common Language Runtime crée l’objet managé et un wrapper COM pouvant être appelé (CCW) pour l’objet. Impossible de référencer directement un objet .NET, les clients COM utilisent le CCW comme proxy pour l’objet managé.
Le runtime crée exactement une ccW pour un objet managé, quel que soit le nombre de clients COM qui demandent ses services. Comme l’illustre l’illustration suivante, plusieurs clients COM peuvent contenir une référence au CCW qui expose l’interface INew. Le wrapper CCW contient quant à lui une référence à l'objet managé qui implémente l'interface et fait l'objet d'un garbage collection. Les clients COM et .NET peuvent effectuer simultanément des requêtes sur le même objet managé.
Les wrappers pouvant être appelé COM sont invisibles pour d’autres classes s’exécutant dans le runtime .NET. Leur objectif principal est de marshaler les appels entre code managé et code non managé. Cependant, les wrappers CCW gèrent également l'identité et la durée de vie des objets managés qu'ils encapsulent.
Identité d’objet
Le runtime alloue de la mémoire pour l'objet .NET depuis son tas collecté, ce qui permet au runtime de déplacer l'objet au sein de la mémoire selon les besoins. En revanche, le runtime alloue de la mémoire pour le wrapper CCW depuis un tas non collecté, permettant ainsi aux clients COM de faire directement référence au wrapper.
Durée de vie de l’objet
Contrairement au client .NET qu’il encapsule, le CCW est compté par références selon les normes traditionnelles de COM. Quand le nombre de références du wrapper CCW atteint zéro, le wrapper libère sa référence à l'objet managé. Un objet managé sans aucune référence restante sera collecté lors du prochain cycle de garbage collection.
Simulation d’interfaces COM
CCW expose toutes les interfaces publiques visibles pour COM, les types de données et les valeurs renvoyées aux clients COM d’une manière conforme à l'application par COM de l’interaction basée sur l’interface. Pour un client COM, l’appel de méthodes sur un objet .NET est identique à l’appel de méthodes sur un objet COM.
Pour créer cette approche transparente, la CCW fabrique des interfaces COM traditionnelles, telles que IUnknown et IDispatch. Comme l’illustre l’illustration suivante, le CCW conserve une référence unique sur l’objet .NET qu’il encapsule. Le client COM et l’objet .NET interagissent entre eux par le biais du proxy et de la construction stub du CCW.
En plus d’exposer les interfaces explicitement implémentées par une classe dans l’environnement managé, le runtime .NET fournit des implémentations des interfaces COM répertoriées dans le tableau suivant pour le compte de l’objet. Une classe .NET peut remplacer le comportement par défaut en fournissant sa propre implémentation de ces interfaces. Toutefois, le runtime fournit toujours l’implémentation pour les interfaces IUnknown et IDispatch .
| Interface | Descriptif |
|---|---|
| IDispatch | Fournit un mécanisme de liaison tardive au type. |
| IErrorInfo | Fournit une description textuelle de l’erreur, de sa source, d’un fichier d’aide, du contexte d’aide et du GUID de l’interface qui a défini l’erreur (toujours GUID_NULL pour les classes .NET). |
| IProvideClassInfo | Permet aux clients COM d’accéder à l’interface ITypeInfo implémentée par une classe managée. Retourne COR_E_NOTSUPPORTED sur .NET Core pour les types non importés à partir de COM. |
| ISupportErrorInfo | Permet à un client COM de déterminer si l’objet managé prend en charge l’interface IErrorInfo . Dans ce cas, permet au client d’obtenir un pointeur vers le dernier objet d’exception. Tous les types managés prennent en charge l’interface IErrorInfo . |
| ITypeInfo (.NET Framework uniquement) | Fournit des informations de type pour une classe qui est exactement la même que les informations de type produites par Tlbexp.exe. |
| IUnknown | Fournit l’implémentation standard de l’interface IUnknown avec laquelle le client COM gère la durée de vie du wrapper CCW et fournit le forçage de type. |
Une classe managée peut également fournir les interfaces COM décrites dans le tableau suivant.
| Interface | Descriptif |
|---|---|
| Interface de classe (_classname) | Interface, exposée par le runtime et non définie explicitement, qui expose toutes les interfaces publiques, méthodes, propriétés et champs qui sont explicitement exposés sur un objet managé. |
| IConnectionPoint et IConnectionPointContainer | Interface pour les objets qui émettent des événements basés sur les délégués (interface pour l'inscription des abonnés d'événements). |
| IDispatchEx (.NET Framework uniquement) | Interface fournie par le runtime si la classe implémente IExpando. L’interface IDispatchEx est une extension de l’interface IDispatch qui, contrairement à l’interface IDispatch, permet l’énumération, l’ajout, la suppression et l’appel de la casse des membres. |
| IEnumVARIANT | Interface pour les classes de type collection, qui énumère les objets de la collection si la classe implémente IEnumerable. |
Présentation de l’interface de classe
L’interface de classe, qui n’est pas explicitement définie dans le code managé, est une interface qui expose toutes les méthodes publiques, propriétés, champs et événements qui sont explicitement exposés sur l’objet .NET. Cette interface peut être double ou dispatch uniquement. L’interface de classe reçoit le nom de la classe .NET elle-même, précédée d’un trait de soulignement. Par exemple, pour la classe Mammal, l’interface de classe est _Mammal.
Pour les classes dérivées, l’interface de classe expose également toutes les méthodes, propriétés et champs publics de la classe de base. La classe dérivée expose également une interface de classe pour chaque classe de base. Par exemple, si la classe Mammal étend la classe MammalSuperclass, qui lui-même étend System.Object, l’objet .NET expose aux clients COM trois interfaces de classe nommées _Mammal, _MammalSuperclass et _Object.
Par exemple, considérez la classe .NET suivante :
' 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() {}
}
Le client COM peut obtenir un pointeur vers une interface de classe nommée _Mammal. Sur .NET Framework, vous pouvez utiliser l’outil Exporter de bibliothèque de types (Tlbexp.exe) pour générer une bibliothèque de types contenant la définition de l’interface _Mammal . L’exportateur de bibliothèque de types n’est pas pris en charge sur .NET Core. Si la Mammal classe a implémenté une ou plusieurs interfaces, les interfaces apparaissent sous la coclasse.
[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;
}
La génération de l’interface de classe est facultative. Par défaut, COM Interop génère une interface dispatch uniquement pour chaque classe que vous exportez vers une bibliothèque de types. Vous pouvez empêcher ou modifier la création automatique de cette interface en appliquant la ClassInterfaceAttribute valeur à votre classe. Bien que l’interface de classe puisse faciliter la tâche d’exposer des classes managées à COM, ses utilisations sont limitées.
Avertissement
L’utilisation de l’interface de classe, au lieu de définir explicitement votre propre, peut compliquer le contrôle de version futur de votre classe managée. Lisez les instructions suivantes avant d’utiliser l’interface de classe.
Définissez une interface explicite pour que les clients COM utilisent plutôt que de générer l’interface de classe.
Étant donné que COM Interop génère automatiquement une interface de classe, les modifications post-version apportées à votre classe peuvent modifier la disposition de l’interface de classe exposée par le Common Language Runtime. Étant donné que les clients COM ne sont généralement pas préparés pour gérer les modifications dans la disposition d’une interface, ils s’interrompent si vous modifiez la disposition membre de la classe.
Cette directive renforce la notion que les interfaces exposées aux clients COM doivent rester inchangeables. Pour réduire le risque de rupture des clients COM en réorganisant par inadvertance la disposition de l’interface, isolez toutes les modifications apportées à la classe de la disposition de l’interface en définissant explicitement des interfaces.
Utilisez ClassInterfaceAttribute pour désengager la génération automatique de l’interface de classe et implémenter une interface explicite pour la classe, comme le montre le fragment de code suivant :
<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; }
}
La valeur ClassInterfaceType.None empêche la génération de l’interface de classe lorsque les métadonnées de classe sont exportées vers une bibliothèque de types. Dans l’exemple précédent, les clients COM peuvent accéder à la LoanApp classe uniquement via l’interface IExplicit .
Éviter la mise en cache des identificateurs de distribution (DispIds)
L’utilisation de l’interface de classe est une option acceptable pour les clients scriptés, les clients Microsoft Visual Basic 6.0 ou tout client à liaison tardive qui ne met pas en cache les DispIds des membres de l’interface. Les DISPID identifient les membres d’interface pour permettre la liaison tardive.
Pour l’interface de classe, la génération de DispIds est basée sur la position du membre dans l’interface. Si vous modifiez l’ordre du membre et exportez la classe vers une bibliothèque de types, vous modifiez les DispIds générés dans l’interface de classe.
Pour éviter d’interrompre les clients COM à liaison tardive lors de l’utilisation de l’interface de classe, appliquez l'attribut ClassInterfaceAttribute avec la valeur ClassInterfaceType.AutoDispatch. Cette valeur implémente une interface de classe de dispatch, mais omet la description de l'interface de la bibliothèque de types. Sans description de l’interface, les clients ne peuvent pas mettre en cache les dispIds au moment de la compilation. Bien qu’il s’agit du type d’interface par défaut de l’interface de classe, vous pouvez appliquer explicitement la valeur d’attribut.
<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; }
}
Pour obtenir le DispId d’un membre d’interface au moment de l’exécution, les clients COM peuvent appeler IDispatch.GetIdsOfNames. Pour appeler une méthode sur l’interface, passez le DispId retourné en tant qu’argument à IDispatch.Invoke.
Limitez l’utilisation de l’option double interface pour l’interface de classe.
Les interfaces doubles permettent une liaison anticipée et tardive aux membres d’interface par les clients COM. Au moment de la conception et pendant les tests, il peut être utile de définir l’interface de classe sur double. Pour une classe managée (et ses classes de base) qui ne seront jamais modifiées, cette option est également acceptable. Dans tous les autres cas, évitez de définir l’interface de classe sur double.
Une interface double générée automatiquement peut être appropriée dans de rares cas ; toutefois, plus souvent, il crée une complexité liée à la version. Par exemple, les clients COM utilisant l’interface de classe d’une classe dérivée peuvent facilement rompre avec les modifications apportées à la classe de base. Lorsqu’un tiers fournit la classe de base, la disposition de l’interface de classe est hors de votre contrôle. En outre, contrairement à une interface dispatch-only, une interface double (ClassInterfaceType.AutoDual) fournit une description de l’interface de classe dans la bibliothèque de types exportée. Une telle description encourage les clients à liaison tardive à mettre en cache les DISPID au moment de la compilation.
Vérifiez que toutes les notifications d’événements COM sont à liaison tardive.
Par défaut, les informations de type COM sont incorporées directement dans des assemblys managés, ce qui élimine la nécessité d’assemblys d’interopérabilité principaux (PIA). Les informations de type incorporées présentent toutefois une limitation : elles ne prennent pas en charge la distribution des notifications d’événements COM par des appels vtable à liaison anticipée, mais seulement par des appels IDispatch::Invoke à liaison tardive.
Si votre application nécessite des appels à liaison précoce vers des méthodes d’interface d’événement COM, vous pouvez définir la propriété Embed Interop Types sur true, ou inclure l’élément suivant dans votre fichier projet :
<EmbedInteropTypes>True</EmbedInteropTypes>