FAQ DCOM/OLE/ATL
FAQ DCOM/OLE/ATLConsultez toutes les FAQ
Nombre d'auteurs : 2, nombre de questions : 91, dernière mise à jour : 16 juin 2021
- J'ai trouvé une bogue dans les ATL !
- Je n'arrive pas à compiler le fichier d'un point de connexion !
- Le compilateur MIDL n'a pas généré les définitions des constantes des identificateurs d'interfaces !
- L'édition de liens de mon programme échoue, et pourtant toutes mes méthodes d'interfaces sont définies !
- Mon programme plante dès que j'effectue une notification d'événement !
- Certains des paramètres de mes méthodes d'interfaces événementielles sont corrompus !
- AtlAdvise échoue, et pourtant je ne vois pas d'erreur !
- Les méthodes de mon interface duale ne sont pas appelées lorsqu'un client m'envoie une requête Automation !
- Je n'arrive pas à créer une page de propriété pour un de mes composants !
- J'ai une erreur lors de l'enregistrement de mon composant !
- J'ai un conflit de version avec la bibliothèque ATL.DLL !
- Je n'arrive pas à créer un composant distribué en DCOM !
- Je n'arrive pas à créer un composant distribué en Visual Basic, mais j'y arrive en C++ !
- Je n'arrive pas à m'abonner aux événements d'un composant distribué
Les ATL sont une aide précieuse pour programmer des composants DCOM en C++. Ce travail de titan, sans être aussi facile qu'avec un outil comme Visual Basic, redevient faisable. Cependant, tout n'est pas si rose. En effet, il faut toujours avoir une bonne compréhension des mécanismes de DCOM sous-jacents, et parfois on a à résoudre des problèmes curieux voire incompréhensible avec les ATL.
Il faut d'abord savoir que les Wizards de Visual C++ ne sont pas exempt de bogues, loin de là. Ils peuvent parfaitement générer du code faux, voire du code qui ne compile franchement pas. La correction des erreurs de compilation et d'édition de lien est assez facile à faire, mais lorsqu'on croise ces erreurs pour la première fois, on peut s'arracher les cheveux facilement.
Ensuite, des erreurs encore plus techniques et incompréhensibles peuvent parfaitement se produire à l'exécution. Cette fois, ces erreurs proviennent souvent de fautes de manipulation de la part du programmeur, ou tout simplement d'une utilisation erronée des fonctionnalités des ATL.
La fin de ce document essaie de recenser, de manière non exhaustive, les principaux problèmes que l'on peut rencontrer lorsqu'on programme des composants avec les ATL.
Les Wizards ont parfois quelques difficultés à implémenter les points de connexions pour les interfaces événementielles dont les méthodes utilisent des passages de paramètres de type BOOL ou des passages de paramètres par référence à l'aide de pointeur. Il faut corriger manuellement le code généré par Visual C++ pour ces points de connexions. Cette correction devra être refaite à chaque fois que le point de connexion sera réimplémenté pour une interface.
Il n'est pas rare que le compilateur C++ se plaigne sur des constantes IID_XXX ou DIID_XXX, qui normalement doivent représenter la valeur du GUID des interfaces et des dispinterfaces. Ce genre de situations peut avoir trois causes :
- le programmeur a modifié une dispinterface événementielle en interface duale pour implémenter un point de connexion avec une interface duale. Il faut dans ce cas recompiler le fichier IDL et réimplémenter le point de connexion. En effet, le nom des constantes contenant le GUID pour les interfaces duales commence par IID, alors que celui utilisé pour les dispinterface commence par DIID. Si un point de connexion a déjà été implémenté, et que l'interface change de nature, il faut refaire complètement le point de connexion et retirer toutes les référencent à l'ancien point de connexion
- le programmeur utilise une interface définie avant le mot-clé library dans le fichier IDL, mais non référencé dans la définition de la bibliothèque. Il faut ajouter une référence sur cette interface ou cette dispinterface dans le corps de la définition de la type library, avec l'une des syntaxes suivantes :
interface
nom;
ou :
dispinterface nom;
En effet, le compilateur MIDL ne génère les définitions pour les interfaces dans les fichiers sources que lorsqu'elles sont référencées dans le corps de la type library - l'inclusion sur les fichier xxx.h et xxx_i.c générés par MIDL dans le fichier source principal du projet est mal faite. Il faut impérativement inclure les fichiers xxx_i.c après le fichier xxx.h correspondant. En effet, les constantes définie en portée globale dans un fichier source sont déclarées statiques par défaut si elles n'ont pas été déclarées extern avant leur définition. C'est une règle du langage C++, et elle impose que les fichiers d'en-tête, contenant les déclarations, soient toujours inclus avant la définition des constantes dans les fichiers sources.
Les Wizards ont parfois quelques difficultés à implémenter des méthodes utilisant le type BOOL ou des types déclarés constants avec le mot-clé const. Par conséquent, ils définissent des fonctions dont les signatures sont incorrectes pour ces interfaces. Le compilateur accepte ces définitions, et les considère comme des surcharges des méthodes virtuelles pures des interfaces. Ces dernières ne sont donc réellement pas implémentées. La correction est ici très simple, il suffit de corriger la liste des paramètres des méthodes définies par le Wizard.
Le point de connexion utilise une interface duale et effectue donc l'appel des clients sur l'une des méthodes custom. Le client est un client Visual Basic, ou un client qui suppose que l'interface événementielle est une dispinterface. Par conséquent, son objet cible ne dispose pas des méthodes custom de l'interface duale, et l'appel de ces méthodes plante le programme.
La solution est d'utiliser d'utiliser une dispinterface pour cette interface événementielle. Il faut modifier le fichier IDL et réimplémenter le point de connexion.
Le code des ATL utilise, au sein de la classe IDispEventImpl, une fonction d'OLE non documentée et boguée. Cette fonction a pour but de générer un appel de fonction __stdcall à partir de la description de la fonction dans une type library et d'un tableau de paramètres. C'est cette fonction qui effectue l'appel des méthodes des entrées SINK_ENTRY_EX.
La nature du bogue est la suivante : un décalage peut parfois apparaître dans la structure de la pile de la fonction événementielle appelée, lorsque l'on utilise des paramètres dont la taille n'est pas de 32 bits. Le résultat est que les paramètres reçus par la fonction événementielle sont faux, et bien entendu inutilisables. Notez que ce genre de phénomène peut également se produire si la fonction événementielle n'est pas déclarée avec les conventions d'appel __stdcall.
Pour éviter ce problème, il faut se restreindre à des paramètres 32 bits exclusivement pour les méthodes des interfaces événementielles. En pratique, Visual Basic montre l'exemple en passant tous les paramètres par pointeurs. C'est la seule solution viable, qui assure en plus la meilleure intégration avec Visual Basic.
AtlAdvise peut échouer pour de multiples raisons. La plus courante est sans doute que le composant cible ne gère pas l'interface événementielle demandée. Si les apparences sont trompeuses, c'est que l'IID ou le DIID demandé n'est pas celui de l'interface à laquelle on essaie de s'abonner.
Une autre raison, beaucoup plus obscure, est la suivante. L'abonnement à une interface événementielle d'un objet situé dans un autre appartement nécessite le marshalling de cette interface. Si aucun proxy / stub n'est enregistré pour cette interface dans la clé Interface de la base de registres, l'abonnement échouera sans renvoyer de message très clair. Cette situation peut se produire pour les interfaces custom quand la DLL contenant les facelets et les stublets n'a pas été enregistrée, ou quand l'interface est une interface dérivant de l'interface IDispatch, mais n'a pas été déclarée duale dans le fichier IDL. Cet oubli arrive souvent lorsqu'on a transformé une dispinterface événementielle en interface duale. La solution est donc soit d'enregistrer la DLL de marshalling, soit de rajouter le mot-clé dual dans les attributs de l'interface événementielle. Notez que le code de marshalling est automatiquement fourni par OLE pour les interfaces duales et les dispinterfaces.
Les implémentations des interfaces duales et des sink maps pour la réception des événements provenant d'une dispinterface utilisent la définition de cette interface dans une type library. C'est la raison pour laquelle il faut toujours communiquer le nom de la constante contenant le GUID de la type library aux classes IDispatchImpl et IDispEventImpl. Par conséquent, si la description de la dispinterface n'est pas donnée dans la type library communiquée à ces deux classes, le code des ATL ne peut pas interpréter les DISPID et les paramètres qu'il reçoit dans son implémentation de la méthode IDispatch. Ces requêtes Automation sont alors tout simplement ignorées, ce qui a pour conséquence que les méthodes des interfaces duales et les fonctions de traitement des événements Automation ne sont pas appelées.
Si ce genre de problème vous arrive, il se peut simplement que le GUID de la type library communiqué aux classes ATL ne soit pas le bon. En pratique, ce type d'erreur survient rarement, parce qu'on ne peut mettre qu'un seul fichier IDL dans un projet ATL dans Visual C++, et il n'y a donc qu'une seule type library dans ce projet. Cependant, ce peut être le cas si l'on cherche à implémenter une interface duale ou des événements Automation d'une dispinterface définies dans une type library externe. Il faut donc bien vérifier que les GUID des type libraries communiqués aux classes ATL sont corrects.
Un autre cas possible est tout simplement que vous référencez bien la type library du projet courant, mais que cette type library ne contient pas la définition de l'interface duale ou de la dispinterface qui pose problème. Ceci peut arriver si la section de library du fichier IDL ne contient aucune référence sur cette interface ou cette dispinterface. La solution dans ce cas est en général de rajouter cette référence manuellement dans la section library, ou de déplacer toutes les définitions de ces interfaces dans cette section. Une référence d'interface peut être ajoutée dans la type library du projet simplement avec une ligne comme celle-ci :
interface
nom;
ou :
dispinterface nom;
dans la section library du fichier IDL.
Les composants gérant les pages de propriétés sont aggrégés par les property frames dans OLE. Ceci implique qu'il faut que les property pages supportent l'aggrégation d'une part, et qu'elles soient dans le même appartement que la property page qui les contient d'autre part. Ce dernier point est plus difficile à contrôler dans un programme multithreadé. Une façon simple de s'en assurer est d'utiliser le modèles de threading Both pour les pages de propriétés.
Les composants ATL ont besoin, pour s'enregistrer, des données d'enregistrement contenus dans leurs ressources. Ces données sont placées dans les fichiers .RGS de votre projet. L'enregistrement peut échouer si vous avez modifié le CLSID d'un composant dans le fichier IDL et que vous n'avez pas reporté cette modification dans le fichier .RGS de ce composant.
La solution consiste ici à contrôler la cohérence entre les CLSID du fichier IDL et les CLSID des fichiers .RGS du projet, et à recompiler le tout.
Il existe plusieurs versions de la DLL ATL.DLL. Malheureusement, elles portent toutes le même nom, et sont incompatibles parce qu'elles enregistrent toutes différentes versions des mêmes composants. C'est une erreur grave dans la gestion des versions de la libraire ATL (on peut également rencontrer ce type d'erreur avec les bibliothèques des MFC).
La seule solution est de compiler les composants avec le code des ATL en statique. Contrairement au MFC, où cette solution est peu satisfaisante en raison de la taille des exécutables générés, elle convient parfaitement pour les ATL.
Pour cela, il faut compiler les serveurs en utilisant la configuration MinDependancy. Cette configuration utilise les macros _ATL_STATIC_REGISTRY et _ATL_MIN_CRT, et n'utilise pas la macro _ATL_DLL contrairement aux autres configurations. La simple correction de ces macros de compilation conditionnelle suffit à supprimer la dépendance des fichiers binaires produits envers la DLL ATL.DLL.
Notez toutefois que si vous avez ajouté le support des MFC dans votre projet, vous devrez également supprimer l'inclusion du fichier atlimpl.cpp dans le fichier StdAfx.cpp, faute de quoi vous aurez des symboles définis plusieurs fois à l'édition de liens dans les configuration DEBUG.
Les erreurs possibles sont les suivantes :
- la DLL de proxy/stub du composant distant n'est pas enregistrée sur le poste client ;
- les clés enregistrées dans la base de registres sont erronées ;
- le serveur n'est pas enregistré sur le poste distant ;
- le compte utilisateur du poste client n'a pas les droits nécessaires pour exécuter le composant sur la machine distante.
Le composant est déclaré avec le mot-clé WithEvents et le poste client ne donne pas assez de droits au compte utilisateur dans lequel le serveur distant fonctionne. Lorsqu'il crée un objet marqué avec le mot-clé WithEvents, Visual Basic crée l'objet et effectue l'abonnement aux notifications dans la foulée. Il peut donc obtenir une erreur lors de l'abonnement, et signaler une erreur bien que le composant ait effectivement été créé.
Le code C++ parvient à créer l'objet, mais l'abonnement se fait dans une deuxième étape, il est donc possible de ne pas s'apercevoir qu'il n'a pas pu être réalisé.
C'est un problème de sécurité de Windows NT. Il faut revoir les droits d'accès sur le poste client.
Une autre possibilité est que la DLL de proxy/stub de l'interface événementielle n'est pas installée sur le poste client.