FAQ DCOM/OLE/ATLConsultez toutes les FAQ

Nombre d'auteurs : 2, nombre de questions : 91, dernière mise à jour : 2 septembre 2018 

 
OuvrirSommaireIDL et type library

Une type library est un fichier binaire (portant l'extension .tlb) contenant la description des méthodes et propriétés d'objets DCOM. Cette description permet à une application de connaître dynamiquement les différentes interfaces supportées par un objet, ainsi que leurs méthodes, leurs paramètres et leurs rôles.

Initialement, les type libraries étaient générées à l'aide du langage ODL (Object Description Language), qui était très proche du langage IDL. Un compilateur spécifique prenait en entrée les fichiers écrits en ODL et produisaient des fichiers portant l'extension .TLB (pour Type LiBrary). Depuis, le langage ODL a été inclus dans le langage IDL. Il est donc possible à présent d'écrire un seul fichier IDL qui décrit les interfaces pour la génération automatique des facelets et des stublets, et qui permet la génération des type libraries.

Créé le 9 juillet 2000  par Christian Casteyde, Aurélien Regat-Barrel

Le but fondamental des technologies à composant est de réutiliser les morceaux de programmes déjà réalisés. En fait, les langages à objets permettaient déjà de réutiliser du code au sein même d'une application, notamment par le mécanisme d'héritage. Les technologies à composants vont au-delà, en brisant la barrière des applications et du code source. Les composants peuvent être réutilisés par d'autres applications que celles pour laquelle ils ont été initialement conçus.

Il est évident que ceux qui écrivent des composants savent parfaitement comment les utiliser. Dans le cas de DCOM, ceci signifie qu'ils connaissent parfaitement les GUID des composants et des interfaces, ainsi que les paramètres et la sémantique des méthodes des interfaces. S'ils documentent leurs composants, d'autres programmeurs peuvent également utiliser ces composants, en brisant cette fois la barrière des projets ou des sociétés dans lesquels les composants sont écrits. Il est donc nécessaire (mais pas obligatoire) de documenter les composants et leurs interfaces.

En fait, l'idéal est tout simplement que les composants soient capables de renseigner directement leurs clients sur la manière de les utiliser. DCOM a prévu des interfaces spécifiques qui permettent à un client de demander à un composant sa description. Ces interfaces seront également décrites plus loin.

Bien entendu, le fait de rendre utilisable un composant ne nuit pas aux droits d'auteurs, puisque DCOM gère les licences d'utilisation par l'intermédiaire de l'interface IClassFactory2 pour les fabriques de classes. Cette interface sera décrite plus loin.

Quoi qu'il en soit, DCOM spécifie un format standard pour les informations permettant de décrire les composants au moyen des type libraries. Ces dernières peuvent être stockées dans un fichier séparé (portant généralement l'extension .tlb), ou être incluses dans les ressources du fichier dll/exe/ocx/... contenant l'implémentation du composant. La base de registres permet de mettre en relation un composant avec sa type library.

Une type library permet à n'importe quel client d'utiliser un composant, indépendament du langage de programmation, sans que le composant ne soit instancié, ni même qu'il soit enregistré. Par exemple, Visual Basic se sert des type libraries pour sa fonctionnalité d'aide à la saisie (Intellisens). Lorsque le programmeur tape du code relatif à un composant DCOM, l'éditeur de Visual Basic lui affiche la liste des méthodes de ce composant, sans qu'il n'ait rien fait de spécial pour cela. En général, les environnements de développement comme Visual Basic lisent également les informations sur les interfaces des composants dans les type librairies, ce qui leur permet de proposer la liste des méthodes appelables et de vérifier la validité d'un script sans l'exécuter. Ces informations peuvent même être utilisées pour compiler les scripts, ce qui revient à construire les tables de fonctions virtuelles et appeler ainsi les méthodes sur des interfaces des composants.

Un autre exemple d'utilisation des type libraries est celui de la directive #import de Visual C++. A partir d'une type library dont l'emplacement est spécifié au moyen de cette directive, Visual C++ génère un ensemble de classes C++ permettant d'utiliser les différentes interfaces et méthodes décrites. La particularité de cette fonctionnalité est qu'elle se base directement et uniquement sur la type library, et à aucun moment sur l'objet décrit. Autrement dit, il est possible de générer et de compiler du code d'utilisation d'un composant DCOM sans que celui-ci ne soit enregistré ni même présent sur le système.

Créé le 9 juillet 2000  par Christian Casteyde, Aurélien Regat-Barrel

