IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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 

 
OuvrirSommaireConcepts

Les objets OLE peuvent être copiés d'une application OLE et collés dans une autre. L'opération de copie s'effectue exactement comme une copie classique, en sélectionnant l'objet à copier (par exemple quelques lignes d'un document) et en utilisant la commande Copier. En revanche, l'opération de collage doit se faire par l'intermédiaire du menu Édition | Collage spécial. Cette option fait apparaître une boîte de dialogue permettant de choisir le format des données à coller. Il faut choisir le format Objet xxxx, qui correspond au format des objets OLE. Il est également possible de choisir le type de collage : avec liaison ou non. Si le collage se fait avec liaison, il s'agit d'une liaison OLE, sinon, c'est une intégration.

La différence entre une liaison et une intégration est importante. Dans le premier cas, l'objet n'est pas réellement collé, seul un lien vers l'objet est créé. Ceci implique plusieurs choses :

  • le document source doit avoir un nom pour que le lien soit valide. Il doit donc avoir été enregistré ;
  • on peut faire autant de lien que l'on veut sans consommer de mémoire ou de place disque, puisque les données ne sont stockées que dans le document original ;
  • le document source ne doit pas être déplacé ou effacé, sinon le lien n'est plus valide ;
  • la modification de l'objet ne peut pas se faire dans l'application qui contient le lien. Pour le modifier, il faut aller dans l'application qui a servi à créer l'objet ;
  • le contenu de l'objet doit être mis à jour lors de l'ouverture du document qui contient le lien.

Inversement, dans le cas d'une intégration, les données sont copiées dans

  • le document conteneur. Les conséquences sont les suivantes :
  • le document dans lequel on a collé le donné contient ces données, il consomme donc plus d'espace disque et de mémoire ;
  • il n'est plus nécessaire de sauvegarder les données originales, ni le document original ;
  • il n'est plus nécessaire de mettre à jour les liens, puisque les données de l'objet sont directement accessibles de son conteneur ;
  • l'objet OLE vit de manière indépendante, il peut donc être modifié et édité au sein même de son conteneur.

Pour éditer un objet OLE, il suffit de double-cliquer dessus. L'application qui a servi à créer cet objet est alors lancée. Si l'objet a été collé avec liaison, Windows bascule vers cette application et celle-ci ouvre le document source pour que l'on puisse le modifier. Si l'objet a été intégré, cette application se fond dans le conteneur et lui apporte ses fonctionnalités : on peut éditer l'objet directement à partir du conteneur. Les menus, barres d'outils et autres aspects visuels du conteneur sont modifiés pour prendre en compte les fonctionnalités de l'application qui permet d'éditer l'objet.

Créé le 9 juillet 2000  par Christian Casteyde

Les composants OLE Automation peuvent être utilisés sans avoir recours à un langage de programmation. Il n'est pas nécessaire d'utiliser des appels de fonction, on peut utiliser à la place un langage de script. Les scripts les plus courants sont VB Script, qui est dérivé de Visual Basic, et Java Script. Il est également possible d'utiliser l'Automation dans les langages de macros des logiciels (pour les logiciels Microsoft, ces langages sont en fait un langage dérivé de Visual Basic).

Les composants OLE qui gèrent l'Automation apparaissent comme des objets possédant des propriétés et des méthodes. Les propriétés peuvent être lues ou écrites, et les méthodes peuvent être appelées comme des fonctions. Si l'appel des méthodes revient à une programmation classique, l'utilisation des propriétés est une nouveauté, puisque les interfaces COM ne donnent accès qu'à des fonctions.

En réalité, les accès aux propriétés des objets OLE Automation sont traduits par OLE dans des appels de méthodes particulières. L'Automation permet donc d'utiliser les objets OLE beaucoup plus naturellement qu'avec une programmation directe. En revanche, les performances sont moins bonne que par une programmation directe, car on évite une étape d'interprétation des commandes.

Créé le 9 juillet 2000  par Christian Casteyde

Un serveur in-process est un serveur dont les composants fonctionnent dans le même processus que ses clients. En pratique, les serveurs in-process sont les serveurs DLL. L'avantage des serveurs in-process est qu'ils sont dans le même espace d'adressage que leur client. Ceci implique :

  • qu'il est possible d'accéder directement à leurs composants, et de créer ceux-ci sans passer par DCOM ;
  • que les appels des méthodes de leurs composants peuvent être effectués directement, donc plus efficacement que pour les composants des serveurs exécutables ;
  • qu'il est possible de réutiliser leurs composants à l'aide du mécanisme d'agrégation.

Toutefois, ce gain disparaît si DCOM constate une incompatibilité entre la gestion du mutithreading effectué par la DLL et celle effectuée par les autres composants du processus. Dans ce cas en effet, DCOM s'intercale et les appels ne se font plus directement.

Les inconvénients des serveurs in-process sont les suivants :

  • les erreurs dans leurs composants peuvent entraîner la terminaison du client.
Créé le 9 juillet 2000  par Christian Casteyde

Un serveur out-of-process est un serveur qui s'exécute dans un autre processus que le processus client. En pratique, ces serveurs sont des exécutables, qui enregistrent leurs composants lors de leur initialisation.

Les avantages des serveurs out-of-process sont les suivants :

  • les clients sont mis à l'abri des fautes des serveurs. La terminaison du serveur n'engendre pas la terminaison du client ;
  • les serveurs out-of-process peuvent partager des données entres les différents clients.

Les inconvénients des serveurs out-of-process sont les suivants :

  • les appels aux méthodes de leurs composants nécessite le passage des paramètres du processus client au processus serveur, ce qui est bien évidemment plus lent ;
  • ils ne peuvent pas être utilisés en tant qu'objets agrégés.

Cette dernière limitation est un handicap majeur de DCOM, qui remet en cause complètement la possibilité de réutiliser facilement les composants distribués. En fait, comme on le verra plus tard, l'agrégation ne peut être utilisée qu'entre objets vivant dans le même appartement (voire la gestion du multithreading pour plus de détails à ce sujet).

Créé le 9 juillet 2000  par Christian Casteyde

Comme on l'a vu ci-dessus, les composants et les interfaces de ces composants sont identifiés de manière unique dans le système par les GUID. En fait, les GUID sont utilisés à chaque fois que l'on a besoin d'un identificateur dont on veut être sûr qu'il est unique dans le monde et qu'il le restera. Les GUID permettent donc d'identifier de manière unique tous les objets du système afin de les référencer d'une manière complètement indépendante de tout contexte.

Pour parvenir à ce résultat, il est nécessaire que ces GUID soient affectés rigoureusement sans collision à quiconque en demande. Ceci est réalisé par l'utilitaire UUIDGEN.EXE, qui calcule un nouveau GUID à chaque fois qu'on l'utilise. Ce GUID est calculé sur des données spécifiques à la machine, sur la date courante et sur un compteur à variation très rapide. Ainsi, il est impossible de générer deux fois le même GUID dans le monde, ce pour un temps très grand.

Bien entendu, il doit y avoir un grand nombre de GUID possibles pour qu'il n'y ait aucune collision. En fait, les GUID sont des nombres à 128 bits exprimés en hexadécimal, ce qui donne un nombre total de GUID immense. Le format des GUID générés par UUIDGEN.EXE est donné par l'exemple suivant :

 
Sélectionnez
6B29FC40-CA47-1067-B31D-00DD010662DA

Lorsque ces GUID sont stockés dans la base de registres, ils apparaissent entre accolades :

 
Sélectionnez
{6B29FC40-CA47-1067-B31D-00DD010662DA}

Cependant, ce format n'est pas celui que DCOM utilise. En effet, DCOM regroupe les huit derniers octets du GUID. La structure des GUID dans DCOM est donc définie ci-dessous :

 
Sélectionnez
typedef struct GUID
{
    unsigned long Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char Data4[8];
} GUID;

Les GUID utilisés couramment par DCOM et les GUID des composants systèmes sont déclarés dans les fichiers d'en-tête de Windows et définis dans les bibliothèques uuid.lib, uuid2.lib et uuid3.lib.

Créé le 9 juillet 2000  par Christian Casteyde

L'intérêt des GUID est de rendre indépendant les composants et leurs interfaces du contexte d'exécution et de leur installation. En effet, comme les clients n'utilisent que les CLSID pour référencer les composants, ils n'ont pas à se préoccuper de savoir si les composants en question sont implémentés dans une DLL ou dans un exécutable. Ils n'ont même pas à savoir si ce serveur fonctionne en local ou sur une autre machine. Par ailleurs, ils n'ont pas à connaître le nom exact du fichier contenant le serveur, ce qui permet de faire des mises à jour de ces fichiers très souplement, sans même à avoir à modifier les clients. Ceci est un très grand progrès, parce qu'il peut y avoir potentiellement beaucoup de clients qui utilisent un même composant.

De même, en référençant les interfaces par leurs IID, les composants n'ont pas à connaître le nom des fonctions implémentées par les composants. Comme les interfaces sont immuables, les clients qui fonctionnaient à un instant t avec un composant fonctionneront toujours, même si une nouvelle version de ce composant apparaît. Ceci n'empêchera cependant pas le composant d'implémenter de nouvelles interfaces : les anciens clients ne les demanderont pas. En revanche, les clients plus récents peuvent les demander et en tirer profit. Inversement, si un client est mis à jour et demande un nouveau service à un ancien composant, celui-ci répondra simplement qu'il ne peut pas le fournir. Le client est alors libre d'utiliser un ancien service ou de demander une mise à jour du composant. Si le composant est mis à jour, le client sera tout de suite capable d'utiliser les nouvelles fonctionnalités, sans qu'on l'ait lui-même mis à jour. Donc le client et le composant sont complètement indépendants, et peuvent chacun être mis à jours, installé ou déployé sans que l'autre en soit affecté.

Bien entendu, il faut mettre en relation les GUID avec les fichiers utilisés pour implémenter les serveurs et avec les ordinateurs. Toutes ces données sont stockées dans la base de registres. C'est elle qui maintient les associations entre les CLSID et les serveurs, et qui mémorise toutes les interfaces disponibles. D'autres options peuvent être définies, notamment, les noms des machines utilisées et les droits d'accès. La base de registres met également en relation les composants avec leurs type libraries. Elle stocke les GUID des composants qui doivent être utilisés pour réaliser un grand nombre de fonctions du système. Par exemple, les viewers de fichiers de QuickView sont des composants OLE, la Corbeille est un composant OLE, les feuilles de propriétés pour les objets système et pour les fichiers sont des composants OLE, et les raccourcis sont liens OLE.

On peut donc en déduire que toute la configuration de DCOM est stockée dans la base de registres, et que DCOM est lui-même profondément intégré dans le système.

Créé le 9 juillet 2000  par Christian Casteyde

Une interface est un groupement de fonctions gérées par un composant. C'est par ces fonctions que l'on peut utiliser le composant. À chaque interface correspond un IID unique qui l'identifie dans l'espace et le temps.

DCOM définit la forme des interfaces au niveau binaire. Une interface est pointeur sur un tableau de pointeurs sur les fonctions qui la constituent. L'ordre des fonctions pointées dans les interfaces est très important. C'est pour cela qu'il ne faut pas changer la structure d'une interface une fois qu'elle a été définie et que ses spécifications ont été publiées. Les interfaces constituent donc un contrat entre le composant et ses clients. Il est donc impossible de modifier une interface, même de l'étendre. À chaque fois que l'on doit changer le contrat défini par une interface, il est nécessaire d'en redéfinir une.

DCOM définit également les conventions d'appel des fonctions des interfaces pour chaque système d'exploitation sur lequel il est implémenté. Ceci permet d'assurer un bon fonctionnement du passage des paramètres lors des appels de fonctions des interfaces. Les conventions d'appel pour Win32 sur les plates-formes x86 sont les suivantes :

  • les arguments sont passés par la pile ;
  • l'ordre d'empilement est de droite à gauche (le dernier argument est empilé en premier) ;
  • les paramètres sont retirés de la pile par la fonction appelée ;
  • les valeurs de retour de type flottantes sont retournés dans le registre st(0) du coprocesseur ;
  • un code d'erreur est retourné dans l'accumulateur EAX.

Ces conventions d'appel sont définies par le mot-clé __stdcall pour les compilateurs C/C++. Afin de rendre le code source portable entre les différentes plates-formes, la macro STDMETHODCALLTYPE a été définie dans le fichier d'en-tête objbase.h.

Toutes les interfaces doivent au moins fournir les fonctionnalités d'une interface particulière, l'interface IUnknown. Les fonctions de l'interface IUnknown doivent obligatoirement apparaître en premier dans le tableau de pointeurs de l'interface. Ces fonctions permettent de gérer la durée de vie des objets et d'obtenir de nouveaux pointeurs sur d'autres interfaces à partir du pointeur sur l'interface courante.

La structure binaire des interfaces est exactement celle qu'utilise les compilateurs C++ pour stocker les pointeurs sur les tables de fonctions virtuelles des classes C++. Ceci implique que l'utilisation des interfaces en C++ est très facile. Les interfaces sont des classes qui ne contiennent aucune donnée membre et dont toutes les méthodes sont publiques et virtuelles pures. L'inclusion des fonctionnalités d'une interface existante se fait aisément par héritage simple. Par exemple, l'interface suivante dispose, outre des fonctions de l'interface IUnknown dont elle hérite, de deux fonctions membres :

 
Sélectionnez
struct IAdder : public IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE Add(long i, long j,
        long *pResult)=0;
    virtual HRESULT STDMETHODCALLTYPE Sub(long i, long j,
        long *pResult)=0;
};

