FR/Documentation/Construire des composants

From Apache OpenOffice Wiki
< FR‎ | Documentation
Revision as of 08:12, 10 July 2008 by SergeMoutou (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Notre propos est d'étendre OpenOffice.org1.1 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 dir à Openoffice.org1.1 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 registery. Registery a déjà été présenté au chapitre 10. La terminologie de Danny Brewer (http://www.oooforum.org/forum/viewtopic.php?t=13335&postdays=0&postorder=asc&start=0) Clarifions (et peut-être inventons) un peu de terminologie.

Add On Un item supplémentaire sur la barre de menu ou un icône sut la barre d'outils. En modifiant le sous neoud de configuration pour un AddOn, vous pouvez ajouter des items nouveaux et des items sous forme d'icnone. Les programmes OOoBasic peuvent modifier la configuration pour ajouter des items au menu. Il n'est pas nécessaire d'utiliser pkgchk. Les composants (voir plus loin) peuvent aussi ajouter des items sur le menu quand ils sont installés en utilisant l'outil pkgchk.

Add In Un composant (voir plus loin) qui fournit des nouvelles fonctions au tableur OOoCalc. Ces fonctions sont alors visibles dans la boîte de dialogue de l'autopilote des fonctions. La 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 pkgchk. 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 8. Le but final est de créer un fichier d'extension uno.so ainsi qu'un fichier rdb.


Les flèches noires de la Figure 8 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 : 1.Créer les fichiers idl et cpp. La Figure 8 montre ces fichiers avec comme nom some.idl et some.cpp. 2.Uitiser idlc pour compiler les fichiers idl en un fichier urd. 3.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. 4.Utiliser cppumaker pour créer les fichiers d'entête hpp et hdl. 5.Utiliser gcc pour compiler le fichier cpp. 6.L'enregistrement final dans la base ded registre a déjà été discuté précédemment et n'est pas montré en Figure 8. 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 suivant est de créer un composant visible d'OpenOffice.org ou plus précisément, visible d'OOoBasic. Le SDK contient un exemple dans le répertoire : “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent”.

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 (utilisé dans le chapitre précédent) <OpenOffice.org1.1_SDK>/examples/cpp/counter Le problème à résoudre pour le deuxième exemple du compteur du SDK est que celui-ci est inaccessible du OOoBasic. Nous allons commencer par décrire cet exemple. Un compteur tout simple Nous commençons cette section par les fichiers counter.cxx et countermain.cxx travaillant ensemble. Notez au passage que cet exemple serait bien plus simple s'il n'était pas destiné à travailler avec OpenOffice.org. Il est seulement donné comme point de départ. De ce point de départ nous allons nous poser un tas de questions qui nous montrerons, je l'espère, comment les composants fonctionnent. J'utiliserai ded temps à autre les notations UML dans ce chapitre. Ainsi nous démarrons avec ce code Listing 163 Le compteur

//c++

  1. include <stdio.h>
  2. 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 164 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. La façon classique de faire cela en C++ est illustrée à présent. Commençons par la librairie dynamique : Listing 165 Code C++ de la librairie dynamique //c++ //counter.cxx

  1. include <stdio.h>
  2. 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 utiliser ce cette librairie il nous faut un programme principal : Listing 166 Programme principal //c++ //countermain.cxx

  1. include "counter.hxx"
  2. 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. Transformer le compteur pour qu'il puisse être enregistré Toute les librairies dynamiques ne sont 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é dans la section 2.5.1. 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.


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 C). Leur noms 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écessaire 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 167 Les interfaces du compteur // C++ class MyCounterImpl : public XCountable , public XServiceInfo { .... Je suppose que XCountable hérite de l'interface XInterface. (à vérifier tout cela) La figure ci-dessous nous montre un composant à l'aide d'une vus schématique.

To do : correct this figure Le rectangle externe de la Figure 15 represente le composant. Dans ce composant, nous voyons l'interface XCountable héritant des interfaces XInterface et XServiceInfo et les trois procédures C pouvant être appelée de l'extérieur.

