FR/Documentation/Construire des composants

From Apache OpenOffice Wiki
Jump to: navigation, search

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

(voir le oooforume)

Clarifions (et peut-être inventons) un peu de terminologie.

  1. 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.
  2. 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...
  3. 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.

Chaîne de compilation d'un composant

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 :

  1. Créer les fichiers idl et cpp. La Figure ci-dessus 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 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.

Registery.png

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.

ComponentInterface.png

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.

Chaîne de compilation du compteur

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.

Service et Interface du compteur

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".

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 :

Un composant scriptable 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 :

  1. 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,
  2. 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.

Documentation caution.png 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.

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              (  )                             
Documentation note.png 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 
Documentation note.png 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)

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

Documentation caution.png 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

Documentation windows.png 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 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.

Documentation note.png 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++.

Documentation windows.png 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

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

Documentation caution.png

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

Personal tools