Ajout de composants

Un article de Frenchmozilla.

Version du 30 avril 2009 à 10:23 par Satanmalin (Discuter | Contributions)
(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

<breadcrumbs></breadcrumbs>

Sommaire

Ajouter des composants à Mozilla

Ce document décrit comment ajouter de nouveaux composants à Mozilla, incluant la construction système et l’enregistrement de composants. Il ne traite pas des extensions, seulement des composants de bases vérifiés dans l’arborescence principale. Cela implique que vous sachiez déjà comment écrire un composant XPCOM, et vouloir les intégrer dans Mozilla.

Où mettre les fichiers

Vous devrez choisir un bon endroit pour que votre composant y vive. Certaines classes partagées générales vivent dans Modèle:Source, Des classes spécifiques a Firefox vivent dans Modèle:Source, un tas d'autres choses aléatoires vit dans Modèle:Source et dans d'autres répertoires sous le répertoire principal mozilla. Si vous étendez un composant, vous pouvez utiliser les répertoires de ce composant. Autrement demander a votre gourou de Mozilla favori ou sur #developers si vous ne savez pas où il doit aller.

En dehors du répertoire de votre composant, il y a souvent :

  • public Là ou vont vos fichiers IDL. Parfois, il y a aussi des fichiers "header" qui seront #included par d'autres composants.
  • src Où vont vos fichiers d'implémentation. Pour des composants C++, vous avez généralement un couple de .cpp/.h pour chaque classe d'implémentation. Si vous implémentez une classe en JavaScript, mettez la aussi ici.
  • content Là où vont vos fichiers d'interface utilisateur, incluant des .xul et chaque fichiers .js associés qui sont utilisé lors de l'exécution Si vous n'êtes pas en train de construire une nouvelle interface utilisateur spécifique à votre composant, vous n'aurez aucun de ces fichiers.

Makefiles

Une fois que votre composant est écrit, vous allez avoir besoin d'écrire les makefiles. Le mieux est de copier le Makefile.in d'un autre composant semblable au votre : La plupart des Makefile sont quasiment identiques. Ces fichiers Makefile.in son traités et le "vrai" Makefile est créé. SI vous utilisez une arborescence séparée pour vos fichiers objets, vos Makefiles finaux apparaitront ici.

Pour plus d'informations sur les Makefiles, allez voir Writing Mozilla Makefiles.

En général, vous aurez un répertoire de haut niveau foo pour votre composant, qui contiendra donc les sous répertoires foo/public, foo/src, etc. Le Makefile dans foo va juste référencer les sous-répertoires et par ailleurs n'a besoin que du paragraphe standardisé :

   DEPTH           = ../..   # <-- 	VOIR CI-DESSOUS POUR LA SIGNIFICATION DE CECI
   topsrcdir       = @top_srcdir@
   srcdir          = @srcdir@
   VPATH           = @srcdir@
   
   include $(DEPTH)/config/autoconf.mk
   
   DIRS            = public src
   
   include $(topsrcdir)/config/rules.mk

Vos autres makefiles vont en fait inclure tous vos fichiers. Dans ces autres Makefiles, vous devriez voir/ajouter les lignes:

   MODULE = foo
   XPIDL_MODULE = foo

MODULE = foo signifie qu'un répertoire dist/include/foo va être créer et vos fichiers publics, comme les fichiers .h, générés à partir des sources .idl, vont être placés ici. D'autres modules seront ensuite en mesure de direREQUIRES = foo pour ajouter ce répertoire a leur paths inclus et utiliser vos fichiers d'interface. XPIDL_MODULE contrôle le nom du fichier the .xpt , qui est une version compilée de vos fichiers IDL. Le nom exacte de ça importe peu, et est généralement le même que celui de votreMODULE.

Autres sections intéressantes du makefile :

  • DEPTH: Vers le haut du makefile donne chemin relatif de votre répertoire mozilla/ a prtir du makefile courant. Donc Modèle:Source doit contenir DEPTH = ../../../..
  • XPIDLSRCS: C'est la liste des fichiers IDL files dans le répertoire courant qui seront traités. Généralement, ce sera seulement dans les makefiles dans les sous-répertoires "public".
  • REQUIRES: C'est une liste de modules que les fichiers incluent à partir du répertoire courant. Ces nom correspondent à la MODULE=foo dans les autres modules (qui génèrent un répertoire dist/include/foo contenant des fichiers header).
  • CPPSRCS: une liste des fichiers .cpp dans le répertoires courant à compilé .
  • EXPORTS: Une liste de fichiers à copier dans le répertoire "dist/include/foo" pour d'autres composants à utilisé. Ce n'est pas banal, et n'est généralement visible que dans les sous-répertoires "public". Ça peut être utilisé, par exemple, si vous avez un fichier helper .h qui n'est pas partie intégrante de votre composant (et na donc pas d'IDL), mais qui serait nécessaire ou utile à d'autres modules C++.
  • EXTRA_COMPONENTS: C'est ici que vous listez vos fichiers JavaScript XPCOM.
  • LOCAL_INCLUDES: Vous pouvez spécifiez manuellement d'autre répertoires à inclure. Normalement, à la place, vous devriez utiliser REQUIRES ce qui installera le path à inclure à votre place. La syntaxe est LOCAL_INCLUDES = -Isome_directory/somewhere. Pour des chemins relatifs, utilisez quelque chose comme -I$(srcdir)/../some_component/src ($srcdir identifie le répertoire courant, qui peut, en fait,être différent du répertoire courant du compilateur).
  • DEFINES: Macros additionnelless de préprocesseur pour le C++. Exemple: DEFINES = -DSOME_DEFINE -DOTHER_DEFINE=25

Ajout de nouveaux composants JavaScript au coeur de Mozilla

Mettez votre fichier JavaScript dans un répertoire quelconque comme foo/src qui est pour les composants XPCOM (n'utilisez pas content qui est pour le code d;interface). Ajoutez ensuite votre nom de fichier à la section EXTRA_COMPONENTS= du Makefile.in.

En bas de votre implémentation JavaScript, vous devriez créer une fonction NSGetModule, qui retourne juste une référence à une implémentation nsIModule :

function NSGetModule(compMgr, fileSpec) {
   return myModule;
}

Voici un exemple d'implémentation d'un nsIModule , ce qui créer des objets de type myImplementation:

var myModule = {
  mCID: Components.ID("{d1a83725-4a27-4a66-b212-915b14722c75}"), // (Utilisez votre CID)
  mContractID: "@example.com/my-contract-id;1",                  // (Utilisez votre contract ID)

  registerSelf: function (compMgr, fileSpec, location, type) {
    compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    compMgr.registerFactoryLocation(this.mCID,
                                    "My class that does something useful", // (Utilisez votre description)
                                    this.mContractID,
                                    fileSpec,
                                    location,
                                    type);
  },

  getClassObject: function (compMgr, cid, iid) {
    if (!cid.equals(this.mCID))
        throw Components.results.NS_ERROR_NO_INTERFACE;

    if (!iid.equals(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

    return this.mFactory;
  },

  mFactory: {
    createInstance: function (outer, iid) {
      if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;
      return (new myImplementation()).QueryInterface(iid);  // (Utilisez votre nom de l'objet de l'implémentation)
    }
  }, 

  canUnload: function(compMgr) {
    return true;
  }
};

Ajoutez de nouveaux composants C++ au coeur de Mozilla

La fonction init

Vous pouvez fournir une fonction d'initialisation à vos classes. Elle sera immédiatement appelée après que votre classes soit allouée et que le constructeur soit appelé. La fonction init comporte 0 arguments, retourne un nsresult, et doit être public. Vous pouvez l'appelez comme vous voulez,référencez la juste a partir de NS_GENERIC_FACTORY_CONSTRUCTOR_INIT (expliqué ci-dessous).

L'avantage d'utiliser la fonction init en plus du constructeur normal C++ est que vous pouvez retourner une erreur à partir de la fonction init. SI une erreur est détectée, la fonction sera désalloué et le caller nécessitant une instance de votre classe recevra l'erreur. Généralement, vous devriez effectuer toute initialisation qui peuvent échouer dans la fonction Init de votre classe.

Enregistrer le composant

Les composants sont enregistrés comme une partie d'un module, la source de ce qui est habituellement dans un répertoire appelé build. Par exemple, Modèle:Source est le module source pour les composants sous Modèle:Source. De même, il y a Modèle:Source, une Modèle:Source, etc.

Premièrement, trouvez le Makefile.in et donnez lui le nom de votre composant. Ca devrait être le même nom que la ligne MODULE = foo dans le makefile de votre composant. Ceci assure que le chemin entré contient dist/include/foo où les fichiers header générés de votre composant sont situés.

Dans le répertoire de construction , vous trouverez un fichier cpp correspondant au module, dont la désignation est quelque peu arbitraire. Dans ce fichier, vous voudrez. Tout d'abord, inclure le fichier header de votre composant. Ce n'est pas le header généré par le IDL, mais le header pour l'implémentation concrète du composant (car ce module est ce qui créer une instance de votre implémentation).

Typiquement, votre fichier header de l'implémentation (nécessaire ici) est dans votre le répertoire de votre composant et n'est pas public. Cela signifie qu'il n'est pas copié/lié à partir de dist/include/foo et inclure juste votre répertoire dans votre section de construction REQUIRESn'est pas suffisant. Dans ce cas, vous allez avoir besoin d;ajouter le répertoire source de votre implémentation àLOCAL_INCLUDES.Prenez le fichier Modèle:Source comme exemple pour savoir comment faire ça.

Ensuite, ajoutez une ligne:

   NS_GENERIC_FACTORY_CONSTRUCTOR(nsYourConcreteClassName)

Ceci générera une fonction appelée nsYourConcreteClassNameConstructor qui alloue votre objet (regardez Modèle:Source). Vous pouvez aussi utilisez:

   NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)

qui appellera nsYourConcreteClassName->Init() après que l'objet soit alloué (Voir Modèle:Anch ci-dessus).

Ajoutez un nsModuleComponentInfo à votre classe

Dans le fichier .cpp du module, il devrait y avoir une longue liste de structurensModuleComponentInfo correspondantes a chacune des classe que le module implémente. Ces structures ressemble généralement à ça:

   { "Friendly class description for people to read",
     YOUR_CLASS_CID,
     YOUR_CLASS_CONTRACTID,
     nsYourConcreteClassNameConstructor }

Il y a beaucoup d'arguments optionnels qui ne sont pas souvent utilisés. Voir définition de nsModuleComponentInfo dans Modèle:Source pour la liste complète.

YOUR_CLASS_CID: (composant ID) est un GUID qui identifie l'implémentation de votre classe. C'est différent des GUIDs utilisés pour identifier chacune des interfaces. (C'est possible, mais pas commun, de créer une instance de classe utilisant cette valeur à la place du instance contract ID.) Cette valeur est souvent définie dans un fichier quelquechoseCID.h aux côtés du fichier module .cpp dans le répertoire de construction. Parfois, on préfère les déclarer dans le fichier .h du composant . Une définition typique est :

   /* 2d96b3d0-c051-11d1-a827-0040959a28c9 */
   #define NS_WINDOW_CID \
   { 0x2d96b3d0, 0xc051, 0x11d1, \
       {0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9}}

The contract ID: (la partie @example.com/..., à ne pas confondre avec "CID" = component ID) est la chaine qu'on transmet à GetService ou à CreateInstance pour obtenir une instance de votre classe. Parfois cela peut être défini à côté du CID dans le fichier header dans le répertoire de construction,ou dans un fichier header public spécial. Ces deux méthodes sont bonnes. Parfois, ils sont juste codés en dur dans la structure ou sont définis dans le fichier IDL, mais ce n'est pas recommandé.

The constructor est le nom du constructeur généré automatiquement par la ligne NS_GENERIC_FACTORY_CONSTRUCTOR que vous avez ajouté au-dessus. Ce sera le nom de votre classe concrète suivi de "Constructor".

If your class implements more than one interface: Définit une structure nsModuleComponentInfo structure pour chaque interface que votre classe implémente. Le nom du CID et du constructeur seront les même pour tous (tant que votre implémentation concrète est la même), mais vous aurez des contract IDs différents correspondant a chaque interface.