L'exmple du compteur

Cet exemple donné avec le SDK est partiellement traité : voir le chapitre 11.5. Il contient deux fichiers : counter.cxx et countermain.cxx. Ces fichiers montrent quelques différences par rapport aux Listing 165 et Listing 164 donné précédemment parcequ'ils sont liés à OpenOffice.org maintenant. Ce petit exemple utilise un module externe enregistré (counter.uno.so construit à partir counter.cxx) et un programme principal (countermain.cxx) qui l'utilise. Le compteur généré, comme ProfUnoLifeTime, est capable de s'exécuter même si OpenOffice ne tourne pas (voir Figure 12.4). Cela nous indique que le compteur est un composant minimal (qui n'a rien à faire avec le service manager). MainCounter en Figure 12.4 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, cela indique qu'ils sont indépendants l'un de l'autre : si MainCounterveut quelquechose 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, mais notre nouvelle intention est justement de montrer l'utilisation du cppuhelper dans cette situation spécifique. La chaîne de compilation pour cet exemple (voir Figure 16) est un peu plus compliqué 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 : XCounter.idl. Le contenu de ce fichier IDL est maintenant compréhensible par un lecteur qui a étudié scrupuleusement le chapitre 10 :

Listing 168 The IDL Counter File // IDL

  1. 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 deds quatre methodes non pas directement mais à travers cppuhelper. Ce fichier IDL est représenté en Figure 17

Modification du Compteur simple pour examiner l'enregistrement Encore une fois nous partons de l'exemple du compteur inclus dans le SDK 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 dans l'enregistrment travaille et particulièrement la nécessité de la règle 2 décrite dans la section 12.3.2. Les modifications consistent seulement à ajouter des printf comme montré dans le listing ci-dessous :

Listing 169 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. 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 170 Petit OOoBasic programme pour voir notre compteur

REM ***** BASIC *****

Sub Main ocmpt = createUnoService("foo.Counter") XRay.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 method --- 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

--- 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 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 dle le rendre scriptable. Utilisation d'une aide pour construire le composant scriptable Nous pouvons trouver un document décrivant cet exemple (de Daniel Bölzle)  : http://udk.openoffice.org/cpp/man/component_tutorial.html Si vous voulez rendre votre composant scriptable il vous faut modifier la règle 3 précédente comme suit : Règle 3 (définitive) Votre composant doit fournir les interfaces : XInterface, XServiceInfo, et XTypeProvider. La dernière est absolument caractéristique d'un composant scriptable. La Figure 18 ci-après nous montre la version définitive d'un composant scriptable. Comme il sera montré plus loin,nous utilisons une aide ( helper) et cela signifie que nous n'aurons pas à implémenter nous-même toutes les interfaces : seule XServiceInfo est nécessaire : les deux autres sont implémentées automatiquement avec l'aide (helper). Que dire de notre makefile ? Nous ne sommes plus vraiment intéressé par appeler notre compteur par countermain.cxx mais plutôt par l'utilisantion de celui-ci dans OooBasicce qui signifie que nous devons enregistrer notre librairie dynamique. 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'utiliserun programme OOoBasic, ou 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 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 ded 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 !!!












Pour accéder au compteur en Basic, vous devez utiliser le même fichier IDL que dans le Listing 163 ; nous donnons maintenant le programme complet du compteur.

Listing 171 Programme complet du compteur // C++ // counter.cxx /**** old class MyCounterImpl : public XCountable , public XServiceInfo

      • old*/

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_nRefCount; 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) { 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 ); }

/******* OLD Reference< XInterface > SAL_CALL MyCounterImpl_create( const Reference< XMultiServiceFactory > & xMgr ) { printf("MyCounterImpl_create called\n"); return Reference< XCountable >( new MyCounterImpl( xMgr ) ); }

}

                • OLD*/

