Génération de composants COM pour l'interopérabilité

Mise à jour : novembre 2007

Si vous envisagez d'écrire des applications COM à l'avenir, vous pouvez concevoir votre code pour réaliser une interopérabilité efficace avec du code managé. Grâce à une planification avancée, vous pouvez également simplifier la migration du code non managé vers du code managé.

Les recommandations suivantes résument les meilleures pratiques pour écrire des types COM qui interagissent avec du code managé.

Fournir des bibliothèques de types

Dans la plupart des cas, le Common Language Runtime nécessite des métadonnées pour tous les types, y compris les types COM. Type Library Importer (Tlbimp.exe), qui est inclus dans le Kit de développement logiciel (SDK) Windows, peut convertir les bibliothèques de types COM en métadonnées .NET Framework. Une fois que la bibliothèque de types est convertie en métadonnées, les clients managés peuvent appeler le type COM facilement. Par souci de convivialité, veillez toujours à fournir les informations de type dans une bibliothèque de types.

Vous pouvez compacter une bibliothèque de types sous la forme d'un fichier séparé ou l'incorporer en tant que ressource dans un fichier .dll, .exe, ou .ocx. De plus, vous pouvez directement générer des métadonnées, ce qui vous permet de signer les métadonnées à l'aide de la paire de clés de l'éditeur. Les métadonnées signées à l'aide d'une clé possèdent une source définitive et peuvent éviter une liaison lorsque l'appelant possède la mauvaise clé, fournissant ainsi une sécurité accrue.

Inscrire des bibliothèques de types

Pour marshaler les appels correctement, le runtime peut avoir besoin de localiser la bibliothèque de types qui décrit un type particulier. Une bibliothèque de types doit être inscrite avant que le runtime ne la voit, sauf dans le cas d'une liaison tardive.

Vous pouvez inscrire une bibliothèque de types en appelant la fonction LoadTypeLibEx de l'API Win32 de Microsoft dont l'indicateur regkind a pour valeur REGKIND_REGISTER. Regsvr32.exe inscrit automatiquement une bibliothèque de types incorporée dans un fichier .dll.

Utiliser des tableaux sécurisés au lieu de tableaux de longueur variable.

Les tableaux sécurisés COM sont autodescriptifs. En examinant le tableau sécurisé, le marshaleur de runtime peut déterminer le rang, la taille, les limites et habituellement le type du contenu du tableau au moment de l'exécution. Les tableaux de longueur variable (ou de style C) n'ont pas la même qualité autodescriptive. Par exemple, la signature de méthode non managée suivante ne fournit aucune information sur le paramètre du tableau hormis le type d'élément.

HRESULT DoSomething(int cb, [in] byte buf[]);

En fait, le tableau ne se distingue d'aucun autre paramètre passé par référence. C'est pourquoi, Tlbimp.exe ne convertit pas le paramètre du tableau de la méthode DoSomething. Le tableau apparaît plutôt comme une référence au type Byte, comme l'illustre le code suivant.

Public Sub DoSomething(cb As Integer, ByRef buf As Byte)
public void DoSomething(int cb, ref Byte buf);

Pour améliorer l'interopérabilité, vous pouvez assigner à l'argument le type SAFEARRAY dans la signature de méthode non managée. Par exemple :

HRESULT DoSomething(SAFEARRAY(byte)buf);

Tlbimp.exe convertit SAFEARRAY en type de tableau managé suivant :

Public Sub DoSomething(buf As Byte())
public void DoSomething(Byte[] buf);

Utiliser des types de données conformes à Automation

Le service marshaling du runtime prend en charge automatiquement tous les types de données conformes à Automation. Les types qui ne sont pas conformes sont ou ne sont pas pris en charge.

Fournir version et paramètres régionaux dans les bibliothèques de types.

Lorsque vous importez une bibliothèque de types, les informations de paramètres régionaux et de version de la bibliothèque de types sont également propagées à l'assembly. Les clients managés peuvent ensuite établir une liaison avec une version ou des paramètres régionaux particuliers ou avec la dernière version de l'assembly. Fournir des informations de version dans la bibliothèque de types permet aux clients de choisir avec précision quelle version de l'assembly utiliser.

Utiliser des types blittables

Les types de données sont soit blittables, soit non blittables. Les types blittables ont une représentation commune au-delà des limites d'interopérabilité. Les types entier et à virgule flottante sont blittables. Les tableaux et structures des types blittables sont également blittables. Les chaînes, dates et objets sont des exemples de types non blittables qui sont convertis durant le processus de marshaling.

Les types blittables et non blittables sont pris en charge par le service marshaling d'interopérabilité ; cependant, les types qui nécessitent une conversion lors du marshaling ne s'exécutent pas aussi bien que les types blittables. Lorsque vous utilisez des types non blittables, gardez à l'esprit que leur marshaling entraîne une charge mémoire supplémentaire.

Les chaînes sont particulièrement problématiques. Les chaînes managées sont stockées comme caractères Unicode et de ce fait peuvent être marshalées beaucoup plus efficacement vers du code non managé qui attend des arguments sous la forme de caractères Unicode. Il est préférable d'éviter des chaînes composées de caractères ANSI si possible.

Implémenter IProvideClassInfo

Lors du marshaling d'interfaces non managées vers du code managé, le runtime crée un wrapper d'un type spécifique. La signature de méthode indique généralement le type de l'interface, mais le type de l'objet qui implémente l'interface peut ne pas être connu. Si le type d'objet est inconnu, le runtime enveloppe l'interface avec un wrapper objet COM générique qui est moins fonctionnel que les wrappers de type spécifique.

Examinons, par exemple, la signature de méthode COM suivante :

interface INeedSomethng {
   HRESULT DoSomething(IBiz *pibiz);
}

