mercredi 28 avril 2010

XULRunner C++, IDL et XPCOM


Cet article à pour objectif de vous permettre de développer des composants XPCOM. Un composant XPCOM étant une passerelle entre le Javascript et un langage de plus bas niveau (ici: le C++).

1. Installation du runtime
Le runtime peut être téléchargé à l’adresse suivante :
ftp://ftp.mozilla.org/pub/mozilla.org/xulrunner/releases/

Une fois l’archive décompréssée, il faut exécuter la commande « xulrunner –register-global » pour enregistrer le runtime sur le systeme, ou « xurunner –register-user » pour ne l’enregistrer que pour l’utilisateur.

2. Composant C++
2.1. Remarques
Pour compiler sous Visual C++ 6, il faut utiliser le SDK 1.8.0.4 version MSVC :
ftp://ftp.mozilla.org/pub/mozilla.org/xulrunner/releases/1.8.0.4/sdk/gecko-sdk-win32-msvc-1.8.0.4.zip .

Attention :
J'ai dû patcher ce SDK 1.8 pour le rendre compatible avec l'appel de fonctions callback Javascript. Ne vous étonnez donc pas si vous avez des problèmes de compilation avec Visual C++ 6.0, je détaillerais ce patch dans un prochain article.


Sinon utiliser la dernière version (actuellement 1.9.0.5).

2.2. Téléchargement du SDK
Le SDK peut être téléchargé à l’adresse suivante :
ftp://ftp.mozilla.org/pub/mozilla.org/xulrunner/releases/

Info : Il peut être installé dans le même répertoire que le runtime.

3. Génération des templates
Avant de créer un composant, on doit créer un fichier IDL qui indique le format de notre composant.

3.1. Exemple de fichier IDL
#include "nsISupports.idl"

[scriptable, uuid(e44e3c7f-6cf2-4dfc-b85e-03a55a6c1dce)]
interface StringParserObserver : nsISupports {
    attribute string name;
    long onWord(in string word);
};

[scriptable, uuid(12564f1e-4bb8-4e51-9147-f2276c38b6b3)]
interface IMyComponent : nsISupports {
    long Add(in long a, in long b, out long c);
    void addObserver(in StringParserObserver observer);
    attribute long m_compteur;
};


3.2. Interprétation du fichier
Dans le fichier précédent on voit deux interfaces, elles héritent toutes les deux de la class « nsISupports », une classe livrée avec le SDK.

L’interface IMyComponent et l’interface de la classe que nous allons partager avec XPCOM.
On trouve dans cette dernière :
- une fonction qui retourne un valeur de type « long » et qui prend en paramètre des variables « a » et « b » en lecture et « c » en écriture. Il existe également un type « inout » pour de la lecture/écriture.

- Une fonction de type void qui prend en paramètre une autre interface décrite dans notre fichier idl. Cette fonction permet en faite d’enregistrer un objet JavaScript afin de le rappeler ultérieurement dans notre composant MyComponent.

- Une variable d’instance m_compteur, qui est une variable public de type long.

L’interface StringParserObserver représente un objet Javascript possédent une fonction onWord et un attribut name.

Chacune de ses interfaces possèdent un uuid unique qui peut être généré par la commande « guidgen » sous Windows si Visual C++ est installé, « uuidgen» sous Linux, ou encore sur le web à l’adresse http://www.somacon.com/p113.php .

3.3. Génération du header d’interface C++
La commande « {SDK_DIRECTORY}/bin/xpidl –m header –I {SDK_DIRECTORY}/idl header {mon_fichier}.idl » permet de générer le template de l’interface de notre objet.

3.4. Génération du fichier XPT
Le fichier XPT sert au runtime pour savoir comment il doit utiliser la DLL.
La commande « {SDK_DIRECTORY}/bin/xpidl –m typelib –I {SDK_DIRECTORY}/idl header {mon_fichier}.idl » permet de le générer.

3.5. Génération de notre classe en C++
3.5.1. Le fichier header
Créer un fichier MyComponent.h et coller le code suivant :
/* Header file */
class _MYCLASS_ : public IMyComponent
{
public:
    NS_DECL_ISUPPORTS
    NS_DECL_IMYCOMPONENT

    _MYCLASS_();

private:
    ~_MYCLASS_();

protected:
    /* additional members */
};

Info : on trouve ce code dans le fichier ImyComponant.h entre des balises « #if 0 » et « #endif »

Remplacer _MYCLASS_ par MyComponent.
En haut du fichier, placer le code suivant :
#include "IMyComponent.h"