//***********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 [] = {

   //{
   //    create_MyService1Impl, getImplementationName_MyService1Impl,
   //    getSupportedServiceNames_MyService1Impl, ::cppu::createSingleComponentFactory,
   //    0, 0
   //},
   {
       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 );

} }

Le makefile est le même que celui des exemples précédents. Le programme OOoBasic pour tester cet exemple est maintenant :

Listing 172 Programme OOoBasic de test

REM ***** BASIC *****

Sub Main Dim oSimpleComponent oSimpleComponent = CreateUnoService( "my_module.MyService" ) oSimpleComponent.setCount(5) print oSimpleComponent.getCount() oSimpleComponent.increment() print oSimpleComponent.getCount() 'XRay.XRay oSimpleComponent End Sub

Une introspection avec Xray du nouveau service créé nous montre maintenant :

properties method --- 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 --- 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 ( )

Un compteur avec attribut Une petite variation sur le composant compteur. Je décide d'utiliser la possibilité des attributs dans les fichiers IDL. Gardez à l'esprit que dans ce cas on accède automatiquement à l'attribut mar 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 173 Notre nouveau fichier IDL pour le compteur //IDL

  1. include <com/sun/star/uno/XInterface.idl>
  2. include <com/sun/star/lang/XInitialization.idl>

module my_module {

interface XSomething : com::sun::star::uno::XInterface { // long getCount(); **************automaticaly generated // void setCount( [in] long nCount ); **************automaticaly generated

   [attribute] long Count;

long increment(); long decrement(); };

service MyService {

   interface XSomething;

}; };

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 174 REM ***** BASIC *****

Sub Main Dim oSimpleComponent oSimpleComponent = CreateUnoService( "my_module.MyService" ) oSimpleComponent.Count = 5 print oSimpleComponent.Count oSimpleComponent.increment() print oSimpleComponent.Count XRay.XRay oSimpleComponent End Sub

Une nouvelle introspection avec Xray du nouveau service créé nous montre maintenant : properties method Properties of object "my_module.MyService": SbxSTRING ImplementationName SbxARRAY SupportedServiceNames SbxARRAY Types SbxARRAY ImplementationId SbxSTRING Dbg_SupportedInterfaces SbxSTRING Dbg_Properties SbxSTRING Dbg_Methods Methods of object "my_module.MyService": SbxEMPTY queryInterface ( SbxOBJECT ) SbxLONG addFive ( SbxLONG ) SbxLONG addSix ( SbxLONG ) SbxSTRING getImplementationName ( void ) SbxBOOL supportsService ( SbxSTRING ) SbxARRAY getSupportedServiceNames ( void ) SbxVOID initialize ( SbxARRAY ) SbxARRAY getTypes ( void ) SbxARRAY getImplementationId ( void ) SbxOBJECT queryAdapter ( void )

Construire le compteur sans aide 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


Notre deuxième composant 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 175 Fichier IDL de départ // IDL

  1. include <com/sun/star/uno/XInterface.idl>
  2. 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;

}; }; La Figure 19 décrit notre premier composant.


Le programme Basic suivant nous montre comment accéder à ce composant (ses services et interfaces). Listing 176 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.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 177 Notre fichier IDL de départ // IDL

  1. include <com/sun/star/uno/XInterface.idl>
  2. 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 178 // service_impl.cxx

  1. include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
  2. include <cppuhelper/factory.hxx>
  3. include <cppuhelper/implementationentry.hxx>
  1. include <com/sun/star/lang/XServiceInfo.hpp>
  2. include <com/sun/star/lang/XInitialization.hpp>
  3. include <com/sun/star/lang/IllegalArgumentException.hpp>
  4. 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 179 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.XRay oSimpleComponent End Sub

D'où viennent les extern “C” présents dans le code source ?

Peut-être retirer cette section déjà évoquée par ailleurs.

Nous avons en fait à implanter l'interface XserviceInfo quand on crée un composant. Cette interface est décrite comme d'habitude à l'aide du fichier IDL correspondant :

Listing 180 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_”.

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" ) demo1 = 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.

Personal tools