Difference between revisions of "FR/Documentation/Construire des composants"
SergeMoutou (talk | contribs) |
|||
(8 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
− | Notre propos est d'étendre OpenOffice. | + | Notre propos est d'étendre OpenOffice.org d'une manière quelconque. Toutes les possibilités d'extensions ne viennent pas à l'esprit la première fois que l'on se demande si cela est possible. Mais si je vous dis qu'il suffit de réaliser une librairie partagée puis de signifier à Openoffice.org qu'il doit l'utiliser, cela ne choquera personne. Ainsi il nous faut produire une librairie avec un suffixe uno.so sous Linux ou uno.dll sous Windows. Pour indiquer à OpenOffice.org d'utiliser cette nouvelle librairie et où la trouver, un fichier rdb est nécessaire et cette opération est appelée enregistrement dans une base de registre. La base de registre a déjà été présentée [[FR/Documentation/Base_de_registres|dans un chapitre précédent]]. Vous pouvez aussi lire [[Non-code_extensions_FR#Extensions_comportant_des_services_UNO_.28code.29|cette section]] avant de commencer. |
=La terminologie de Danny Brewer= | =La terminologie de Danny Brewer= | ||
([http://www.oooforum.org/forum/viewtopic.php?t=13335&postdays=0&postorder=asc&start=0 voir le oooforume]) | ([http://www.oooforum.org/forum/viewtopic.php?t=13335&postdays=0&postorder=asc&start=0 voir le oooforume]) | ||
Line 220: | Line 220: | ||
La règle 3 définitive [[Documentation/FR/Construire_des_composants#Utilisation_d.27une_aide_.28helper.29_pour_construire_le_composant_scriptable|est présentée plus loin]], mais vous n'avez aucune raison de vous impatienter. Sinon, vous pouvez aussi lire le [[Documentation/DevGuide/WritingUNO/Core_Interfaces_to_Implement|Developer's Guide]] à ce sujet. | La règle 3 définitive [[Documentation/FR/Construire_des_composants#Utilisation_d.27une_aide_.28helper.29_pour_construire_le_composant_scriptable|est présentée plus loin]], mais vous n'avez aucune raison de vous impatienter. Sinon, vous pouvez aussi lire le [[Documentation/DevGuide/WritingUNO/Core_Interfaces_to_Implement|Developer's Guide]] à ce sujet. | ||
− | ==L'exemple du compteur== | + | ==L'exemple du compteur du SDK== |
Cet exemple du SDK est partiellement [[Counter_Example|décrit ici en anglais]]. Il contient deux fichiers : counter.cxx et countermain.cxx. Ces fichiers montrent quelques différences par rapport aux Listing 3 et Listing 4 donnés précédemment parce qu'ils sont liés à OpenOffice.org maintenant, donc plus complexes : countermain.cxx sera responsable de l'enregistrement de "counter.uno.so" et counter.cxx devra implanter toutes les interfaces de la règle 3 provisoire. Ce petit exemple utilise donc une librairie externe enregistrée (counter.uno.so construit à partir counter.cxx) et un programme principal (countermain.cxx) qui l'utilise. | Cet exemple du SDK est partiellement [[Counter_Example|décrit ici en anglais]]. Il contient deux fichiers : counter.cxx et countermain.cxx. Ces fichiers montrent quelques différences par rapport aux Listing 3 et Listing 4 donnés précédemment parce qu'ils sont liés à OpenOffice.org maintenant, donc plus complexes : countermain.cxx sera responsable de l'enregistrement de "counter.uno.so" et counter.cxx devra implanter toutes les interfaces de la règle 3 provisoire. Ce petit exemple utilise donc une librairie externe enregistrée (counter.uno.so construit à partir counter.cxx) et un programme principal (countermain.cxx) qui l'utilise. | ||
Line 495: | Line 495: | ||
et ainsi nous pouvons utiliser le makefile de ce composant. | et ainsi nous pouvons utiliser le makefile de ce composant. | ||
− | {{ | + | {{Warn|Pour mon premier essai j'ai tenté d'utiliser la première solution mais elle ne fonctionne pas correctement : il y a probablement un problème avec la base de registre. Si un second programme gère l'enregistrement, cela ne fonctionne pas. Il doit y avoir un autre problème mais je n'ai pas poussé mais investigations plus loin. Cela fonctionne en utilisant le makefile de CppComponent en renommant le fichier IDL et le fichier cxx du MakeFile. |
A faire : résoudre le problème précédent sur l'enregistrement !!! En fait je pense que l'enregistrement réalisé par countermain n'est pas correct pour retrouver le service manager. Enquête à suivre.}} | A faire : résoudre le problème précédent sur l'enregistrement !!! En fait je pense que l'enregistrement réalisé par countermain n'est pas correct pour retrouver le service manager. Enquête à suivre.}} | ||
Pour le moment je vais donc expliquer la méthode à suivre à partir de l'exemple CppComponent du SDK. | Pour le moment je vais donc expliquer la méthode à suivre à partir de l'exemple CppComponent du SDK. | ||
Line 789: | Line 789: | ||
queryAdapter ( ) | queryAdapter ( ) | ||
</pre> | </pre> | ||
− | {{ | + | {{Note|A noter pour conclure que l'outil [[Extensions_development_basic#X-Ray_tool|XRay]] peut vous permettre de faire fonctionner le compteur en "XRayant" la méthode increment par exemple. [[Object_Inspector|L'inspecteur Java]] permet en plus d'utiliser la méthode setCount qui demande un paramètre (ce que XRay ne sait pas encore faire).}} |
L'utilisation de [[Object_Inspector|l'inspecteur Java]] se fait avec un programme OOoBasic : | L'utilisation de [[Object_Inspector|l'inspecteur Java]] se fait avec un programme OOoBasic : | ||
<source lang='oobas'> | <source lang='oobas'> | ||
Line 872: | Line 872: | ||
queryAdapter ( ) AS object | queryAdapter ( ) AS object | ||
</pre> | </pre> | ||
− | {{ | + | {{Note|Vous gardez le même code source que l'exemple précédent pour faire fonctionner cet exemple, c'est à dire qu'il y aura du code pour getCount() et setCount() même si ces fonctions n'apparaissent pas dans le fichier IDL. Si vous faites un XRay sur la propriété Count, cela vous donne la bonne valeur (pourtant le champ correspondant n'existe pas, il est implanté dans un champ qui se nomme m_nCount de type sal_Int32).}} |
==Construire le compteur sans aide (helper)== | ==Construire le compteur sans aide (helper)== | ||
Line 883: | Line 883: | ||
=Notre deuxième composant= | =Notre deuxième composant= | ||
− | {{ | + | {{Warn|Je ne laisse cette section que parce qu'elle reprend un exemple du [[Documentation/DevGuide/WritingUNO/C%2B%2B/C%2B%2B_Component|Developer's Guide]] en le francisant. J'ai retiré cet exemple de la version anglaise du document car il est bien mieux traité dans le [[Documentation/DevGuide/WritingUNO/C%2B%2B/C%2B%2B_Component|Developer's Guide]].}} |
Notre première tâche consiste à trouver un morceau de code comme point de départ. L'idée générale est de partir de : “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent” et de modifier légèrement le code. Commençons par décrire l'exemple : un seul module comportant deux services (MyService1 et MyService2). Ce que réalise cet exemple n'est pas extraordinaire mais fournit un bon point de départ. | Notre première tâche consiste à trouver un morceau de code comme point de départ. L'idée générale est de partir de : “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent” et de modifier légèrement le code. Commençons par décrire l'exemple : un seul module comportant deux services (MyService1 et MyService2). Ce que réalise cet exemple n'est pas extraordinaire mais fournit un bon point de départ. | ||
Voici le fichier IDL décrivant tout cela : | Voici le fichier IDL décrivant tout cela : | ||
Line 1,158: | Line 1,158: | ||
=Les problèmes spécifiques à Microsoft Windows= | =Les problèmes spécifiques à Microsoft Windows= | ||
− | {{ | + | {{Win|Cette section aborde des problèmes que vous pouvez être amenés à rencontrer lorsque vous faites une édition de lien entre un composant et une librairie de style Unix. Gardez à l'esprit que les composants sont toujours construits avec Visual C++. Mais il y a un tas de librairies libres externes qui sont plutôt de style Unix et qui ne peuvent ainsi être construite qu'avec cygwin ou quelque chose d'approchant (MinGw). Les porter sous VC++ demanderait un travail trop important. Je veux donner ici un exemple montrant une voie que j'ai mis plusieurs jours à découvrir. Je ne vais pas construire un composant dans l'exemple qui suit, mais seulement un binaire exécutable construit avec VC++ qui utilise une dll construite avec cygwin.}} |
== Notre exemple == | == Notre exemple == | ||
Line 1,204: | Line 1,204: | ||
Notez que j'ai ajouté une fonction externe C nommée "essai1". Le point important est que je ne peux pas utiliser directement les classes car les différents compilateurs leur donne des noms différents dans les fichiers objets ou librairies générés. | Notez que j'ai ajouté une fonction externe C nommée "essai1". Le point important est que je ne peux pas utiliser directement les classes car les différents compilateurs leur donne des noms différents dans les fichiers objets ou librairies générés. | ||
− | {{ | + | {{Note|VC++ ajoute un "_" comme prefix du nom de la fonction et un "@n" comme suffix avec n comme taille en octets des paramètres. Dans notre exemple "_essai1@4" serait exporté.}} |
Line 1,236: | Line 1,236: | ||
</pre> | </pre> | ||
L'option "-fPIC" ne semble pas nécessaire sous cygwin. Arrivé ici vous avez un fichier counter.dll. Copiez-le ainsi que counter.def dans un répertoire que vous allez utiliser avec VC++. | L'option "-fPIC" ne semble pas nécessaire sous cygwin. Arrivé ici vous avez un fichier counter.dll. Copiez-le ainsi que counter.def dans un répertoire que vous allez utiliser avec VC++. | ||
− | {{ | + | {{Win|Si vous pouvez directement donner à gcc une librairie dynamique pour la phase d'édition de liens, ce n'est pas le cas sous Windows. Utiliser une dll se fait avec une librairie statique créée pour vous avec l'outil lib qui utilise le fichier de définition pour cela.}} |
La compilation sous VC++ se fait comme suit | La compilation sous VC++ se fait comme suit | ||
<pre> | <pre> | ||
Line 1,245: | Line 1,245: | ||
==MakeFile== | ==MakeFile== | ||
− | {{ | + | {{Warn| |
Le chapitre sur les [[MakeFile|MakeFile]] est en construction et même pas traduit en français pour le moment, mais nous espérons ajouter un exemple de makeFile qui gère ce genre de situations dans le futur (aussi proche que possible).}} | Le chapitre sur les [[MakeFile|MakeFile]] est en construction et même pas traduit en français pour le moment, mais nous espérons ajouter un exemple de makeFile qui gère ce genre de situations dans le futur (aussi proche que possible).}} | ||
Nous allons maintenant examiner comment transformer un composant en addin. | Nous allons maintenant examiner comment transformer un composant en addin. | ||
Line 1,312: | Line 1,312: | ||
* [[Non-code_extensions_FR|Extensions et packages UNO]] | * [[Non-code_extensions_FR|Extensions et packages UNO]] | ||
* Writing a Program to Control OpenOffice.org, by Franco Pingiori — [http://www.linuxjournal.com/article/8550 Part 1] and [http://www.linuxjournal.com/article/8608 Part 2], Linux Journal | * Writing a Program to Control OpenOffice.org, by Franco Pingiori — [http://www.linuxjournal.com/article/8550 Part 1] and [http://www.linuxjournal.com/article/8608 Part 2], Linux Journal | ||
− | * Bernard Marcelly's | + | * Bernard Marcelly's [[Extensions_development_basic#Xray_tool|XRay tool description]] in this wiki |
* See also [[Extension Deployement|Extension Deployement]] | * See also [[Extension Deployement|Extension Deployement]] | ||
* [[UNO component packaging|(Python) UNO component packaging]] | * [[UNO component packaging|(Python) UNO component packaging]] |
Latest revision as of 20:21, 13 July 2018
Notre propos est d'étendre OpenOffice.org d'une manière quelconque. Toutes les possibilités d'extensions ne viennent pas à l'esprit la première fois que l'on se demande si cela est possible. Mais si je vous dis qu'il suffit de réaliser une librairie partagée puis de signifier à Openoffice.org qu'il doit l'utiliser, cela ne choquera personne. Ainsi il nous faut produire une librairie avec un suffixe uno.so sous Linux ou uno.dll sous Windows. Pour indiquer à OpenOffice.org d'utiliser cette nouvelle librairie et où la trouver, un fichier rdb est nécessaire et cette opération est appelée enregistrement dans une base de registre. La base de registre a déjà été présentée dans un chapitre précédent. Vous pouvez aussi lire cette section avant de commencer.
La terminologie de Danny Brewer
Clarifions (et peut-être inventons) un peu de terminologie.
- Add On : ajoute un item supplémentaire sur la barre de menu ou un icône sur la barre d'outils. En modifiant le sous-noeud de configuration pour un AddOn, vous pouvez ajouter des items nouveaux et des items sous forme d'icone. Les programmes OOoBasic peuvent modifier la configuration pour ajouter des items au menu. Il n'est pas nécessaire d'utiliser pkgchk (devenu unopkg maintenant). Les composants (voir plus loin) peuvent aussi ajouter des items sur le menu quand ils sont installés en utilisant l'outil unopkg.
- Add In : Un composant (décrit plus loin dans un autre chapitre) qui fournit des nouvelles fonctions au tableur OOoCalc. Ces fonctions sont alors visibles dans la boîte de dialogue de l'autopilote des fonctions. Cette boîte de dialogue peut complètement décrire vos fonctions, leurs paramètres, des informations supplémentaires etc...
- Composant : Un composant UNO, écrit avec n'importe lequel des langages supportés, doit être installé avec la commande pkgchk. (Ce sera "pkgchk.exe" pour les utilisateurs de Windows.) Un composant fournit un ou plusieurs services qui sont enregistrés dans la base de registres. Ces services peuvent être instanciés comme n'importe quel autre service original d'OpenOffice. Par exemple en OooBasic, vous pouvez simplement appeler createUnoService( "name.DannyBrewer.magic.SomeService" ) pour avoir une instance du service et ainsi appeler directement l'ensemble de ses méthodes. De manière similaire, Python, Java ou Visual Basic, ou n'importe quel autre langage peut utiliser le nouveau service installé.
Un composant fournit un ou plusieurs services.
Service
C'est une abstraction UNO pour un objet. Un service peut actuellement ne pas représenter un objet associé unique. Un service peut avoir différentes interfaces qu'il implémente.. Un service possède des propriétés. Quelles méthodes sont utilsables à partir d'un service sont déterminées par les interfaces qu'il implémente.
Comme un Add-in de OooCalc est seulement un composant qui implante certaines interfaces et services particuliers, le add-in de Ooocalc est installé comme un autre composant. Cela signifie qu'un add-in est installé et desinstallé en utilisant unopkg.
Faire un add-in pour OooCalc est comme faire un autre service. Mais il faudra lui ajouter le service particulier : com.sun.star.sheet.AddIn, et devra implanter correctement toutes ses interfaces, sachant qu'il y en a plusieurs. Mais une fois cela réalisé votre service procure de nouvelles fonctions au tableur. Les add-In de OooCalc sont abordés dans le prochain chapitre.
Chaîne de compilation d'un composant
La création d'un composant nécessite un fichier IDL décrivant ses services et interfaces et un fichier cpp contenant le code correspondant. La construction complète à partir de ces deux fichiers (et quelques autres) est complètement décrite en Figure ci-dessous. Le but final est de créer un fichier d'extension uno.so ainsi qu'un fichier rdb. Pour plus d'information sur les outils binaires utilisés dans la chaîne de compilation, lisez le chapitre correspondant du Developer's Guide.
Les flèches noires de cette Figure indiquent comment créer un fichier à partir d'un autre et l'outil employé pour cela. Par exemple, on se sert de idlc pour créer un fichier some.urd à partir d'un fichier some.idl. Les flèches rouges indiquent que regcomp est utilisé pour placer le nom de la librairie réalisée dans le fichier rdb. Finalement les flèches bleues indiquent une dépendance. Le processus complet de construction peut être décrit comme suit :
- Créer les fichiers idl et cpp. La Figure ci-dessus montre ces fichiers avec comme nom some.idl et some.cpp.
- Uitiser idlc pour compiler les fichiers idl en un fichier urd.
- Transformer le fichier urd en un fichier rdb en utilisant regmerge. Le fichier rdb n'est pas encore complet car il doit contenir le nom et le chemin du fichier uno.so, opération réalisée à la fin en utilisant l'outil regcomp.
- Utiliser cppumaker pour créer les fichiers d'entête hpp et hdl.
- Utiliser gcc pour compiler le fichier cpp.
- L'enregistrement final dans la base de registre a déjà été discuté précédemment et n'est pas montré en Figure ci-dessus.
Pour créer un composant vous devez d'abord créer le fichier idl correspondant. Ce fichier IDL décrit l'interface.
Le but des exemples suivants est de créer un composant visible d'OpenOffice.org ou plus précisément, visible du langage de programmation OOoBasic. Le SDK contient un exemple dans le répertoire : “<OpenOffice.org_SDK>/examples/DevelopersGuide/Components/CppComponent” et décrit aussi dans le Developer's Guide.
Premier exemple : accéder à un compteur à partir du OOoBasic
Notre premier objectif est de transformer un compteur en un composant. Un compteur n'est probablement pas grandiose mais c'est idéal pour débuter. L'inspiration pour cet exemple m'est venue à partir des deux exemples du SDK :
- <OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent (vaguement évoqué dans la section précédente) et décrit aussi dans le Developer'sGuide.
- <OpenOffice.org1.1_SDK>/examples/cpp/counter
Le problème à résoudre pour le deuxième exemple, celui du compteur, est que celui-ci est inaccessible à partir du OOoBasic. Nous aimerions le rendre accessible : un composant accessible à partir de OOoBasic est appelé "scriptable" dans la terminologie des composants.
Nous allons commencer par décrire l'exemple du compteur.
Un compteur tout simple
Nous désirons réaliser dans cette section deux fichiers counter.cxx et countermain.cxx travaillant ensembles. Notez au passage que cet exemple serait bien plus simple s'il n'était pas destiné à travailler avec OpenOffice.org. Il nous intéresse parce qu'il est simple et nous servira de point de départ. A partir de là, nous nous poserons un tas de questions et leurs réponses nous montreront, je l'espère, comment les composants fonctionnent. J'utiliserai de temps à autre les notations UML dans ce chapitre.
Implantation en un seul fichier
Nous commençons par le code de notre fameux compteur implantée en un seul fichier (sans compter le fichier d'inclusion) et par la même occasion complètement indépendant d'OpenOffice.org :
//Listing 1 Le compteur
//c++
//la classe et son utilisation dans un seul fichier
#include <stdio.h>
#include "counter.hxx"
MyCounterImpl::MyCounterImpl()
: m_nCount( 0 )
{ printf( "< MyCounterImpl ctor called >\n" ); }
MyCounterImpl::~MyCounterImpl()
{ printf( "< MyCounterImpl dtor called >\n" ); }
int MyCounterImpl::getCount()
{ return m_nCount; }
void MyCounterImpl::setCount( int nCount )
{ m_nCount = nCount; }
int MyCounterImpl::increment()
{ return (++m_nCount); }
int MyCounterImpl::decrement()
{ return (--m_nCount); }
int main(int argc, char **argv)
{
MyCounterImpl Cnt;
Cnt.setCount(50);
printf("-- %d\n",Cnt.getCount());
Cnt.increment();
printf("-- %d\n",Cnt.getCount());
Cnt.decrement();
printf("-- %d\n",Cnt.getCount());
return 0;
}
et son fichier d'entête associé
//Listing 2 Fichier d'entête du compteur
// C++
// counter.hxx
class MyCounterImpl {
int m_nCount;
public:
MyCounterImpl();
~MyCounterImpl();
int getCount();
void setCount( int nCount );
int increment();
int decrement() ;
};
Ce que nous voulons réaliser est la séparation du code en deux parties une pour la classe compteur ( counter.cxx) et la seconde pour l'utilisation de la classe ( countermain.cxx). Le fichier counter.cxx sera compilé en une librairie dynamique (fichier counter.so) et countermain utilisera cette librairie dynamique.
Implantation en deux fichiers
La façon classique de faire cela en C++ est illustrée à présent. Commençons par la librairie dynamique :
//Listing 3 Code C++ de la librairie dynamique
//c++
//counter.cxx
#include <stdio.h>
#include "counter.hxx"
MyCounterImpl::MyCounterImpl()
: m_nCount( 0 )
{ printf( "< MyCounterImpl ctor called >\n" ); }
MyCounterImpl::~MyCounterImpl()
{ printf( "< MyCounterImpl dtor called >\n" ); }
int MyCounterImpl::getCount()
{ return m_nCount; }
void MyCounterImpl::setCount( int nCount )
{ m_nCount = nCount; }
int MyCounterImpl::increment()
{ return (++m_nCount); }
int MyCounterImpl::decrement()
{ return (--m_nCount); }
Ce fichier est compilé à l'aide de la ligne de commande :
gcc -shared -fPIC -o counter.so counter.cxx
pour donner la librairie dynamique "counter.so".
Pour utiliser ce cette librairie il nous faut un programme principal :
//Listing 4 Programme principal
//c++
//countermain.cxx
#include "counter.hxx"
#include <stdio.h>
int main(int argc, char **argv)
{
MyCounterImpl Cnt;
Cnt.setCount(50);
printf("-- %d\n",Cnt.getCount());
Cnt.increment();
printf("-- %d\n",Cnt.getCount());
Cnt.decrement();
printf("-- %d\n",Cnt.getCount());
return 0;
}
La compilation est réalisée avec la ligne de commande :
gcc -o test1 countermain.cxx -L ./ counter.so -lstdc++
L'exécution de cet exemple nous donne à l'écran :
[smoutou@p3 component]$ ./test1 < MyCounterImpl ctor called > -- 50 -- 51 -- 50 < MyCounterImpl dtor called >
Une commande ldd nous montre clairement que ce code a besoin de counter.so :
[smoutou@p3 component]$ ldd test1 counter.so => ./counter.so (0x40015000) libstdc++.so.5 => /usr/lib/libstdc++.so.5 (0x40024000) libc.so.6 => /lib/i686/libc.so.6 (0x400dd000) libm.so.6 => /lib/i686/libm.so.6 (0x4020e000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x40231000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Cet exemple est réalisé de manière classique, c'est à dire qu'il n'utilise aucune librairie de OpenOffice.org. Au contraire, l'exemple donné avec le SDK fait exactement la même chose mais nécessite OpenOffice.org ou en tout cas sa base de registre.
Transformer le compteur pour qu'il puisse être enregistré
Toute les librairies dynamiques ne peuvent pas forcément être enregistrées dans la base de registre d'OpenOffice. Si nous prenons celle que nous avons construit dans la section précédente et essayons de l'enregistrer, regardons ce qui se passe :
[smoutou@p3 bin]$ regcomp -register -r counter.uno.rdb -c counter.so Aborted
Regcomp est complètement incapable de faire son travail correctement Pourquoi ?
Si l'on veut qu'une librairie dynamique puisse être enregistrée, il nous faut suivre des règles très strictes.
Règle 1 :Il vous faut construire un fichier IDL. Ce fichier IDL vous permet de construire un fichier urd que l'on pourra ajouter dans un fichier rdb. Cela a déjà été discuté rapidement ici. Cette démarche a été réalisée en fait lors de notre premier essai d'enregistrement à l'aide de regcomp mais l'erreur qui en a resulté nous montre que cette règle ne suffit pas à elle seule.
Regardez la figure ci-dessous qui est une illustration de cette règle 1.
En l'examinant de plus près vous voyez qu'un fichier some.idl est requis (et naturellement le fichier uno.so correspondant).
Règle 2 : Votre librairie dynamique doit contenir obligatoirement trois sous programmes (au format du langage C). Leur noms et prototypes sont :
extern "C" void SAL_CALL component_getImplementationEnvironment(
sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv );
extern "C" sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry );
extern "C" void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr, void * );
Les deux premiers sous-programmes sont nécessaires pour l'enregistrement et le troisième pour permettre les appels UNO_QUERY.
Règle 3 (règle provisoire) : Votre composant doit contenir d'autres interfaces que celles décrite dans votre fichier IDL : XInterface, XServiceInfo. L'exemple original du compteur les implémente effectivement comme cela est montré dans le listing suivant :
//Listing 6 Les interfaces du compteur
// C++
class MyCounterImpl
: public XCountable
, public XServiceInfo
{
....
Je suppose que XCountable hérite de l'interface com.sun.star.uno.XInterface. (à vérifier tout cela) Il vous faut aussi regarder l'interface com.sun.star.lang.XServiceInfo. La figure ci-dessous nous montre un composant à l'aide d'une vue schématique.
Le rectangle externe du bas de la Figure ci-dessus represente le composant. Dans ce composant, nous voyons notre classe Myclass héritant des interfaces XInterface, XServiceInfo et XCountable, ainsi que les trois procédures C pouvant être appelée de l'extérieur. Ce schéma indique que toutes les méthodes virtuelles des classes héritées sont à écrire (dix en tout pour cet exemple).
La règle 3 définitive est présentée plus loin, mais vous n'avez aucune raison de vous impatienter. Sinon, vous pouvez aussi lire le Developer's Guide à ce sujet.
L'exemple du compteur du SDK
Cet exemple du SDK est partiellement décrit ici en anglais. Il contient deux fichiers : counter.cxx et countermain.cxx. Ces fichiers montrent quelques différences par rapport aux Listing 3 et Listing 4 donnés précédemment parce qu'ils sont liés à OpenOffice.org maintenant, donc plus complexes : countermain.cxx sera responsable de l'enregistrement de "counter.uno.so" et counter.cxx devra implanter toutes les interfaces de la règle 3 provisoire. Ce petit exemple utilise donc une librairie externe enregistrée (counter.uno.so construit à partir counter.cxx) et un programme principal (countermain.cxx) qui l'utilise.
Le compteur généré (comme l'exemple ProfUnoLifeTime du SDK) est capable de s'exécuter même si OpenOffice ne tourne pas. Cela nous indique que ce compteur est un composant minimal (qui n'a rien à faire avec le service manager). MainCounter est le programme binaire à lancer pour le test de l'exemple. Il n'y a pas de flèche directe entre MainCounter et Counter.uno.so (dans la chaîne de compilation de la Figure ci-dessous), cela indique qu'ils sont indépendants l'un de l'autre : si MainCounter veut quelque chose de Counter.uno.so, il doit le demander au cppuhelper (en d'autre mots à Openoffice). Cet exemple a été déjà réalisé sans le cppuhelper ci-dessus, mais notre intention est justement de montrer l'utilisation du cppuhelper dans cette situation spécifique.
La chaîne de compilation pour cet exemple (voir Figure ci-dessus) est un peu plus compliquée que précédemment à cause des deux fichiers sources : counter.cxx et countermain.cxx. A noter comme cela a déjà été dit que nous avons à créer un troisième fichier pour cet exemple : Counter.idl. Le contenu de ce fichier IDL est maintenant compréhensible par un lecteur qui a étudié scrupuleusement le chapitre sur les fichiers IDL :
//Listing 6 The IDL Counter File : Counter.idl
// IDL
#include <com/sun/star/uno/XInterface.idl>
module foo
{
/**
* Interface to count things.
*/
interface XCountable : com::sun::star::uno::XInterface
{
long getCount();
void setCount( [in] long nCount );
long increment();
long decrement();
};
service Counter
{
// exported interface:
interface XCountable;
};
};
Ce fichier IDL décrit l'interface de counter.cxx qui deviendra counter.uno.so (counter.uno.dll sous Windows), un fichier librairie dynamique après compilation. Mais de nouveau vous appelez une des quatre méthodes non pas directement mais à travers cppuhelper. Ce fichier IDL est représenté en Figure ci-dessous.
Modification du Compteur simple pour examiner l'enregistrement
Encore une fois nous partons de l'exemple du compteur inclus dans le SDK et dans le répertoire <OpenOffice.org1.1_SDK>/examples/cpp/counter. Nous le modifions pour voir dans la console un certain nombre d'informations sur la manière dont l'enregistrement travaille et particulièrement la nécessité de la règle 2 décrite dans la section ci-avant. Les modifications consistent seulement à ajouter des printf comme montré dans le listing ci-dessous :
//Listing 7 Modified Counter Component (extract)
// C++
.....
//*************************************************************************
OUString SAL_CALL MyCounterImpl::getImplementationName( )
throw(RuntimeException)
{
printf("MyCounterImpl::getImplementationName( ) called \n");
return OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
}
//*************************************************************************
sal_Bool SAL_CALL MyCounterImpl::supportsService( const OUString& ServiceName )
throw(RuntimeException)
{
printf("MyCounterImpl::supportsService called\n");
Sequence< OUString > aSNL = getSupportedServiceNames();
const OUString * pArray = aSNL.getArray();
for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
if( pArray[i] == ServiceName )
return sal_True;
return sal_False;
}
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames( )
throw(RuntimeException)
{
printf("MyCounterImpl::getSupportedServiceNames( ) called \n");
return getSupportedServiceNames_Static();
}
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames_Static( )
{
OUString aName( RTL_CONSTASCII_USTRINGPARAM(SERVICENAME) );
printf("MyCounterImpl::getSupportedServiceNames_Static( ) called with %s\n",RTL_CONSTASCII_USTRINGPARAM(SERVICENAME));
return Sequence< OUString >( &aName, 1 );
}
/**
* Function to create a new component instance; is needed by factory helper implementation.
* @param xMgr service manager to if the components needs other component instances
*/
Reference< XInterface > SAL_CALL MyCounterImpl_create(
const Reference< XMultiServiceFactory > & xMgr )
{ printf("MyCounterImpl_create called\n");
return Reference< XCountable >( new MyCounterImpl( xMgr ) );
}
//#####################################################################################
//#### EXPORTED ####################################################################################
//######################################################################################
/**
* Gives the environment this component belongs to.
*/
extern "C" void SAL_CALL component_getImplementationEnvironment(const sal_Char ** ppEnvTypeName, uno_Environment ** ppEnv)
{
printf("getImplementationEnvironnement return %s\n",CPPU_CURRENT_LANGUAGE_BINDING_NAME);
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
/**
* This function creates an implementation section in the registry and another subkey
*
* for each supported service.
* @param pServiceManager the service manager
* @param pRegistryKey the registry key
*/
extern "C" sal_Bool SAL_CALL component_writeInfo(void * pServiceManager, void * pRegistryKey)
{
sal_Bool result = sal_False;
printf("component_writeInfo called\n");
if (pRegistryKey)
{
try
{
Reference< XRegistryKey > xNewKey(
reinterpret_cast< XRegistryKey * >( pRegistryKey )->createKey(
OUString( RTL_CONSTASCII_USTRINGPARAM("/" IMPLNAME "/UNO/SERVICES") ) ) );
printf("New key : %s\n",RTL_CONSTASCII_USTRINGPARAM("/" IMPLNAME "/UNO/SERVICES"));
printf("--component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static()\n");
const Sequence< OUString > & rSNL =
MyCounterImpl::getSupportedServiceNames_Static();
const OUString * pArray = rSNL.getConstArray();
for ( sal_Int32 nPos = rSNL.getLength(); nPos--; ) {
xNewKey->createKey( pArray[nPos] );
printf("----Sous-Key : %s build\n",OUStringToOString(pArray[nPos],
RTL_TEXTENCODING_ASCII_US)
.pData->buffer);
}
return sal_True;
}
catch (InvalidRegistryException &)
{
// we should not ignore exceptions
}
}
return result;
}
/**
* This function is called to get service factories for an implementation.
*
* @param pImplName name of implementation
* @param pServiceManager a service manager, need for component creation
* @param pRegistryKey the registry key for this component, need for persistent data
* @return a component factory
*/
/**/
extern "C" void * SAL_CALL component_getFactory(const sal_Char * pImplName, void * pServiceManager, void * pRegistryKey)
{
void * pRet = 0;
printf("component_getFactory called\n");
if (rtl_str_compare( pImplName, IMPLNAME ) == 0)
{
Reference< XSingleServiceFactory > xFactory( createSingleFactory(
reinterpret_cast< XMultiServiceFactory * >( pServiceManager ),
OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) ),
MyCounterImpl_create,
MyCounterImpl::getSupportedServiceNames_Static() ) );
if (xFactory.is())
{
xFactory->acquire();
pRet = xFactory.get();
}
}
return pRet;
}
Avec countermain légèrement modifié pour savoir à partir de quand l'écriture dans la base de registre commence, cette pièce de code produit la sortie suivante :
[smoutou@p3 counter]$ make countermain.run cd ../../../LINUXexample.out/bin && countermain Here begin registry getImplementationEnvironnement return gcc3 component_writeInfo called New key : /com.sun.star.comp.example.cpp.Counter/UNO/SERVICES --component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static() MyCounterImpl::getSupportedServiceNames_Static( ) called with foo.Counter ----Sous-Key : foo.Counter build Here ends registry and begin instanciation getImplementationEnvironnement return gcc3 component_getFactory called MyCounterImpl::getSupportedServiceNames_Static( ) called with foo.Counter MyCounterImpl_create called < MyCounterImpl2 ctor called > 42,43,42 Another registry use : getImplementations -- com.sun.star.comp.bridge.UnoUrlResolver < MyCounterImpl2 dtor called > [smoutou@p3 counter]$
tandis qu'une utilisation directe de regcomp produit la sortie suivante :
[smoutou@p3 bin]$ regcomp -register -r counter.uno.rdb -c counter.uno.so getImplementationEnvironnement return gcc3 component_writeInfo called New key : /com.sun.star.comp.example.cpp.Counter/UNO/SERVICES --component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static() MyCounterImpl::getSupportedServiceNames_Static( ) called with foo.Counter ----Sous-Key : foo.Counter build register component 'counter.uno.so' in registry 'counter.uno.rdb' succesful! [smoutou@p3 bin]$
Nous pouvons tirer des deux sorties consoles que l'enregistrement travaille de la façon attendue. Le premier appel demande des renseignements sur l'environnement qui retourne ici “gcc3”, et ensuite componentwriteinfo qui est responsable de l'enregistrement lui-même.
Résultats
Une question : comment est perçu notre compteur par OOoBasic à cette étape ? Parce que notre compteur est enregistré nous le voyons partiellement, mais nous ne pouvons en aucun cas l'utiliser.
'Listing 8 Petit OOoBasic programme pour voir notre compteur
REM ***** BASIC *****
Sub Main
ocmpt = createUnoService("foo.Counter")
XRay oCmpt
End Sub
En utilisant l'outil XRay sur cet objet comme montré dans le listing ci-dessus, cela nous donne les informations suivantes :
properties
--- Object internal name : ( no name ) Dbg_Methods string <...> basic prop, read-only Dbg_Properties string <...> basic prop, read-only Dbg_SupportedInterfaces string <...> basic prop, read-only
methods
--- Object internal name : ( no name ) queryInterface ( aType as type ) AS variant acquire ( ) release ( )
supported interfaces
--- List of supported interfaces --- com.sun.star.uno.XInterface
Comme on peut le voir ci-dessus, seule l'interface com.sun.star.uno.XInterface peut être vue : pas grandiose ! On peut ainsi l'instancier mais on ne pourra pas appeler une de ses méthodes. L'étape suivante est de pouvoir utiliser le compteur avec OOoBasic : en d'autre mots de le rendre scriptable.
Utilisation d'une aide (helper) pour construire le composant scriptable
Nous pouvons trouver un document décrivant cet exemple (de Daniel Bölzle) mais que je n'ai pas réussi à faire fonctionner. Je vais donc construire ce composant moi-même.
Les règles 1 et 2 sont expliquées ici.
Si vous voulez rendre votre composant scriptable il vous faut modifier la règle 3 provisoire précédente comme suit :
Règle 3 (définitive) : Votre composant doit fournir les interfaces : com.sun.star.uno.XInterface, com.sun.star.lang.XServiceInfo, com.sun.star.uno.XWeak, et com.sun.star.lang.XTypeProvider. La dernière est absolument caractéristique d'un composant scriptable. Voir aussi le Developer's Guide à ce sujet.
La Figure ci-après nous montre la version définitive d'un composant "scriptable".
Vous voyez que la classe que vous avez à implanter hérite de certaines interfaces : toutes les méthodes de ces interfaces se trouvent dans votre composant et il vous faut donc les implanter. Comme il sera montré plus loin, nous utilisons une aide (helper en anglais que je ne sais pas traduire) et cela signifie que nous n'aurons pas à implémenter nous-même toutes ces méthodes : seules celles de l'interface com.sun.star.lang.XServiceInfo est nécessaire : les trois autres sont implémentées automatiquement avec l'aide (helper) comme c'est indiqué dans cette page du Developer's Guide.
Donnons pour conclure, notre schéma définitif de l'implantation avec Helper :
Règle 4 : Vous voyez deux interfaces sur le figure ci-dessus, alors vous aurez à utiliser "::cppu::WeakImplHelper2" quand vous allez définir votre classe.
Que dire de notre makefile ? Nous ne sommes plus vraiment intéressé par appeler notre compteur par countermain.cxx mais plutôt par l'utilisation de celui-ci dans OooBasic, ce qui signifie que nous devons enregistrer notre librairie dynamique à l'aide de notre Makefile et non plus à l'aide de "countermain.cxx". Deux solutions s'offrent à nous :
- nous laissons encore countermain réaliser l'enregistrement et nous stoppons ce programme juste après cet enregistrement ce qui nous laisse le temps d'utiliser un programme OOoBasic,
- nous cherchons un exemple du SDK qui réalise correctement l'enregistrement. On peut en trouver un dans le SDK comme mentionné plus haut :<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent
et ainsi nous pouvons utiliser le makefile de ce composant.
Pour le moment je vais donc expliquer la méthode à suivre à partir de l'exemple CppComponent du SDK.
Comment faire fonctionner nos exemples
Si vous voulez faire fonctionner l'exemple du compteur et tous les autres du chapitre suivant, le mieux est de partir du répertoire
<OOo_SDK>/examples/DevelopersGuide/Component/CppComponent
et de le copier entièrement en
<OOo_SDK>/examples/DevelopersGuide/Component/MyComponent
par exemple. Le point important étant qu'il ait la même profondeur sans quoi il y aurait un peu plus de changement à réaliser dans le Makefile, changements indiqués plus loin.
Fichier CppComponent.uno.xml
Dans votre répertoire <OOo_SDK>/examples/DevelopersGuide/Component/MyComponent nouvellement créé, vous pouvez voir un fichier CppComponent.uno.xml Ouvrez-le et remplacer
<type>my_module.XSomething</type> <type>my_module.MyService1</type> <type>my_module.MyService2</type>
par
<type>foo.XCountable</type> <type>foo.Counter</type>
Fichier Makefile
Dans le Makefile original remplacer
IDLFILES = some.idl
par
IDLFILES = Counter.idl
et remplacer aussi
CXXFILES = service1_impl.cxx \ service2_impl.cxx
par
CXXFILES = service_impl.Last.OK.cxx # service2_impl.cxx
où vous adapterez le nom de votre fichier source.
Fichier source IDL
Pour accéder au compteur en Basic, vous devez utiliser le même fichier IDL que dans le Listing 2.
Fichier source compteur
Nous donnons maintenant le programme complet du compteur.
//Listing 9 Programme complet du compteur
// C++
// service_impl.Last.OK.cxx
#ifndef _RTL_USTRING_HXX_
#include <rtl/ustring.hxx>
#endif
#ifndef _CPPUHELPER_QUERYINTERFACE_HXX_
#include <cppuhelper/queryinterface.hxx> // helper for queryInterface() impl
#endif
#ifndef _CPPUHELPER_FACTORY_HXX_
#include <cppuhelper/factory.hxx> // helper for component factory
#endif
// New
#include <cppuhelper/implbase2.hxx> // "2" implementing two interfaces
#include <cppuhelper/implementationentry.hxx>
// generated c++ interfaces
#ifndef _COM_SUN_STAR_LANG_XSINGLESERVICEFACTORY_HPP_
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XMULTISERVICEFACTORY_HPP_
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XSERVICEINFO_HPP_
#include <com/sun/star/lang/XServiceInfo.hpp>
#endif
#ifndef _COM_SUN_STAR_REGISTRY_XREGISTRYKEY_HPP_
#include <com/sun/star/registry/XRegistryKey.hpp>
#endif
#ifndef _FOO_XCOUNTABLE_HPP_
#include <foo/XCountable.hpp>
#endif
#define SERVICENAME "foo.Counter"
#define IMPLNAME "com.sun.star.comp.example.cpp.Counter"
namespace my_sc_impl
{
static Sequence< OUString > getSupportedServiceNames_MyCounterImpl()
{
static Sequence < OUString > *pNames = 0;
if( ! pNames )
{
// MutexGuard guard( Mutex::getGlobalMutex() );
if( !pNames )
{
static Sequence< OUString > seqNames(1);
seqNames.getArray()[0] = OUString(RTL_CONSTASCII_USTRINGPARAM(SERVICENAME));
pNames = &seqNames;
}
}
return *pNames;
}
static OUString getImplementationName_MyCounterImpl()
{
static OUString *pImplName = 0;
if( ! pImplName )
{
// MutexGuard guard( Mutex::getGlobalMutex() );
if( ! pImplName )
{
static OUString implName( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
pImplName = &implName;
}
}
return *pImplName;
}
// New
class MyCounterImpl : public ::cppu::WeakImplHelper2<
XCountable, XServiceInfo >
{
// to obtain other services if needed
Reference< XMultiServiceFactory > m_xServiceManager;
sal_Int32 m_nCount;
public:
// XServiceInfo implementation
virtual OUString SAL_CALL getImplementationName( ) throw(RuntimeException);
virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) throw(RuntimeException);
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) throw(RuntimeException);
static Sequence< OUString > SAL_CALL getSupportedServiceNames_Static( );
// XCountable implementation
virtual sal_Int32 SAL_CALL getCount() throw (RuntimeException)
{ return m_nCount; }
virtual void SAL_CALL setCount( sal_Int32 nCount ) throw (RuntimeException)
{ m_nCount = nCount; }
virtual sal_Int32 SAL_CALL increment() throw (RuntimeException)
{ return (++m_nCount); }
virtual sal_Int32 SAL_CALL decrement() throw (RuntimeException)
{ return (--m_nCount); }
};
//*************************************************************************
OUString SAL_CALL MyCounterImpl::getImplementationName( )
throw(RuntimeException)
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
}
//*************************************************************************
sal_Bool SAL_CALL MyCounterImpl::supportsService( const OUString& ServiceName )
throw(RuntimeException)
{
Sequence< OUString > aSNL = getSupportedServiceNames();
const OUString * pArray = aSNL.getArray();
for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
if( pArray[i] == ServiceName )
return sal_True;
return sal_False;
}
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames( )
throw(RuntimeException)
{
return getSupportedServiceNames_Static();
}
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames_Static( )
{
OUString aName( RTL_CONSTASCII_USTRINGPARAM(SERVICENAME) );
return Sequence< OUString >( &aName, 1 );
}
//***********NEW
Reference< XInterface > SAL_CALL MyCounterImpl_create(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< XTypeProvider * >( new MyCounterImpl() );
}
}
//##################################################################################################
//#### EXPORTED ####################################################################################
/* shared lib exports implemented without helpers in service_impl1.cxx */
namespace my_sc_impl
{
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
{
MyCounterImpl_create, getImplementationName_MyCounterImpl,
getSupportedServiceNames_MyCounterImpl, ::cppu::createSingleComponentFactory,
0, 0
},
{ 0, 0, 0, 0, 0, 0 }
};
}
extern "C"
{
void SAL_CALL component_getImplementationEnvironment(
sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv )
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
sal_Bool SAL_CALL component_writeInfo(
XMultiServiceFactory * xMgr, XRegistryKey * xRegistry )
{
return ::cppu::component_writeInfoHelper(
xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
void * SAL_CALL component_getFactory(
sal_Char const * implName, XMultiServiceFactory * xMgr,
XRegistryKey * xRegistry )
{
return ::cppu::component_getFactoryHelper(
implName, xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
}
Voila, nous sommes prêts pour les tests maintenant.
Fichier TestCppComponent.cxx
Dans votre répertoire vous aves un fichier appelé TestCppComponent.cxx qu'il vous faut modifier car celui-ci est compilé pour un autre exemple que le votre. Le meilleur moyen est de mettre en commentaire tout le contenu du main. A ce moment là votre test du compteur ne pourra pas se faire avec la commande :
make TestCppComponent.run
mais seulement par
make SimpleComponent.odt.load
et vous modifiez le programme OOoBasic du document SimpleComponent.odt comme indiqué ci-après. Nous apprendrons à appeler notre compteur à partir du langage C++ un peu plus tard.
Fichier OOoBasic de test
Le programme OOoBasic pour tester cet exemple est maintenant :
'Listing 10 Programme OOoBasic de test
REM ***** BASIC *****
Sub Main
Dim oSimpleComponent
oSimpleComponent = CreateUnoService( "foo.Counter" )
oSimpleComponent.setCount(5)
print oSimpleComponent.getCount()
oSimpleComponent.increment()
print oSimpleComponent.getCount()
'XRay oSimpleComponent
End Sub
Ce programme de test peut être écrit dans SimpleComponent.odt and lancé avec le bouton fait pour cela. Tout cela se fait avec un
make SimpleComponent.oxt.load
Une introspection avec XRay du nouveau service créé nous montre maintenant :
properties
--- Object internal name : com.sun.star.comp.example.cpp.Counter Count long ImplementationName string SupportedServiceNames ] string Types []type ImplementationId []byte Dbg_Methods string Dbg_Properties string Dbg_SupportedInterfaces string
methods
--- Object internal name : com.sun.star.comp.example.cpp.Counter queryInterface ( aType as type ) acquire ( ) release ( ) getCount ( ) setCount ( nCount as long ) increment ( ) decrement ( ) getImplementationName ( ) supportsService ( ServiceName as string ) getSupportedServiceNames ( ) getTypes ( ) getImplementationId ( ) queryAdapter ( )
A noter pour conclure que l'outil XRay peut vous permettre de faire fonctionner le compteur en "XRayant" la méthode increment par exemple. L'inspecteur Java permet en plus d'utiliser la méthode setCount qui demande un paramètre (ce que XRay ne sait pas encore faire). |
L'utilisation de l'inspecteur Java se fait avec un programme OOoBasic :
'Listing 10b Programme OOoBasic de test
REM ***** BASIC *****
Dim oSimpleComponent
oSimpleComponent = CreateUnoService( "foo.Counter" )
oInspector = createUnoService("org.openoffice.InstanceInspector")
oInspector.inspect(oSimpleComponent, "MyCounter")
End Sub
Un compteur avec attribut
Une petite variation sur le composant compteur va nous accaparer maintenant : l'utilisation d'un attribut. Gardez à l'esprit que dans ce cas on accède automatiquement à l'attribut par deux méthodes set/get suivie du nom de l'attribut. Ces méthodes, j'insiste un peu, sont créées automatiquement il faut donc les retirer (ou ne pas les ajouter) dans le fichier IDL. Mon nouveau fichier IDL devient ainsi :
//Listing 11 Notre nouveau fichier IDL pour le compteur
//IDL
#include <com/sun/star/uno/XInterface.idl>
//#include <com/sun/star/lang/XInitialization.idl>
module foo
{
interface XCountable : com::sun::star::uno::XInterface
{
// long getCount(); ************* generee automatiquement
// void setCount( [in] long nCount ); ********** generee automatiquement
[attribute] long Count;
long increment();
long decrement();
};
service Counter
{
interface XCountable;
};
};
Comment ce nouveau compteur est-il vu du OooBasic ? On ne voit plus du tout getCount and setCount ! Cela ne signifie pas qu'elles ne sont pas disponibles. Pour prouver qu'elles sont bien disponibles, utilisons le programme OooBasic suivant. Il fonctionne correctement.
'Listing 12
REM ***** BASIC *****
Sub Main
Dim oSimpleComponent
oSimpleComponent = CreateUnoService( "my_module.MyService" )
oSimpleComponent.Count = 5
print oSimpleComponent.Count
oSimpleComponent.increment()
print oSimpleComponent.Count
XRay oSimpleComponent
End Sub
Une nouvelle introspection avec Xray du nouveau service créé nous montre maintenant :
properties
--- Object internal name : com.sun.star.comp.example.cpp.Counter Count long ImplementationName string <...> pseudo-prop, read-only SupportedServiceNames []string pseudo-prop, read-only Types []type pseudo-prop, read-only ImplementationId []byte pseudo-prop, read-only Dbg_Methods string <...> basic prop, read-only Dbg_Properties string <...> basic prop, read-only Dbg_SupportedInterfaces string <...> basic prop, read-only
methods
--- Object internal name : com.sun.star.comp.example.cpp.Counter queryInterface ( aType as type ) AS variant acquire ( ) release ( ) increment ( ) AS long decrement ( ) AS long getImplementationName ( ) AS string supportsService ( ServiceName as string ) AS boolean getSupportedServiceNames ( ) AS []string getTypes ( ) AS []type getImplementationId ( ) AS []byte queryAdapter ( ) AS object
Construire le compteur sans aide (helper)
Nous avons vu comment utiliser une aide pour éviter de coder certaines interfaces. Il est naturellement possible au programmeur de tout coder lui-même. C'est ce que nous voulons faire pour l'exemple du compteur.
A faire...
Composants évolués
Les composants peuvent proposer des interfaces très évoluées, capables de travailler sur des documents. Il vous faudra pour cela obtenir un contexte et/ou un Service Manager.
Notre deuxième composant
Je ne laisse cette section que parce qu'elle reprend un exemple du Developer's Guide en le francisant. J'ai retiré cet exemple de la version anglaise du document car il est bien mieux traité dans le Developer's Guide. |
Notre première tâche consiste à trouver un morceau de code comme point de départ. L'idée générale est de partir de : “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent” et de modifier légèrement le code. Commençons par décrire l'exemple : un seul module comportant deux services (MyService1 et MyService2). Ce que réalise cet exemple n'est pas extraordinaire mais fournit un bon point de départ. Voici le fichier IDL décrivant tout cela :
//Listing 13 Fichier IDL de départ
// IDL
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
module my_module
{
interface XSomething : com::sun::star::uno::XInterface
{
string methodOne( [in] string val );
};
service MyService1
{
interface XSomething;
};
service MyService2
{
interface XSomething;
interface com::sun::star::lang::XInitialization;
};
};
Le programme Basic suivant nous montre comment accéder à ce composant (ses services et interfaces).
'Listing 14 Accéder à un service et son interface en OOoBasic
REM ***** BASIC *****
Sub demonstrateSimpleComponent
Dim oSimpleComponent
oSimpleComponent = CreateUnoService( "my_module.MyService1" )
msgbox oSimpleComponent.methodOne("Component succesfully instantiated!")
'XRay oSimpleComponent
End Sub
Nous avons décidé en fait de simplifier cet exemple de départ, car il n'y a aucun exemple plus simple fourni avec le SDK. Le listing suivant nous fournit notre nouveau fichier IDL qui définit un seul service « MyService » une seule interface « XSomething » et deux méthodes.
Listing 15 Notre fichier IDL de départ
// IDL
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
module my_module
{
interface XSomething : com::sun::star::uno::XInterface
{
string methodOne( [in] string val );
string methodTwo( [in] string val );
};
service MyService
{
interface XSomething;
};
};
Il suffit ensuite de renommer le fichier “service2_impl.cxx” trouvé dans l'exemple “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent” comme “service_impl.cxx” et ensuite réaliser les changements marqués en rouge dans le listing suivant :
Listing 16
// service_impl.cxx
#include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <my_module/XSomething.hpp>
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for odk interfaces
using namespace ::com::sun::star::uno; // for basic types
namespace my_sc_impl
{
static Sequence< OUString > getSupportedServiceNames_MyServiceImpl()
{
static Sequence < OUString > *pNames = 0;
if( ! pNames )
{
// MutexGuard guard( Mutex::getGlobalMutex() );
if( !pNames )
{
static Sequence< OUString > seqNames(1);
seqNames.getArray()[0] = OUString(RTL_CONSTASCII_USTRINGPARAM("my_module.MyService"));
pNames = &seqNames;
}
}
return *pNames;
}
static OUString getImplementationName_MyServiceImpl()
{
static OUString *pImplName = 0;
if( ! pImplName )
{
// MutexGuard guard( Mutex::getGlobalMutex() );
if( ! pImplName )
{
static OUString implName( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_implementation.MyService") );
pImplName = &implName;
}
}
return *pImplName;
}
class MyServiceImpl : public ::cppu::WeakImplHelper3<
::my_module::XSomething, lang::XServiceInfo, lang::XInitialization >
{
OUString m_arg;
public:
// focus on three given interfaces,
// no need to implement XInterface, XTypeProvider, XWeak
// XInitialization will be called upon createInstanceWithArguments[AndContext]()
virtual void SAL_CALL initialize( Sequence< Any > const & args )
throw (Exception);
// XSomething
virtual OUString SAL_CALL methodOne( OUString const & str )
throw (RuntimeException);
// **********************ADDED
virtual OUString SAL_CALL methodTwo( OUString const & str )
throw (RuntimeException);
// ********************** END ADDED
// XServiceInfo
virtual OUString SAL_CALL getImplementationName()
throw (RuntimeException);
virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
throw (RuntimeException);
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
throw (RuntimeException);
};
// XInitialization implemention
void MyServiceImpl::initialize( Sequence< Any > const & args )
throw (Exception)
{
if (1 != args.getLength())
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("give a string instanciating this component!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
if (! (args[ 0 ] >>= m_arg))
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("no string given as argument!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
}
// XSomething implementation
OUString MyServiceImpl::methodOne( OUString const & str )
throw (RuntimeException)
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(
"called methodOne() of MyService implementation: ") ) + m_arg + str;
}
// **********************ADDED
OUString MyServiceImpl::methodTwo( OUString const & str )
throw (RuntimeException)
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(
"called methodTwo() of MyService2 implementation: ") ) + m_arg + str;
}
// ********************** END ADDED
// XServiceInfo implementation
OUString MyServiceImpl::getImplementationName()
throw (RuntimeException)
{
// unique implementation name
return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService") );
}
sal_Bool MyServiceImpl::supportsService( OUString const & serviceName )
throw (RuntimeException)
{
// this object only supports one service, so the test is simple
return serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("my_module.MyService") );
}
Sequence< OUString > MyServiceImpl::getSupportedServiceNames()
throw (RuntimeException)
{
return getSupportedServiceNames_MyServiceImpl();
}
Reference< XInterface > SAL_CALL create_MyServiceImpl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyServiceImpl() );
}
}
/* shared lib exports implemented without helpers in service_impl1.cxx */
namespace my_sc_impl
{
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
{
create_MyServiceImpl, getImplementationName_MyServiceImpl,
getSupportedServiceNames_MyServiceImpl,::cppu::createSingleComponentFactory,
0, 0
},
{ 0, 0, 0, 0, 0, 0 }
};
}
extern "C"
{
void SAL_CALL component_getImplementationEnvironment(
sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv )
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry )
{
return ::cppu::component_writeInfoHelper(
xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr,
registry::XRegistryKey * xRegistry )
{
return ::cppu::component_getFactoryHelper(
implName, xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
}
Le fichier makefile est seulement changé pour prendre en compte des noms modifiés. Ensuite le module est complété et testé avec le programme OOoBasic :
'Listing 17 Programme OooBasic de test
REM ***** BASIC *****
Sub Main
Dim oSimpleComponent
oSimpleComponent = CreateUnoService( "my_module.MyService" )
msgbox oSimpleComponent.methodOne( "Component succesfully instantiated!" )
msgbox oSimpleComponent.methodTwo( "Component succesfully instantiated!" )
'XRay oSimpleComponent
End Sub
D'où viennent les extern “C” présents dans le code source ? Ce problème a déjà été évoqué ici.
Nous avons en fait à implanter l'interface com.sun.star.lang.XServiceInfo quand on crée un composant. Cette interface est décrite comme d'habitude à l'aide du fichier IDL correspondant :
//Listing 18 Fichier IDL correspondant au service XServiceInfo
// IDL
module com { module sun { module star { module lang {
interface XServiceInfo: com::sun::star::uno::XInterface
{
string getImplementationName();
boolean supportsService( [in] string ServiceName );
sequence<string> getSupportedServiceNames();
};
}; }; }; };
Nous retrouvons des méthodes que l'on a pu reconnaître dans le code complet du composant. Ce service fournit de l'information concernant l'implémentation, c'est à dire quels services sont implantés et le nom de l'implantation. La première méthode fournit le nom de l'implantation du service La seconde méthode teste si le service passé en argument est disponible, c'est à dire implanté dans le composant. La troisième méthode fournit des noms ded service de l'implantation, incluant aussi indirectement les noms des services. Nous en déduisons une règle : quand les services sont écrits les trois méthodes de l'interface XserviceInfo doivent être écrites comme des fonctions C externes avec un préfixe “component_”.
Les problèmes spécifiques à Microsoft Windows
Notre exemple
Notre exemple est encore basé sur le compteur. Je commence par donner le code source complet car il est un peu modifié par rapport à l'exemple déjà traité. Commençons par le fichier d'inclusion :
//Listing 20 counter.hxx
// C++
class MyCounterImpl {
int m_nCount;
public:
MyCounterImpl();
~MyCounterImpl();
int getCount();
void setCount( int nCount );
int increment();
int decrement() ;
};
et par son code source C++ correspondant :
// counter.cxx Listing 21
#include <stdio.h>
#include "counter.hxx"
MyCounterImpl::MyCounterImpl()
: m_nCount( 0 )
{ printf( "< MyCounterImpl ctor called >\n" ); }
MyCounterImpl::~MyCounterImpl()
{ printf( "< MyCounterImpl dtor called >\n" ); }
int MyCounterImpl::getCount()
{ return m_nCount; }
void MyCounterImpl::setCount( int nCount )
{ m_nCount = nCount; }
int MyCounterImpl::increment()
{ return (++m_nCount); }
int MyCounterImpl::decrement()
{ return (--m_nCount); }
extern "C" void essai1(int *a){
MyCounterImpl count;
count.setCount( *a);
count.increment();
*a=count.getCount();
}
Notez que j'ai ajouté une fonction externe C nommée "essai1". Le point important est que je ne peux pas utiliser directement les classes car les différents compilateurs leur donne des noms différents dans les fichiers objets ou librairies générés.
VC++ ajoute un "_" comme prefix du nom de la fonction et un "@n" comme suffix avec n comme taille en octets des paramètres. Dans notre exemple "_essai1@4" serait exporté. |
Et maintenant le listing du programme principal :
//Listing 22 Main Program
//c++
//countermain.cxx
#include <stdio.h>
extern "C" void essai1(int *a);
// le __declspec(dllimport) ne marche pas
//__declspec(dllimport) void essai1(void);
int main(int argc, char **argv)
{ int b=12;
essai1(&b);
printf("resultat attendu : 13, resultat calcule : %d\n",b);
return 0;
}
Passons maintenant à la description de la phase de compilation.
Compilation
Le Listing 21 est compilé sous cygwin avec les outils du GNU et transformé en vraie dll.
gcc -c counter.cxx -o counter.o dlltool --export-all --output-def counter.def counter.o dllwrap counter.o --dllname counter.dll --def counter.def
L'option "-fPIC" ne semble pas nécessaire sous cygwin. Arrivé ici vous avez un fichier counter.dll. Copiez-le ainsi que counter.def dans un répertoire que vous allez utiliser avec VC++.
La compilation sous VC++ se fait comme suit
lib /machine:i386 /def:counter.def cl countermain.cxx counter.lib
Voila c'est fait. La première ligne construit le fichier counter.lib qui est utilisé en deuxième ligne. Si votre cygwin1.dll est accessible, vous lancez simplement countermain
et regardez ce qui se passe.
MakeFile
Le chapitre sur les MakeFile est en construction et même pas traduit en français pour le moment, mais nous espérons ajouter un exemple de makeFile qui gère ce genre de situations dans le futur (aussi proche que possible). |
Nous allons maintenant examiner comment transformer un composant en addin.
Composant comme add-in simplifié
Il est possible d'utiliser un composant comme add-in OooCalc, c'est à dire une fonction OooCalc qui utilise le code du composant. L'idée est d'écrire une fonction OOoBasic qui appelle le composant et d'utiliser la fonction OOoBasic dans le tableur et cela parcequ'OpenOffice autorise l'utilisation de fonctions OOoBasic dans son tableur. Il nous est donc possible d'utiliser du code C++ dans le tableur de la manière suivante : Calc appelle le Basic qui appelle le C++. Cette manière de procéder a des limites, car les variables OOoBasic meurent quand les macros finissent et qu'ainsi il est difficile de stocker des résultats intermédiaire. Cela dit, c'est peut être pas si simple à faire non plus en C++. Il est difficile aussi de partager des objets entre deux sous-programmes Basic ! Nous pouvons donner un exemple. Le fichier IDL correspondant est :
module my_module {
interface XSomething : com::sun::star::uno::XInterface
{
long addFive(
[in] long intDummy
);
long addSix(
[in] long intDummy
);
};
service MyService
{
interface XSomething;
};
};
Si nous construisons complètement ce composant on peut écrire le programme Basic pour tester le fonctionnement :
REM ***** BASIC *****
function demo(val as long) as long
Dim oSimpleComponent As object
oSimpleComponent = CreateUnoService( "my_module.MyService" )
demo = oSimpleComponent.addFive(val)
end Function
De retour dans notre feuille de calcul, nous pouvons écrire dans une cellule “=demo(6)” ou “= demo(B2)” si B2 contient une valeur numérique.
Nous examinerons plus loin dans ce document la vraie manière de construire un add-in.
Réaliser des composants avec boîtes de dialogue
Ce problème est tellement vaste que nous lui réservons une page entière ici où nous aborderons le problème du compteur et de sa relation avec sa boîte de dialogue de plusieurs façons différentes.
Retour à la page d'accueil
Page d'accueil du développement C++ à l'aide du SDK
Voir aussi
- Version anglaise de cette page
- Daniel Bölzle's tutorial : Writing a simple UNO component
- Writing UNO Components in Developer's Guide
- C++ and UNO tutorial
- Creating an URE application in C++
- Service Declaration
- Registering properties
- UNO tutorial
- UNO IDL
- Extensions Packager (BasicAddonBuilder from Paolo Mantovani)
- BASIC UNO Object Browser : You can see the corresponding code as a complex component.
- Minimal Java Component
- General UNO Component project Type in Java
- Office Add-on Projet Type in Java
- Uno/Article/Types&Reflection
- Component with Python
- Extensions et packages UNO
- Writing a Program to Control OpenOffice.org, by Franco Pingiori — Part 1 and Part 2, Linux Journal
- Bernard Marcelly's XRay tool description in this wiki
- See also Extension Deployement
- (Python) UNO component packaging