Lors de son importation, la méthode est convertie comme suit :

Interface INeedSomething
   Sub DoSomething(pibiz As IBiz)
End Interface
interface INeedSomething {
   void DoSomething(IBiz pibiz);
}

Si vous passez un objet managé qui implémente l'interface INeedSomething vers l'interface IBiz, le marshaleur d'interopérabilité tente d'envelopper l'interface avec un wrapper d'objet d'un type spécifique lors de l'introduction initiale de IBiz au code managé. Pour identifier le type correct de wrapper, le marshaleur doit connaître le type de l'objet qui implémente l'interface. L'une des méthodes permettant au marshaleur de déterminer le type objet est d'interroger l'interface IProvideClassInfo. Si l'objet implémente IProvideClassInfo, le marshaleur détermine le type de l'objet et enveloppe l'interface dans un wrapper typé.

Utiliser des appels modulaires

Le marshaling de données entre du code managé et non managé a un coût. Vous pouvez réduire ce coût en réduisant les transitions au-delà des limites. Les interfaces qui minimisent le nombre de transitions fonctionnent généralement mieux que les interfaces qui dépassent souvent les limites, exécutant des tâches mineures à chaque va et vient.

Utiliser les valeurs HRESULT d'échec prudemment

Lorsqu'un client managé appelle un objet COM, le runtime mappe les valeurs HRESULT d'échec de l'objet COM aux exceptions que le marshaleur lève au retour de l'appel. Le modèle d'exception managé a été optimisé pour les cas sans exception ; il n'existe pratiquement aucune charge mémoire associée au fait d'intercepter des exceptions lorsque celles-ci ne se produisent pas. Inversement, lorsqu'une exception se produit, l'interception de cette exception peut s'avérer coûteuse.

Utilisez les exceptions modérément et évitez de retourner des valeurs HRESULT d'échec à des fins d'information. Réservez les valeurs HRESULT d'échec pour les situations avec exceptions. Il est important de comprendre que l'utilisation excessive des valeurs HRESULT d'échec risque d'affecter les performances.

Libérer des ressources externes explicitement

Certains objets utilisent des ressources externes durant leur durée de vie ; une connexion de base de données, par exemple, peut mettre à jour un jeu d'enregistrements. Généralement, un objet est rattaché à une ressource externe pendant la durée de vie de cette ressource, tandis qu'une libération explicite peut retourner la ressource immédiatement. Par exemple, vous pouvez utiliser la méthode Close sur un fichier objet au lieu de fermer le fichier dans le destructeur de classe ou avec IUnknown.Release. En fournissant un équivalent à la méthode Close dans votre code, vous pouvez libérer la ressource de fichier externe bien que l'objet fichier continue à exister.

Éviter de redéfinir les types non managés

La manière correcte d'implémenter une interface COM existante dans du code managé est de commencer par importer la définition de l'interface à l'aide de Tlbimp.exe ou d'une API équivalente. Les métadonnées résultantes fournissent une définition compatible de l'interface COM (même IID, même DispIds, etc.).

Évitez de redéfinir les interfaces COM manuellement dans du code managé. Cette tâche prend du temps et produit rarement une interface managée compatible avec l'interface COM existante. Utilisez plutôt Tlbimp.exe pour garantir la compatibilité des définitions.

Évitez l'utilisation de valeurs HRESULT de succès

Intercepter des exceptions est la façon la plus naturelle que les applications managées possèdent pour traiter les situations d'erreur. Pour rendre l'utilisation de types COM transparente, le runtime lève automatiquement une exception à chaque fois qu'une méthode COM retourne une valeur HRESULT d'échec.

Si votre objet COM retourne une valeur HRESULT de succès, le runtime retourne la valeur qui se trouve dans le paramètre retval. Par défaut, la valeur HRESULT est ignorée entravant ainsi l'examen de la valeur d'une valeur HRESULT de succès par le client managé. Bien que vous puissiez conserver un HRESULT à l'aide de l'attribut PreserveSigAttribute, le procédé demande des efforts. Vous devez ajouter l'attribut manuellement à un assembly généré à l'aide de Tlbimp.exe ou d'une API équivalente.

Il est préférable d'éviter les valeurs HRESULT de succès autant que possible. Vous pouvez plutôt retourner des informations sur l'état d'un appel via un paramètre Out.

Éviter l'utilisation des fonctions modules

Les bibliothèques de types peuvent contenir des fonctions définies sur un module. Généralement, vous utilisez ces fonctions pour fournir des informations de type pour les points d'entrée DLL. Tlbimp.exe n'importe pas ces fonctions.

Évitez l'utilisation des membres de System.Object dans les interfaces par défaut

Les clients managés et les coclasses COM interagissent à l'aide de wrappers fournis par le runtime. Lorsque vous importez un type COM, le processus de conversion ajoute toutes les méthodes de l'interface par défaut de la coclasse à la classe wrapper, qui dérive de la classe System.Object. Veillez à nommer les membres de l'interface par défaut de manière à éviter tout conflit d'affectation de nom avec les membres de System.Object. Si un conflit se produit, la méthode importée remplace la méthode de classe de base.

Cette action peut s'avérer profitable si la méthode de l'interface par défaut et la méthode de System.Object fournissent la même fonctionnalité. Elle peut par contre s'avérer problématique si les méthodes de l'interface par défaut sont utilisées de manière imprévue. Pour éviter des conflits d'attribution de noms, évitez d'utiliser les noms suivants dans les interfaces par défaut : Object, Equals, Finalize, GetHashCode, GetType, MemberwiseClone et ToString.

Voir aussi

Référence

Type Library Importer (Tlbimp.exe)

Autres ressources

Considérations de design pour l'interopérabilité