Lorsqu'un composant possède une type library, il faut enregistrer cette type library pour que le système puisse la retrouver. Le lien entre le composant et sa type library est fait dans la clé du CLSID du composant. Il suffit de définir une sous-clé nommée TypeLib dont la valeur est le GUID de la type library en question, tel qu'il est défini dans le fichier IDL qui a servi à le générer. De même, pour faire le lien entre les interfaces d'un composant et la type library qui les décrit, il suffit d'ajouter une sous-clé TypeLib dans la clé du GUID de l'interface et dont la valeur est le GUID de la type library. Par exemple, pour le composant Adder, qui gère les interfaces IAdder et IOpposite, les trois entrées suivantes peuvent être ajoutées pour pointer sur la type library du composant :

 
Sélectionnez

[HKEY_CLASSES_ROOT\CLSID\{91e132a0-0df1-11d2-86cc-444553540000}\TypeLib]
@="{128abb80-0e9a-11d2-86cc-444553540000}"
[HKEY_CLASSES_ROOT\Interface\{e3261620-0ded-11d2-86cc-444553540000}\TypeLib]
@="{128abb80-0e9a-11d2-86cc-444553540000}"
[HKEY_CLASSES_ROOT\Interface\{e3261621-0ded-11d2-86cc-444553540000}\TypeLib]
@="{128abb80-0e9a-11d2-86cc-444553540000}"

Ces entrées permettent au système de déterminer le GUID de la type library pour un objet donné. Il faut donc qu'il puisse récupérer des informations sur la type library à partir de ce GUID. Ceci est possible en définissant une sous-clé portant comme nom le GUID de la type library dans la sous-clé TypeLib de la clé HKEY_CLASSES_ROOT. Cette sous-clé peut contenir des informations pour plusieurs versions de la même type library. Chacune de ces versions est définie dans une sous-clé dont le nom est la version composée du numéro de version majeur, d'un point et du numéro de version mineure. La valeur de cette sous-clé est une chaîne de caractère décrivant la bibliothèque. Par exemple, pour la type library du composant Adder, on a :

 
Sélectionnez

[HKEY_CLASSES_ROOT\TypeLib\{128abb80-0e9a-11d2-86cc-444553540000}]
[HKEY_CLASSES_ROOT\TypeLib\{128abb80-0e9a-11d2-86cc-444553540000}\1.0]
@="Adder Type Library"

Enfin, chaque version peut être décrite dans plusieurs langages. Chaque langage est donné dans une sous-clé dont le nom est l'identificateur de langage en hexadécimal, sans le 0x et sans les 0 non significatifs. Dans cette sous-clé, une sous-clé est encore définie, avec pour nom le nom du système pour laquelle cette bibliothèque a été écrite. En pratique, ce système sera souvent win32. La valeur de cette clé contient le chemin sur le fichier contenant la type library. Pour le composant Adder, la structure des clé identifiant ce fichier est donc celle-ci :

 
Sélectionnez

[HKEY_CLASSES_ROOT\TypeLib\{128abb80-0e9a-11d2-86cc-444553540000}\1.0\0\win32]
@="c:\\winnt\\system32\\Adder.tlb"
Créé le 9 juillet 2000  par Christian Casteyde

Ce paragraphe n'a pas la prétention de donner une description complète du langage IDL, ce qui dépasserait le cadre de cette FAQ. Cependant, il explique un peu la syntaxe du langage et a pour but de permettre la rédaction de fichiers IDL de plus en plus complexes à l'aide de la référence du langage.

Le compilateur utilisé pour traiter les fichiers IDL est nommé MIDL.EXE (pour Microsoft IDL). MIDL est un outil complexe, qui permet de générer non seulement les stublets et les facelets, mais également les type libraries. Ce paragraphe ne présente que les fonctions élémentaires de MIDL, vous trouverez la référence complète de MIDL dans le SDK de Windows.

Le langage IDL est un langage qui ressemble au C++, en particulier pour la syntaxe de l'héritage. Il accepte également les directives du préprocesseur C, tout comme le C++. Les types de données sont exprimés avec une syntaxe équivalente à celle du C, et les types de base portent le même nom. En revanche, IDL possède un certain nombre de mots-clés qui permettent de décrire les interfaces, et il intègre également les mots clés du langage ODL afin de permettre la génération des type libraries.

En fait, MIDL utilise le préprocesseur C du compilateur Microsoft (CL.EXE). Il est donc nécessaire que CL.EXE ou qu'un programme de même nom qui l'émule soit accessible dans le path de votre ordinateur. Si ce n'est pas le cas, il est possible d'utiliser l'option /cpp_cmd pour indiquer le nom du préprocesseur que MIDL doit utiliser.