#define MY_COMPONENT_CONTRACTID "@thalesgroup.com/TestLibXPCOM/MyComponent;1"
#define MY_COMPONENT_CLASSNAME "Test de DLL XPCOM"
//fd9056c2-fe40-4d83-b561-dccdd799d551
#define MY_COMPONENT_CID \
{\
    0xfd9056c2,\  //fd9056c2
    0xfe40,\  //fe40
    0x4d83,\  //4d83
    {  0xb5, 0x61, //b561
        0xdc, 0xcd, 0xd7, 0x99, 0xd5, 0x51 }\ //dccdd799d551
}


- MY_COMPONENT_CONTRACTID permet d’identifier notre composant auprès de l’interface XPCOM

- MY_COMPONENT_CID correspond à une conversion de l’UUID en hexadecimal.

3.5.2. Le fichier source
Copier le code suivant (se trouvant également dans le fichier « IMyComponent.h ») dans le fichier MyComponent.cpp :
/* Implementation file */
NS_IMPL_ISUPPORTS1(_MYCLASS_, IMyComponent)

_MYCLASS_::_MYCLASS_()
{
    /* member initializers and constructor code */
}

_MYCLASS_::~_MYCLASS_()
{
    /* destructor code */
}

/* long Add (in long a, in long b, out long c); */
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *c, PRInt32 *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void addObserver (in StringParserObserver observer); */
NS_IMETHODIMP _MYCLASS_::AddObserver(StringParserObserver *observer)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* attribute long m_compteur; */
NS_IMETHODIMP _MYCLASS_::GetM_compteur(PRInt32 *aM_compteur)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP _MYCLASS_::SetM_compteur(PRInt32 aM_compteur)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}


Remplacer _MYCLASS_ par MyComponent et ajouter les lignes suivante en haut du fichier source :
#define XPCOM_GLUE 1
#define XPCOM_GLUE_USE_NSPR 1

#include "mozilla-config.h"
#include "xpcom-config.h"

#include "MyComponent.h"


3.5.3. Le fichier d’enregistrement du module
Afin d’enregistrer notre composant auprès d’XPCOM, créer un fichier source nommé MyComponentModule.cpp et insérer le code suivant :
#define XPCOM_GLUE 1
#define XPCOM_GLUE_USE_NSPR 1

#include "mozilla-config.h"
#include "xpcom-config.h"

#include "nsIGenericFactory.h"
#include "MyComponent.h"

NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent)

static nsModuleComponentInfo components[] =
{
    {
        MY_COMPONENT_CLASSNAME,
        MY_COMPONENT_CID,
        MY_COMPONENT_CONTRACTID,
        MyComponentConstructor
    }
};

NS_IMPL_NSGETMODULE("MyComponentsModule", components)


4. Configuration du projet
Pour compiler, notre projet a besoin de connaître les répertoire d’includes suivants :
- {SDK}/sdk/include
- {RUNTIME}/include

De connaitre le chemin vers les librairies :
- {SDK}/sdk/lib
- {RUNTIME}/lib

D’intégrer au projet les librairie suivante :
- nspr4.lib
- plds4.lib
- xpcom.lib
- xpcomglue_s.lib

5. Utilisation du composant
Une fois notre composant compilé, copier la dll acompagnée de son fichier xpt dans le répertoire component de firefox ou de notre application XUL et faire attention au subtilités de notre lanceur afin qu’il prenne bien en compte notre composant.

6. Utilisation de l’objet JavaScript
Notre classe MyComponent possède une fonction AddObserver qui permet au javascript de donner une instance d’objet qu’on peut utiliser à partir de notre dll.

Exemple de code JavaScript utilisant notre composant :

<script type="text/javascript">
 var obj = null;
 try {
  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
  const cid = "@marcbuils.fr/TestLibXPCOM/MyComponent;1";
  obj = Components.classes[cid].createInstance();
  obj = obj.QueryInterface(Components.interfaces.IMyComponent);

  obj.addObserver({
   onWord: function (word) {
    alert("Word: "+word);
    return 0;
   },
   name: "Word"
  });
 } catch (e)
 {
  alert(e);
 }
</script>


Code à intégrer :

- MyComponent.h
#include “nsCOMPtr.h”;
…
protected:
    nsCOMPtr m_observer;


- MyComponent.cpp
NS_IMETHODIMP MyComponent::AddObserver(StringParserObserver *observer)
{
    this->m_observer = observer;
    return NS_OK;
}


this->m_observer->onWord(..) peut maintenant être appelée dans toutes les fonctions de la classe appartenant au Thread principal.

7. Ajout d’un Thread utilisant notre objet JavaScript
MyComponent.h
private:
    static void PR_CALLBACK thread(void*);