En général, on ne dispose que d'un pointeur sur les interfaces, quel que soit le moyen d'obtention utilisé (il s'agit donc d'un double pointeur sur la table des fonctions virtuelle). Dans notre exemple, si pI est un pointeur sur cette interface, il est possible d'utiliser directement les fonctions Add et Sub avec la syntaxe suivante :

 
Sélectionnez
pI->Add(2, 3, &i);

Il faut faire très attention à bien utiliser la macro STDMETHODCALLTYPE dans la déclaration des fonctions membres des interfaces. En effet, une discordance des conventions d'appel entre le compilateur et les composants utilisés peut être très difficile à détecter.

Il est très important de ne pas définir de données membres dans une interface C++. En effet, pour pouvoir utiliser le pointeur sur cette interface, il est essentiel qu'elle ait exactement la même structure binaire que l'interface DCOM sous-jacente. Si l'on inclut des données membres dans l'interface C++, il n'est pas certain que le compilateur considérera que pointeur sur la table de fonctions virtuelles est au début des données de l'interface. Par conséquent, il risque d'interpréter le pointeur sur le tableau de pointeurs des fonctions de l'interface comme une des données de la classe. Ce problème vient du fait que les compilateurs C++ ne garantissent pas l'emplacement du pointeur sur la table de fonctions virtuelles : il peut très bien être placé après les données de la classe. Le seul moyen d'être sûr et certain de son emplacement est de ne pas mettre de données du tout. Heureusement, ceci n'empêche pas du tout le programmeur de créer un composant en faisant hériter une de ses classes d'une interface, car les compilateurs C++ seront capables de retrouver le sous objet qui représente l'interface.

Il est impossible de constituer une interface qui définit les fonctions membres de plusieurs interfaces par héritage multiple. En effet, les compilateurs C++ utilisent alors plusieurs sous objets, donc plusieurs pointeurs sur les tables de fonctions virtuelles. Cette structure ne correspond plus à celle que DCOM impose pour les interfaces.

À de rares exceptions près, les méthodes des interfaces doivent toujours renvoyer un code d'erreur dont le type est HRESULT. Ce type est défini dans le fichier d'en-tête winerror.h. Si les fonctions peuvent être exécutées de manière asynchrone, elles ne doivent renvoyer aucune valeur. En fait, il est possible de réaliser des interfaces dont les méthodes retournent un autre type que le code d'erreur HRESULT ou void. Mais dans ce cas, les objets qui implémentent ces méthodes doivent être capables de gérer eux-même le marshalling de leurs interfaces. Voir le paragraphe concernant le marshalling des interfaces pour plus de détails à ce sujet.

Créé le 9 juillet 2000  par Christian Casteyde

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.