Le type int n'est pas accepté tel quel par MIDL. En effet, ce n'est pas un type portable, et IDL est supposé décrire les interfaces de manière indépendante de la plate-forme. Il faut donc toujours utiliser une version soit courte, soit longue de int dans les fichiers IDL.

Les types de base utilisés par IDL sont décrits ci-dessous :

  • boolean, type de base pour les nombres booléens. La représentation est faite sur 8 bits, de manière non signée. Les deux seules valeurs possibles sont TRUE et FALSE. Ce type correspond au type bool du C++
  • byte, type générique stocké sur 8 bits. Il n'a pas de notion de valeur ou de signe associée, les données de ce type sont transmises telles quelles sur le réseau. L'interprétation de ces données doit être faite au niveau des bits, les nombres stockées dans ce type peuvent changer de valeur selon la représentation interne de la machine cible. Ce type de donnée n'a pas d'équivalent direct en C/C++
  • char, type de base pour les caractères, stocké sur 8 bits. Les caractères sont toujours considérés comme non signés par IDL. Ce type de donnée est équivalent au type unsigned char du C/C++
  • wchar_t, type de base pour les caractères longs, stocké sur 16 bits. Les caractères sont toujours considérés comme non signés par IDL. Ce type de donnée est équivalent au type unsigned wchar_t du C++
  • short, small, type de donnée permettant de stocker des entiers courts (16 bits). Par défaut, les valeurs stockées sont signées. Il est possible d'utiliser le mot-clé unsigned pour les rendre non signées. Ce type n'a pas d'équivalent direct en C/C++ (la taille des types n'est pas normalisée)
  • int, long, type de donnée permettant de stocker des entiers normaux (32 bits). Par défaut, le type int est considéré comme égal au type long sur les plateformes 32 bits, cependant, sa taille est variable (tout comme en C/C++). Il est donc fortement recommandé de ne jamais l'utiliser sans qualification de taille. Les valeurs stockées sont signées, mais il est possible de les rendres non signées à l'aide du mot-clé unsigned. Ce type n'a pas d'équivalent en C/C++
  • hyper, type de données permettant de stocker un entier long (64 bits). Par défaut, les valeurs stockées sont signées, mais elles peuvent devenir non signées si l'on utilise le mot-clé unsigned. Ce type est équivalent au type __int64 des compilateurs C/C++ pour Windows
  • float, type de données permettant de stocker un nombre à virgule flottante sur 32 bits. Ce type n'a pas d'équivalent direct en C/C++ (le format des nombres à virgule flottante n'est pas normalisé)
  • double, type de données permettant de stocker un nombre à virgule flottante sur 64 bits. Ce type n'a pas d'équivalent direct en C/C++
  • void *, type de donnée permettant de stocker un pointeur sur un contexte d'exécution 32 bits
  • handle_t, type de donnée primitif permettant de stocker un handle

Les mots-clés short, small, long et hyper sont en réalité des qualificateurs de taille du type int. Ils peuvent cependant être utilisés directement, sans le mot-clé int.

Les mots-clés short et small sont synonymes.

Il est possible de définir des structures et des types complexes à l'aide du mot-clé typedef du C. Par exemple :

 
Sélectionnez

typedef struct
{
    long int i;
    float *pf;
} s;

La syntaxe du C++ pour la définition des classes et des structures n'est pas acceptée par MIDL. Il faut utiliser le mot-clé typedef.

Tous les types prédéfinis de Windows et toutes les interfaces de base de DCOM doivent être définies au début du fichier IDL. Ces déclarations sont données dans le fichier unknwn.idl, cependant, ce fichier ne doit pas être inclus à l'aide de la directive #include du préprocesseur. En effet, ceci aurait pour conséquence d'inclure les déclarations des prototypes de fonctions des interfaces de base de DCOM, ce qui forcerait la création de stublets et de facelets pour ces fonctions. Or ceux-ci sont fournis par DCOM lui-même, et le fichier IDL ne pourrait donc pas être compilé. Pour résoudre ce problème, le mot-clé import a été défini dans IDL. Ce mot-clé permet d'inclure les déclarations d'un fichier IDL, mais pas les définitions. Sa syntaxe est la suivante :

 
Sélectionnez

import "fichier.idl";

On veillera surtout à ne pas oublier le point-virgule à la fin de la directive import. La syntaxe n'est donc pas la même que celle de la directive #include.

En fait, le mot-clé import permet également de lancer une nouvelle instance du préprocesseur sur le fichier importé. Ceci signifie que les macros définies dans ce fichier sont traitées indépendamment de celles du fichier qui l'importe, et qu'elles n'y sont donc plus définies.

Les interfaces sont définies à l'aide du mot-clé interface. Ce mot-clé doit être immédiatement suivi du nom de l'interface, puis d'une interface dont cette dernière hérite (elle hérite au moins de IUnknown), et enfin de la déclaration des méthodes de l'interface. La syntaxe utilisée est exactement celle du C++ pour la définition des classes, à ceci près que le mot-clé class a été remplacé par le mot-clé interface et que toutes les méthodes sont bien évidemment publiques :

 
Sélectionnez

interface nom : base
{
    // Description des méthodes.
};

IDL ne supporte pas l'héritage multiple.

Il faut obligatoirement spécifier une interface de base.

La description des méthodes est réalisée avec une syntaxe similaire de la déclaration des fonctions en C. En fait, la syntaxe est exactement la même, à ceci près que des informations additionnelles sont données pour renseigner MIDL sur la manière dont ces fonctions seront utilisées.

D'une manière générale, un certain nombre d'éléments de la description peuvent recevoir des attributs qui les définissent précisément. Ces attributs précèdent toujours l'élément qu'ils qualifient et sont donnés entre crochets, à l'aide de mots-clés spécialisés. Lorsque plusieurs attributs sont donnés, ils sont séparés par des virgules :

 
Sélectionnez

[
attribut, attribut, etc...
]

L'un des attributs les plus important est introduit par le mot-clé uuid. C'est tout simplement le GUID associé à un objet donné (interface, composant, bibliothèque). Cet attribut doit apparaître en premier, avant les autres. La syntaxe du mot-clé uuid est donnée ci-dessous :

 
Sélectionnez

uuid(valeur)

où valeur est le GUID représenté en hexadécimal, sans les accolades.

Un autre attribut important est l'attribut qui signale qu'une interface est définie dans le cadre de DCOM. MIDL est capable de générer des proxy et des stubs pour les RPC en plus des facelets et des stublets pour DCOM (les fichiers générés ne portent d'ailleurs pas le même nom). Afin de lui signaler quel type de fichiers sources il doit générer, il faut utiliser le mot-clé object. La syntaxe de ce mot-clé est élémentaire, puisqu'il suffit de l'utiliser comme un simple attribut :

 
Sélectionnez

object

Viennent ensuite les attributs qui permettent de spécifier des informations pour l'utilisateur du composant. Ces informations apparaîtront dans les type libraries qui seront générées par MIDL. Le premier de ces attributs est la chaîne de caractères qui donne de l'aide sur un élément particulier. Cette chaîne de caractères est introduite à l'aide du mot-clé helpstring :

 
Sélectionnez

helpstring("Description de l'élément qualifié par helpstring")

Comme les type libraries peuvent contenir des messages textuels, elles sont dépendantes de la langue utilisée. C'est pour cette raison que le mot-clé lcid a été introduit : il permet de spécifier la langue utilisée pour une type library. Différents codes sont définis dans le fichier d'en-tête olenls.h. Cependant, le code le plus courant est bien entendu 0x0000, qui ne spécifie aucune langue particulière. La langue utilisée est alors un anglais neutre en général. La syntaxe du mot-clé lcid est donnée ci-dessous :

 
Sélectionnez

lcid(code)

où code est le code du langage utilisé.

Enfin, le mot-clé version permet de spécifier la version d'une type library. Sa syntaxe est la suivante :

 
Sélectionnez

version(numéro)

où numéro est le numéro de version. Ce numéro doit être constitué de deux nombres séparés par un point, par exemple 1.0.

Les attributs présentés ci-dessus servent uniquement à titre de renseignement, cependant, la plupart des attributs restants sont utilisés pour préciser la sémantique des méthodes des interfaces et de leurs paramètres. Ils permettent de renseigner MIDL sur la manière dont le code de marshalling doit être généré dans les facelets et les stublets. En particulier, les indications données suffisent à déterminer quels sont les paramètres en entrée, les paramètres en sortie d'une méthode, et la description des types complexes (tableaux, listes chaînées, etc...).

Les attributs in et out permettent de spécifier le sens dans lequel les paramètres sont passés entre l'appelant et l'appelé. Il est possible qu'un paramètre soit spécifié à la fois en tant que paramètre in et out. Il est très important de bien donner ces informations, car elles permettent à MIDL de ne copier les valeurs de ces paramètres que lorsque cela est nécessaire. Un paramètre in ne voit sa valeur copiée que de l'espace d'adressage du client vers celui du serveur avant l'appel de la fonction du serveur. Inversement, un paramètre out voit sa valeur copiée de l'espace d'adressage du serveur vers celui du client en retour de fonction. Enfin, les paramètres in, out sont copiés à la fois à l'appel et au retour de fonction. Ces copies pouvant se faire à travers un réseau, il est nécessaire de ne copier que les paramètres pour lequel ceci est vraiment nécessaire.

L'attribut async permet d'indiquer à MIDL qu'une méthode d'une interface doit être exécutée de manière asynchrone. Ceci signifie que le client poursuit son exécution juste après avoir effectué l'appel à cette méthode, sans attendre que celle-ci ne se termine. Bien entendu, dans ce cas, la méthode ne peut pas renvoyer de code d'erreur, le client ne l'utiliserait d'ailleurs pas. Elle doit donc renvoyer void. De même, tous ses paramètres doivent être in seulement, aucun paramètre ne peut être modifié en retour par le serveur.

L'attribut string permet d'indiquer à MIDL que le paramètre qualifié est une chaîne de caractère. Le paramètre en question doit être un tableau d'éléments du type char, wchar_t, byte ou un type équivalent, ou un pointeur sur un tableau d'éléments de ce type. Lorsque cet attribut est utilisé, la longueur de la chaîne de caractère est déterminée dynamiquement avec la convention du C : la fin de chaîne est marquée par un caractère nul. Cet attribut ne peut donc pas être utilisé avec des langages qui ne traitent pas les chaînes de caractères comme le C. Si l'on veut transmettre une chaîne de caractères de taille fixe, il est préférable de décrire cette chaîne de caractères comme un tableau.

Les tableaux sont déclarés avec la syntaxe du C/C++ dans MIDL. MIDL utilise la même sémantique que le C/C++ pour les tableaux : la borne inférieure doit toujours être 0, et les tableaux de dimension deux ou plus sont traités comme des tableaux de tableaux, et il n'est pas obligatoire de préciser la taille de la dernière dimension. Cependant, dans ce cas, la borne supérieure doit pouvoir être déterminée lors de l'exécution du programme. Pour cela, il faut indiquer le nom d'un identificateur qui contiendra la taille du tableau. Lorsque le tableau est défini à l'intérieur d'une structure, cet identificateur doit être l'un des champs de cette structure, et lorsque le tableau est un des paramètres d'une fonction, cet identificateur doit être un autre paramètre de cette fonction. Pour spécifier quel identificateur doit être utilisé, il faut utiliser le mot-clé size_is. Sa syntaxe est la suivante :

 
Sélectionnez

[size_is(identificateur)] tableau[]

Lorsque l'on utilise des tableaux de pointeurs, il est nécessaire que chacun des éléments du tableau soit initialisé et pointent sur de la mémoire valide.

La manipulation des pointeurs est une opération difficile à réaliser pour MIDL, pour diverses raisons. La première est que lorsqu'un pointeur est passé en paramètre, il est nécessaire d'effectuer les opérations de marshalling pour les données pointées en plus du pointeur. La deuxième raison est que les pointeurs et les références conduisent souvent à la création d'alias dans le code, c'est à dire à la possibilité d'accéder de plusieurs manières différentes à une même variable donnée (par exemple, l'identificateur qui la représente et par son adresse). Afin de faciliter la tâche à MIDL, un certain nombre d'attributs sont disponibles pour qualifier les pointeurs et préciser leurs propriétés.

Si le pointeur utilisé ne dispose d'aucune propriété particulière, MIDL doit effectuer toutes les vérifications avant d'effectuer le marshalling de ce pointeur. En particulier, il doit vérifier la présence d'alias et de cycles de pointeurs éventuels dans les structures chaînées par pointeurs. Ce type de pointeurs qui demandent le plus de calculs, peut être déclaré à l'aide du qualificateur ptr. Les propriétés de ces pointeurs sont les suivantes :

  • ils peuvent avoir la valeur nulle à l'appel
  • le serveur peut changer leur valeur
  • les données pointées peuvent être accédées par d'autres identificateurs accessibles par le serveur

Si le pointeur utilisé est le seul moyen dont dispose le serveur pour accéder aux données qu'il référence, ou autrement dit s'il n'y a pas d'alias sur ces données, ce pointeur est dit unique. Les pointeurs uniques peuvent être qualifiés à l'aide du mot-clé unique. Les pointeurs uniques possèdent les propriétés suivantes :

  • ils peuvent avoir la valeur nulle à l'appel
  • le serveur peut changer leur valeur

Enfin, si le pointeur n'est utilisé que pour accéder à une donnée par indirection, c'est à dire si la donnée peut être passée directement par valeur lors de l'appel, le pointeur est dit pointeur par référence. Les pointeurs de ce type sont qualifiés par le mot-clé ref. Les pointeurs par références doivent respecter les contraintes suivantes :

  • ils doivent être initialisés lors de l'appel, et pointer sur des données valides (autrement dit, ils ne peuvent pas contenir la valeur nulle)
  • ils ne doivent pas changer de valeur au cours de l'exécution du code du serveur
  • ils ne doivent pas être des alias d'autres identificateurs

Il est évident que les pointeurs par références sont les plus efficaces. En pratique, on utilisera souvent les pointeurs uniques cependant, car ils conviennent dans la plupart des situations.

Lors de la manipulation des pointeurs, un certain nombre de problèmes peuvent se poser, essentiellement lorsqu'une allocation mémoire doit avoir lieu. Une des règles à respecter est que pour les pointeurs qui ne sont pas par références, les données pointées doivent obligatoirement être allouées, parce que le serveur est susceptible de désallouer cette mémoire. De plus, les allocations et libérations de mémoire doivent toujours se faire avec l'allocateur de DCOM, par l'intermédiaire de l'interface IMalloc. Enfin, il faut savoir que de la mémoire peut être perdue lorsqu'un pointeur passé en paramètre contient une adresse valide sur un bloc alloué et que le serveur met à la valeur nulle ce pointeur. Si l'adresse n'est pas stockée au niveau du client dans une variable annexe, le bloc de mémoire est perdu. Il est également bon de savoir que lorsque le serveur change la valeur d'un pointeur en une autre valeur, les données pointées par la nouvelle valeur sont supposée être du même type que celles pointées par l'ancienne valeur. Ceci implique qu'elles ont la même taille, et que dans le cas des pointeurs out, les données sont recopiées au retour de la fonction dans le même bloc mémoire que celui qui a été utilisé par le client pour les passer. Le pointeur ne change donc pas de valeur du côté du client.

Nous avons vu la manière de décrire les interface, les méthodes et les paramètres des méthodes dans les paragraphe suivant. Mais MIDL permet de faire bien plus que cela, puisque depuis la version 3 de MIDL, il est possible d'inclure la définition des type libraries dans les fichiers IDL. Ceci permet d'éviter d'avoir à redéfinir les interfaces à la fois dans les fichiers IDL et dans les fichiers ODL : une seule définition suffit à présent. Nous allons maintenant voir comment les type libraries sont définies dans les fichiers IDL.

La génération d'une type library se fait à l'aide du mot-clé library. Ce mot-clé s'utilise avec la syntaxe suivante :

 
Sélectionnez

library nom_librairie
{
    coclass nom_composant
    {
        interface I1;
        interface I2;
        etc...
    }
};

nom_librairie est le nom de la type library que MIDL va générer (Attention ! Ce n'est pas forcément le nom du fichier .TLB généré), nom_composant est le nom d'un des composants décrits par cette bibliothèque et I1, I2, etc sont des interfaces déjà définies.

La syntaxe donnée ci-dessus est simplifiée. C'est la syntaxe minimale pour générer une type library.

Les attributs des différents éléments n'ont pas été précisés. En réalité, il faut donner au moins un GUID pour la type library et un CLSID pour le composant.

Pour illustrer ce qui a été dit jusqu'à présent, un fichier IDL simple est fournit ci-dessous. Cet exemple complet est le fichier IDL utilisé pour la description du composant Adder. Ce fichier est commenté à chaque étape de la description et sa lecture ne devrait pas poser de problèmes.

Exemple 12. Fichier IDL pour le composant Adder

 
Sélectionnez

// Ce fichier contient la description de l'interface IAdder.
// Il sert pour la génération des stublets/facelets,
// et pour la génération des bibliothèques de types.
// Toutes les définitions de bases doivent être incluses, mais
// il ne faut pas inclure les prototypes de fonctions. Le mot-clé
// import est donc utilisé à la place de la directive #include :
import "unknwn.idl";
// Définition de l'interface IAdder :
// Tous les éléments d'un fichier IDL peuvent avoir des attributs.
// Ces attributs sont donnés avant l'élément, entre crochets :
[
    // Les GUID sont indiqués avec le mot-clé uuid. Ils doivent impérativement
    // apparaître en premier :
    uuid(e3261620-0ded-11d2-86cc-444553540000),
    // Le mot-clé object permet d'indiquer à MIDL qu'il doit générer
    // les fichiers des facelets/stublets pour une interface DCOM, et non pas
    // les fichiers pour le RPC de DCE :
    object,
    // Le mot-clé helpstring permet de donner une description de l'objet
    // défini :
    helpstring("Definition of the IAdder interface")
]
interface IAdder : IUnknown
{
    [
        helpstring("Adds two integers and returns the result")
    ]
    // Les paramètres des fonctions peuvent être également qualifiés.
    // Les options les plus utiles sont in, out et in out, qui indiquent
    // le sens de passage des paramètres :
    HRESULT Add([in] long i, [in] long j, [out] long *pResult);
    [
        helpstring("Substracts two integers and returns the result")
    ]
    HRESULT Sub([in] long i, [in] long j, [out] long *pResult);
};
// Définition de l'interface IOpposite :
[
    uuid(e3261621-0ded-11d2-86cc-444553540000), object,
    helpstring("Definition of the IOpposite interface")
]
interface IOpposite : IUnknown
{
    [
        helpstring("Calculates the opposite of an integer")
    ]
    HRESULT Opposite([in] long i, [out] long *pResult);
};
// Définition de la type library pour le composant ADDER :
[
    uuid(128abb80-0e9a-11d2-86cc-444553540000),
    helpstring("Adder Type Library"),
    // Le mot-clé lcid permet d'indiquer un code de langue pour la bibliothèque.
    // Le code nul correspond au langage neutre (le langage utilisé pour le
    // codage), c'est très souvent l'anglais de base :
    lcid(0x0000),
    // Le mot-clé version permet d'indiquer le numéro de version de l'objet
    // qu'il qualifie :
    version(1.0)
]
library AdderTypeLibrary
{
    // Le mot-clé coclass permet de définir un composant DCOM
    // avec les interfaces qu'il gère :
    [
        uuid(91e132a0-0df1-11d2-86cc-444553540000),
        helpstring("Description of the Adder Component")
    ]
    coclass Adder
    {
        // Attention à ne pas inclure l'interface IUnknown ici. Elle est
        // incluse automatiquement par IDL et ne doit pas l'être à nouveau.
        interface IAdder;
        interface IOpposite;
    }
};

Voici à présent le fichier IDL contenant la description de l'interface du composant Calculator :

Exemple 13. Fichier IDL pour le composant Calculator

 
Sélectionnez

import "unknwn.idl";
// Définit l'interface IMultiplier :
[
    uuid(e3261622-0ded-11d2-86cc-444553540000), object,
    helpstring("Definition of the IMultiplier interface")
]
interface IMultiplier : IUnknown
{
    [
        helpstring("Multiplies two integers and returns the result")
    ]
    HRESULT Mul([in] long i, [in] long j, [out] long *pResult);
};
// Importe la définition des interfaces de Adder :
import "..\..\Adder\AdderPrx\adder.idl";
[
    uuid(128abb81-0e9a-11d2-86cc-444553540000),
    helpstring("Calculator Type Library"),
    lcid(0x0000),
    version(1.0)
]
library CalculatorTypeLibrary
{
    [
        uuid(91e132a1-0df1-11d2-86cc-444553540000),
        helpstring("Description of the Calculator Component")
    ]
    coclass Calculator
    {
        interface IAdder;
        interface IOpposite;
        interface IMultiplier;
    }
};
Créé le 9 juillet 2000  par Christian Casteyde

Les fichiers IDL sont compilés avec le programme MIDL, fourni dans le kit de développement pour Windows. La syntaxe de MIDL est relativement simple :

 
Sélectionnez

MIDL /app_config fichier.idl

Où fichier est le nom du fichier IDL. L'option /app_config permet de simplifier l'écriture des fichiers IDL. Sans cette option, il est nécessaire de générer un fichier ACF (Application Configuration File) en plus du fichier IDL. Ce fichier contient toutes les définitions dépendantes du système. L'option /app_config permet d'inclure ces définitions automatiquement.

MIDL utilise le préprocesseur C de Microsoft par défaut pour traiter les directives de préprocesseur du fichier IDL. Si vous ne disposez pas de ce préprocesseur, vous pouvez en indiquer un autre à l'aide de l'option /cpp_cmd. Le préprocesseur indiqué doit malgré tout être dans l'un des répertoires du path de votre ordinateur pour que MIDL fonctionne correctement.

MIDL génère alors les fichiers suivants :

fichier.h Ce fichier contient la déclaration des interfaces définies dans le fichier IDL. Ce fichier d'en-tête peut être utilisé aussi bien avec un compilateur C qu'avec un compilateur C++ ;
fichier_i.c Ce fichier contient la définition des CLSID et GUID des composants et de leurs interfaces définis dans le fichier IDL. Le fichier objet obtenu en compilant ce fichier doit être lié avec les clients et avec les serveurs ;
fichier_p.c Ce fichier contient le code C des stublets et des facelets. Une fois compilé, il permet de générer une DLL unique, qui servira à la fois pour le proxy sur la machine où tourne le client et pour le stub sur la machine où tourne le serveur ;
dlldata.c Ce fichier contient des données propres aux stublets et aux facelets. Il doit être compilé et lié avec le fichier objet issu de la compilation de fichier_p.c ;
fichier.tlb Ce fichier est la type library contenant la description des interfaces et des composants définis dans le fichier IDL.

MIDL n'accepte malheureusement pas d'autres types que HRESULT et void pour les valeurs de retour des méthodes des interfaces. Si l'on doit écrire un composant qui utilise une interface dont une des méthodes renvoie un autre type, il faut abandonner le marshalling standard, et faire en sorte que ce composant gère lui-même son propre marshalling.

De plus, les proxies et les stubs générés par MIDL n'acceptent pas le mécanisme d'agrégation. Il est donc impossible de réaliser un agrégat d'objets qui utilisent le marshalling pour communiquer entre eux. Cependant, rien n'interdit d'utiliser les mécanismes d'agrégation pourvu que les composants gèrent eux-même le marshalling de leurs interfaces.

Créé le 9 juillet 2000  par Christian Casteyde

Les fichiers produits par MIDL doivent être compilés de la manière suivante :

  • le fichier fichier_i.c doit être compilé indépendamment des autres pour définir les CLSID et GUID des interfaces du composant. Le fichier objet obtenu devra donc être lié aussi bien avec le client qu'avec le composant, ou la DLL contenant les stublets et les facelets
  • les fichiers fichier_p.c et dlldata.c doivent être compilés et liés avec le fichier issu de la compilation de fichier_i.c pour générer une DLL. Cette DLL contiendra les facelets et les stublets pour le proxy et le stub standard, pour toutes les interfaces des composants décrits dans le fichier IDL.

On prendra garde lors de la compilation à respecter les consignes suivantes :

  • définir la macro REGISTER_PROXY_DLL, qui permet de générer le code nécessaire pour l'enregistrement automatique de la DLL dans la base de registre par DCOM
  • compiler les fichiers pour une cible multithreadée
  • utiliser un alignement de 8 octets pour les données
  • inclure toutes les options nécessaires pour générer une DLL Windows
  • supprimer les fonctionnalités spécifiques des compilateurs (par exemple, contrôles de débordement de piles, gestion des exceptions, etc...)

De plus, lors de l'édition de lien, il faudra penser à respecter les règles suivantes :

  • inclure la bibliothèque statique RPCRT4.LIB
  • exporter les fonctions DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer et GetProxyDllInfo, fonction qui sont définies dans la bibliothèque précédemment citée
  • indiquer toutes les options pour générer une DLL Windows

On fera très attention au processus de compilation et d'édition de lien de ces fichiers, car de nombreux problèmes peuvent survenir, surtout si l'on n'utilise pas un compilateur Microsoft (les compilateurs Microsoft utilisent les options par défaut qui conviennent). Les problèmes les plus courants concernent les conventions d'appel. Il est nécessaire de bien utiliser les macros du type STDMETHODCALLTYPE lors de la définition de ses méthodes d'interfaces afin d'éviter des conflits entre les clients et les proxies d'une part, et entre les stubs et les serveurs d'autre part. Si vous rencontrez des problèmes ou de plantages lors de l'exécution de votre programme, vérifiez toutes les conventions d'appel.

Un autre problème peut apparaître lors de l'exportation des fonctions des DLL générées : certains environnements de développement les exportent avec la décoration du C ou du C++. Windows attend que ces fonctions soient exportées exactement avec le nom qui leur a été donné ci-dessus, sans décoration et en respectant la casse. Si vous rencontrez des problèmes de chargement de DLL, vérifiez le nom des fonctions exportées.

Enfin, il se peut que les options de compilation diffèrent selon que la cible est une DLL ou un exécutable. Dans ce cas, il est nécessaire de compiler deux fois les fichiers utilisés en commun par le proxy et le stub d'une part, et par les clients et les composants d'autre part (par exemple, le fichier fichier_i.c). Il est recommandé en général de les compiler plusieurs fois, dans des répertoires différents, afin d'éviter tout conflit éventuel entre les cibles qui utilisent des fichiers communs.

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.