MyComponent.cpp
#include "prthread.h"
#include "prlog.h"
#include "nsServiceManagerUtils.h"
#include "nsIProxyObjectManager.h"
…
NS_IMETHODIMP MyComponent::AddObserver(StringParserObserver *observer)
{
    PRThread *t;
    t = PR_CreateThread( PR_USER_THREAD,
        MyComponent::thread,
        observer,
        PR_PRIORITY_NORMAL,
        PR_GLOBAL_THREAD,
        PR_UNJOINABLE_THREAD,
        0);
    PR_ASSERT(t);

    return NS_OK;
}
…
void PR_CALLBACK MyComponent::thread(void *args)
{
    nsCOMPtr obs;
    nsresult rv;
    PRInt32 returnValue;

    nsCOMPtr manager = do_GetService(
        "@mozilla.org/xpcomproxy;1",
        &rv);
    manager->GetProxyForObject( NS_PROXY_TO_MAIN_THREAD,
        StringParserObserver::GetIID(),
        NS_REINTERPRET_CAST( StringParserObserver *,
            args),
        NS_PROXY_SYNC|NS_PROXY_ALWAYS,
        getter_AddRefs(obs));

    while(1)
    {
        PR_Sleep(10000);
        obs->OnWord(“MyWord”, &returnValue);
    }
}


Commentaires
L’utilisation de la function PR_CreateThread permet de créer des thread multiplateforme, mais aussi des thread qui peuvent utiliser les méthodes « PR » de la librairie Mozilla.
L’utilisation du proxy permet quand à lui d’utiliser des objets qui interagissent avec l’interface XPCOM dans un thread qui n’est pas LE thread principale (JavaScript ne fonctionnant que dans un seul et unique Thread).

8. Retourner un objet héritant d’une interface XPCOM

Exemple :
nsCOMPtr MBMSManagerVariable::varTab[1000];
int MBMSManagerVariable::NBR = 0;

NS_IMETHODIMP MBMSManagerVariable::getVariable(MBMSVariable* *_retval)
{
    MBMSVariable* variable = new MBMSVariable();
    MBMSManagerVariable::varTab[MBMSManagerVariable::NBR] = (nsCOMPtr)variable;

    NS_ADDREF(*_retval = MBMSManagerVariable::varTab[MBMSManagerVariable::NBR++]);
    return NS_OK;
}


Attention :
Si vous n’utilisez pas la fonction NS_ADDREF, l’objet sera correctement retourné au Javascript, mais supprimé de la mémoire si il n’est pas enregistré (ou écrasé) dans une variable Javascript.


9. Les extensions

Une extension XULRunner (plugin) est composé des éléments suivants :
- install.rdf
- chrome.manifest
- chrome/
- … [architecture identique à une application XUL (components/, plugins/, …)

9.1. install.rdf
9.1.1. Exemple :
<rdf xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"  xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <description about="urn:mozilla:install-manifest">
    <i:name>Driver</i:name>
    <i:version>1.0.0</i:version>
    <i:id>driver@marcbuils.fr</i:id>
    <i:type>2</i:type>
    <i:description>Driver I2C pour port parallele</i:description>

    <i:targetapplication>
      
      <description>
       <i:id>1fac95ee-5300-4bff-b313-b6fd269c4227</i:id>
       <i:minversion>0.9</i:minversion>
       <i:maxversion>1.1</i:maxversion>
      </description>
    </i:targetapplication>

    
    <i:creator>Marc BUILS</i:creator>

    
    <i:homepageurl>http://www.marcbuils.fr</i:homepageurl>
    <i:updateurl>http:///marcbuils.fr/xul/update.php?soft=mbms&plugin=driver</i:updateurl>

  </description>
</rdf>


10. Les différentes formes d’une extension
10.1. Répertoire
L’extension peut se présenter sous la forme d’un répertoire ayant pour nom l’id de l’extension et pour contenu les fichiers décrits dans le chapitre « Format d’une extension ».

10.2. Package XPI
L’extension peut également se présenter sous la forme d’un package XPI qui est un zip du contenu du répertoire précédent.

11. Installation d’une extension
Le plugin, sous forme de répertoire ou de fichier xpi, peut être installé dans le répertoire extensions de l’application

Si le plugin est sous la forme de .XPI, il peut être installé via le manager d’extension fournit dans le toolkit Mozilla. Dans ce cas le plugin s’installe par défaut dans le répertoire du profil de l’extension (exemple : D:\Documents and Settings\marc\Application Data\marcbuils\MBMS\Profiles\yj8toifp.default\extensions)

Aucun commentaire:

Enregistrer un commentaire