XPCOM

XPIDL, un langage de description d'interfaces

Présentation de XPIDL

A l'instar de CORBA, XPCOM dispose d'un langage de description d'interfaces, un IDL (Interface Description Language). Celui-ci se nomme XPIDL (Cross-Platform Interface Description Language), et utilise une syntaxe relativement proche de l"IDL de CORBA.

Le but du langage XPIDL est de décrire, dans un langage commun, le fonctionnement qu'adopte un composant. Cela permet de séparer l'interface du composant de son implémentation.

Trois éléments clés caractérisent une interface :

IID (Interface IDentifier)

Lors de l'écriture d'une interface, il faut attribuer à celle-ci un nombre unique qui permet de l'identifier : un IID (Interface IDentifier). Ce nombre de 128 bits est un UUID (Universally Unique IDentifier).

Pour générer un UUID, on peut utiliser la commande uuidgen sous Linux.

L'IID est utilisé à l'exécution, ce qui permet de ne pas utiliser le nom de l'interface en C++, et ce, pour un gain de rapidité.

nsISupports, l'interface mère de toutes les interfaces

A l'instar des composants Microsoft COM, tout composant XPCOM dispose de trois méthodes de base. Celles-ci sont définies par l'interface nsISupports, dont doit hériter toute interface.

Cette interface mère est décrite dans le fichier MOZILLA/xpcom/base/nsISupports.idl dont la partie essentielle est la suivante :

[scriptable, uuid(00000000-0000-0000-c000-000000000046)]
interface nsISupports {
  void QueryInterface(in nsIIDRef uuid, 
                      [iid_is(uuid),retval] out nsQIResult result);
  [noscript, notxpcom] nsrefcnt AddRef();
  [noscript, notxpcom] nsrefcnt Release();
};

Les trois méthodes déclarées sont QueryInterface, AddRef et Release, et on remarque que l'IID de nsISupports est 00000000-0000-0000-c000-000000000046.

La méthode QueryInterface est la plus importante. C'est elle qui permet de s'assurer qu'un composant implante une interface, et donc qu'il respecte le contrat défini par l'interface.

Lorsque l'on souhaite utiliser un composant, on doit au préalable appeler la méthode QueryInterface en lui passant l'IID de l'interface. Le composant doit alors retourner un pointeur sur lui-même, ou retourner une erreur (sous forme d'un code de retour, ou d'une levée d'exception, selon le langage dans lequel est écrit le composant). Ce mécanisme est appelé interface discovery (découverte d'interfaces).

Les méthodes AddRef et Release permettent, quant à elle, d'effectuer du comptage de références lorsque l'on utilise un composant.

En JavaScript, les méthodes AddRef et Release ne peuvent pas être appelées. En effet, c'est XPConnect qui a la charge de gérer les appels. En C++, la situtation est différente. En effet, il incombe au développeur de prendre garde au comptage de référence. Toutefois, cela est facilité par l'utilisation de smart pointers (Cf. The Complete nsCOMPtr User's Manual).

Héritage multiple d'interfaces

Tout comme en Java, l'héritage multiple d'interfaces est possible. Il faut prendre garde au fait que, lorsque l'on utilise un composant, on n'a accès qu'aux méthodes déclarées par l'interface que l'on a demandé lors de l'appel de QueryInterface. En effet, l'appel de QueryInterface a pour effet de réaliser un cast de l'objet, et seules les méthodes déclarées par l'interface dont on a passé l'IID sont accessibles. Pour appeler les méthodes d'une des autres interfaces, il faut donc, à nouveau, appeler QueryInterface avec un autre IID.

Supposons que l'on dispose des interfaces suivantes :

interface A {
  void methodA();
};

interface B {
  void methodB();
};

Supposons, par ailleurs, qu'il existe un composant implémentant les deux interfaces. Lorsque l'on obtiendra une référence sur le composant, nous allons tout d'abord vouloir appeler la méthode methodA. Nous allons donc appeler QueryInterface sur le composant, en lui passant l'IID de l'interface A. Puis, pour appeler la méthode methodB, il nous faudra appeler QueryInterface en lui passant l'IID de l'interface B.

Cela est possible car, rappelons-le, lorsque l'on appelle la méthode QueryInterface d'un composant, celle-ci retourne une référence sur le composant (sauf s'il n'implémente pas l'interface, auquel cas il lance une exception).