Difference between revisions of "FR/Documentation/Construire des Addins"

From Apache OpenOffice Wiki
Jump to: navigation, search
m (Quatrième étape)
 
(11 intermediate revisions by 2 users not shown)
Line 260: Line 260:
 
};
 
};
 
</source>
 
</source>
{{Documentation/Tip|Si nous donnons les prototypes C++ des méthodes dans la même section que les méthode en écriture IDL c'est pour que vous preniez le temps de bien regarder la correspondance (présence des "const" et "&" dans les paramètres). Un oubli de ce genre et le message d'erreur se reportera sur l'appel du constructeur en vous disant que la méthode correspondante est abstraite. C'est pas souvent que je donne ce genre de conseil, mais faites volontairement une erreur pour voir le message d'erreur en question.}}
+
{{Tip|Si nous donnons les prototypes C++ des méthodes dans la même section que les méthode en écriture IDL c'est pour que vous preniez le temps de bien regarder la correspondance (présence des "const" et "&" dans les paramètres). Un oubli de ce genre et le message d'erreur se reportera sur l'appel du constructeur en vous disant que la méthode correspondante est abstraite. C'est pas souvent que je donne ce genre de conseil, mais faites volontairement une erreur pour voir le message d'erreur en question.}}
  
 
==La classe C++==
 
==La classe C++==
Line 494: Line 494:
  
 
L'implémentation de cette interface <idl>com.sun.star.lang.XServiceInfo</idl> n'est pas obligatoire. Vous devez l'implanter si vous voulez que votre add-in soit aussi "scriptable" : appelable depuis OOoBasic par exemple.  
 
L'implémentation de cette interface <idl>com.sun.star.lang.XServiceInfo</idl> n'est pas obligatoire. Vous devez l'implanter si vous voulez que votre add-in soit aussi "scriptable" : appelable depuis OOoBasic par exemple.  
{{Documentation/Note|En fait je n'ai pas réussi à supprimer cette interface d'un add-in, peut être parce que j'utilise un "helper". A fouiller un peu plus en détail. De toute façon, j'ai pris le parti dans ce document, de toujours montrer des add-ins scriptables donc supportant cette interface.}}
+
{{Note|En fait je n'ai pas réussi à supprimer cette interface d'un add-in, peut être parce que j'utilise un "helper". A fouiller un peu plus en détail. De toute façon, j'ai pris le parti dans ce document, de toujours montrer des add-ins scriptables donc supportant cette interface.}}
 
Voila donc le fichier IDL correspondant :
 
Voila donc le fichier IDL correspondant :
 
<source lang="idl">
 
<source lang="idl">
Line 563: Line 563:
 
}
 
}
 
</source>
 
</source>
{{Documentation/Note|On est obligé d'implanter le code correspondant à cette interface car l'interface XAddin hérite de XLocalizable.}}
+
{{Note|On est obligé d'implanter le code correspondant à cette interface car l'interface XAddin hérite de XLocalizable.}}
  
 
==L'interface XInitialization==
 
==L'interface XInitialization==
Line 590: Line 590:
 
}
 
}
 
</source>
 
</source>
{{Documentation/Note|On se passera de cette interface dans ce chapitre. Si vous voulez voir comment elle fonctionne allez voir l'exemple "CppComponent" du SDK.}}
+
{{Note|On se passera de cette interface dans ce chapitre. Si vous voulez voir comment elle fonctionne allez voir l'exemple "CppComponent" du SDK.}}
  
 
== Le code complet==
 
== Le code complet==
Line 1,046: Line 1,046:
  
 
Notez aussi qu'au chapitre 3 nous avons présenté des conversions un peu plus abstraites en utilisant les templates. Il me semble que ce type de conversion est intégré dans la nouvelle version du projet maintenant.
 
Notez aussi qu'au chapitre 3 nous avons présenté des conversions un peu plus abstraites en utilisant les templates. Il me semble que ce type de conversion est intégré dans la nouvelle version du projet maintenant.
 +
 +
==Gérer simultanément des Séquences de Séquences et des Séquences simples==
 +
Vous avez certainement déjà rencontré des fonctions de OOoCalc capables de travailler sur des groupes de cellules ou sur une liste de valeurs (ou de cellules). La fonction addition en est un exemple. Le problème est que le groupe de cellule est une séquence de séquence tandis que la liste est une séquence (simple) de valeurs et la question est : comment gérer une telle situation ?
 +
 +
Christophe Devalland s'est trouvé face à ce problème quand il s'est intéressé à l'interfaçage de la librairie [http://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/casref_en/casref_en.html XCas/xgiac]  (Voir aussi [[FR/Documentation/Construire_des_Addins#Notes|la note ci-après]]) et a résolu le problème avec l'aide de Niklas Nebel de cette manière :
 +
<source lang="cpp">
 +
Any CASImpl::cadd(const Sequence< Any > &aValList )
 +
    throw (RuntimeException)
 +
{    sal_Int32 n1;
 +
    sal_Int32 nE1 = aValList.getLength();
 +
    Sequence< Sequence< Any > > aArraySeq;
 +
    gen e;
 +
    giac::context * contextptr=0;
 +
    e=0;
 +
    for( n1 = 0 ; n1 < nE1 ; n1++ )
 +
    {
 +
        try
 +
        {
 +
            if ( aValList[ n1 ] >>= aArraySeq )
 +
            {
 +
                // traiter la zone de cellules contenue dans aArraySeq
 +
                //
 +
                Somme_Array(e,aArraySeq);
 +
            }
 +
            else
 +
            {
 +
                // aValList[n1] ne contient qu'une valeur
 +
                e=e+Cellule2Gen(aValList[ n1 ]);
 +
            }
 +
        }
 +
        catch (std::runtime_error & erreur)
 +
        {
 +
            return makeAny(OUString::createFromAscii("Erreur : ")+OUString::createFromAscii(erreur.what()));
 +
        }
 +
    }
 +
    e=giac::simplify(e,contextptr);
 +
    return Gen2any(e);
 +
}
 +
void Somme_Array(gen & e, Sequence< Sequence< Any > > & aArraySeq)
 +
{
 +
    // somme le contenu de la zone de cellules désignée par aArraySeq
 +
    sal_Int32        n1, n2;
 +
    sal_Int32        nE1 = aArraySeq.getLength();
 +
    sal_Int32        nE2;
 +
    for( n1 = 0 ; n1 < nE1 ; n1++ )
 +
    {
 +
        const Sequence< Any >    rList = aArraySeq[ n1 ];
 +
        nE2 = rList.getLength();
 +
        const Any*    pList = rList.getConstArray();
 +
        for( n2 = 0 ; n2 < nE2 ; n2++ )
 +
        {
 +
            // version modifiée de Cellule2Gen pour gérer les cellules vides
 +
            OUString type_name=pList[n2].getValueTypeName();
 +
            if (type_name==OUString::createFromAscii("double"))
 +
            {
 +
                double valeur;
 +
                sal_Int32 partie_entiere;
 +
                pList[n2] >>= valeur;
 +
                partie_entiere=(sal_Int32)(valeur);
 +
                if (partie_entiere==valeur)
 +
                {
 +
                    // retrouver le type int perdu par OOo
 +
                    e+=(int)partie_entiere;     
 +
                }
 +
                else
 +
                {
 +
                    // non entier
 +
                    // chaine=OUString::valueOf(valeur);
 +
                    e+=valeur;
 +
                }
 +
            }
 +
            else
 +
            { 
 +
                if (type_name==OUString::createFromAscii("string"))
 +
                {
 +
                    OUString chaine;
 +
                    pList[n2] >>= chaine;
 +
                    OString aOString;
 +
                    if (chaine.getLength()!=0)
 +
                    {
 +
                        aOString = OUStringToOString (chaine,RTL_TEXTENCODING_UTF8);
 +
                        gen temp(string(aOString),0);
 +
                        e+=temp;
 +
                    }
 +
                    else
 +
                    {
 +
                    // ne rien faire, la cellule est vide
 +
                    // cela peut se produire quand on somme une zone de cellules dont certaines seront amenées
 +
                    // à se remplir plus tard.
 +
                    // aOString = OUStringToOString (OUString::createFromAscii("nulle"),RTL_TEXTENCODING_UTF8);
 +
                    }
 +
                }
 +
            }
 +
        }
 +
    }
 +
}
 +
</source>
 +
Ceci représente beaucoup de code mais concentrez-vous sur :
 +
<source lang="cpp">
 +
try
 +
        {
 +
            if ( aValList[ n1 ] >>= aArraySeq )
 +
            {
 +
                // traiter la zone de cellules contenue dans aArraySeq
 +
                //
 +
                Somme_Array(e,aArraySeq);
 +
            }
 +
            else
 +
            {
 +
                // aValList[n1] ne contient qu'une valeur
 +
                e=e+Cellule2Gen(aValList[ n1 ]);
 +
            }
 +
        }
 +
</source>
 +
et toutes ses variables associées : c'est là qu'est traité le problème. Bien sûr le problème que vous avez à résoudre est un peu différent, il n'empêche que tout est là.
  
 
=Interfacer la librairie GSL (GNU Scientific Library)=
 
=Interfacer la librairie GSL (GNU Scientific Library)=
Line 1,174: Line 1,289:
  
 
où vous voyez en haut les coefficients sur la première ligne. Ils signifie que le polynôme qui nous intéresse est  -1+x^5. Les cinq racines se retrouvent ensuite en dessous (avec la partie réelle à gauche et la partie imaginaire à droite).
 
où vous voyez en haut les coefficients sur la première ligne. Ils signifie que le polynôme qui nous intéresse est  -1+x^5. Les cinq racines se retrouvent ensuite en dessous (avec la partie réelle à gauche et la partie imaginaire à droite).
 
+
=Notes=
{{Documentation/Caution|Si vous installez un addin externe sous linux et rencontrez un problème, vérifiez les dépendances de votre librairie dynamique avec la commande ldd : <code>ldd votre_addin.so</code> pour voir si par hasard votre problème n'est pas dû à une librairie manquante.}}
+
{{Warn|Si vous installez un addin externe sous linux et rencontrez un problème, vérifiez les dépendances de votre librairie dynamique avec la commande ldd : <code>ldd votre_addin.so</code> pour voir si par hasard votre problème n'est pas dû à une librairie manquante.}}
{{Documentation/Note|Au lieu d'aller plus an avant pour interfacer la librairie GSL prenez en considération le fait qu'un projet avancé d'interfaçage de la librairie giac est en cours (giac est un Computer Algebra System utilisé dans [http://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/casref_en/casref_en.html XCas]). Voici la [http://cdeval.free.fr/test/Guide%20d%27utilisation%20de%20CmathOOoCAS.odt documentation en français] de l'addin correspondant.}}
+
{{Note|Au lieu d'aller plus en avant pour interfacer la librairie GSL prenez en considération le fait qu'un projet avancé d'interfaçage de la librairie giac existe (giac est un Computer Algebra System utilisé dans [http://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/casref_en/casref_en.html XCas]). Voici la [http://cdeval.free.fr/spip.php?article132 documentation en français] de l'addin correspondant ainsi que l'addin proprement dit [http://cdeval.free.fr/CmathOOoUpdate/CmathOOoCAS.oxt en version 1.0] pour Linux ou M$ Windows (même fichier pour les deux versions).}}
  
 
= Retour à la page d'accueil=
 
= Retour à la page d'accueil=

Latest revision as of 21:13, 14 July 2018

Notre propos est d'étendre OpenOffice.org Calc avec du code C++. De telles extensions sont appelées Add-ins, comme on a déjà eu l'occasion de le souligner, et sont simples à implanter. Encore une fois les aides (helpers) seront de la partie et nous aiderons à simplifier le code.

Introduction

Partons de la documentation officielle Voir : http://api.openoffice.org/docs/common/ref/com/sun/star/sheet/AddIn.html et http://sc.openoffice.org/addin_howto.html Chaque fonction d'un AddIn peut prendre des paramètres ayant les types suivants :

  • long pour les valeurs entières.
  • double pour les valeurs réelles.
  • string pour les chaînes de caractères.
  • long[][] pour les tableaux deux dimensions d'entiers.
  • double[][] pour es tableaux deux dimensions de réels.
  • string[][] pour les tableaux deux dimensions de chaînes de caractères.
  • any[][] pour les tableaux deux dimensions de contenus divers et non définis. Chaque any contiendra un double ou *une chaîne de caractères (string), dependant des données.
  • any

Chaque fonction AddIn doit retourner elle aussi des types définis dans la liste suivante :

  • long
  • double
  • string
  • long[][]
  • double[][]
  • string[][]
  • any[][]
  • XVolatileResult
  • any

Les services et interfaces nécessaires

Ce que nous avons à faire et encore une fois défini par des services et interfaces. Ce type d'investigation a déjà souvent été rencontré dans ce document : naviguer dans les fichiers IDL pour glaner le plus d'informations possibles. Les interfaces que nous aurons à examiner sont trouvés à partir du fichier IDL Addin.idl documentant le service com.sun.star.sheet.AddIn :

//Listing 1 Service AddIn (fichier IDL)
// IDL
module com {  module sun {  module star {  module sheet {
service AddIn
{
	interface com::sun::star::lang::XServiceName;
	interface com::sun::star::sheet::XAddIn;
	[optional] interface com::sun::star::sheet::XCompatibilityNames;
};
}; }; }; };

On peut voir les trois interfaces (deux ne sont pas optionnelles) sur lesquelles nous allons enquêter. Commençons par regarder la première  :

//Listing 2 Interface XServiceName (fichier IDL)
// IDL
module com {  module sun {  module star {  module lang {
interface XServiceName: com::sun::star::uno::XInterface
{ 
	string getServiceName();  
}; 
}; }; }; };

Voir aussi la documentation complète de l'interface com.sun.star.sheet.XServiceName.

L'implantation C++ correspondante devrait être (basée sur les sources d'OpenOffice <OOo_1.1.3_src>/sch/source/addin/sampleaddin.cxx) :

//Listing 3 Implémentation de l'interface XServiceName in C++
// C++
// XServiceName
OUString SAL_CALL SampleAddIn::getServiceName() throw( uno::RuntimeException )
{
	return OUString::createFromAscii( "com.sun.star.chart.SampleAddIn" );
}

où nous avons simplement changé le nom “com.sun.star.chart.SampleAddIn“ avec notre propre nom de service. L'interface com.sun.star.sheet.XAddin est probablement la plus importante et donnée maintenant  :

//Listing 4 Interface XAddin
// IDL
module com {  module sun {  module star {  module sheet {
interface XAddIn: com::sun::star::lang::XLocalizable
{
	string getProgrammaticFuntionName( [in] string aDisplayName );
	string getDisplayFunctionName( [in] string aProgrammaticName );
	string getFunctionDescription( [in] string aProgrammaticName );
	string getDisplayArgumentName(
			[in] string aProgrammaticFunctionName,
			[in] long nArgument );
	string getArgumentDescription(
			[in] string aProgrammaticFunctionName,
			[in] long nArgument );
	string getProgrammaticCategoryName( [in] string aProgrammaticFunctionName );
	string getDisplayCategoryName( [in] string aProgrammaticFunctionName );
 
};
}; }; }; };

Une implémentation possible de ces méthodes pourrait être  :

//Listing 5 C++ Implementation of XAddin Interface 
// C++
// XAddIn
 
OUString SAL_CALL ScaDateAddIn::getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException )
{
    //  not used by calc
    //  (but should be implemented for other uses of the AddIn service)
    return OUString();
}
 
/*** This method returns the name for a given function, which should be displayed for the user, e.g. in a formula. 
***/
OUString SAL_CALL ScaDateAddIn::getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet;
	.....
    return aRet;
}
 
/*** The strings which this method returns are displayed in the AutoPilot for functions to give a short description for the entire function.
***/
OUString SAL_CALL ScaDateAddIn::getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet;
	....
    return aRet;
}
 
/*** For each parameter you will find a name which occurs in the parameter list. These strings are requested parameter wise.
One special parameter should be mentioned here: The "constREFXPS&" ("com::sun::star::beans::XPropertySet" in the IDL file) in some functions is not visible in the UI. It's a "hidden" parameter for which no request is generated, but it must be considered in the count of parameters, when informations for the other parameters are requested.
***/
 
OUString SAL_CALL ScaDateAddIn::getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{
    OUString aRet;
	....
    return aRet;
}
 
/*** Unlike the previous method, a description of a parameter is returned, which is also used in the AutoPilot for functions. 
***/
OUString SAL_CALL ScaDateAddIn::getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{
    OUString aRet;
	....
    return aRet;
}
 
/*** Each function in OpenOffice.org Calc can be assigned to a category. This is also used in AutoPilot for functions to make the listing of functions clearer. So this method returns the internal name for the category, which the function should be assigned to.
***/
OUString SAL_CALL ScaDateAddIn::getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet;
    ....
    return aRet;
}
 
/*** Same as getProgrammaticCategoryName but this name of the category will be displayed in the UI. Of course this makes only sense, if it's a user defined one. For the build in categories the names of course are fix. In the current implementation no language dependent strings are returned. This should be done, when Calc gets support for user defined categories.
***/
OUString SAL_CALL ScaDateAddIn::getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    return getProgrammaticCategoryName( aProgrammaticName );
}

La dernière interface com.sun.star.sheet.XCompatibilityNames est optionnelle :

// Listing 6 XCompatibilityNames Interface (IDL File)
// IDL
module com {  module sun {  module star {  module sheet {
interface XCompatibilityNames: com::sun::star::uno::XInterface
{
	sequence< com::sun::star::sheet::LocalizedName >
		getCompatibilityNames( [in] string aProgrammaticName );
};
}; }; }; };

qui pourrait être implémentée (basée sur les sources OpenOffice.org1.1) : OOo_1.1.3_src/scaddins/source/datefunc/datefunc.cxx

//Listing 7  Implémentation en C++ de l'interface XCompatibilityNames
// C++
// XCompatibilityNames
 
/*** This method is very important in the context with the im- and export of Excel files. For each function a list is returned, where every element holds a name and a accompanying language. With this it is possible to parse a number of language variants of the add-in for the import. One remark: Excel is only able to load such add-in functions, which are in the same language as the installed add-in. In this case, OpenOffice.org Calc is a little bit more advanced than Excel... :-) In the opposite direction, when exporting to Excel, the installed OpenOffice.org language is chosen to select a name.
The main goal for the implementation of the Analysis add-in was to get a better compatibility to Excel. For this reason the "CompatibilityNames" are chosen in a way, that they are identically to those which are uses by Excel. These don't need to match the "DisplayFunctionNames", but in most cases they do. Exceptions are the cases, where the Excel namings colliding with already build in functions of Calc. In this cases, a simple "_ADD" is appended to the name.
***/
uno::Sequence< sheet::LocalizedName > SAL_CALL ScaDateAddIn::getCompatibilityNames(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    ....
 
    uno::Sequence< sheet::LocalizedName > aRet( nCount );
    sheet::LocalizedName* pArray = aRet.getArray();
 
    ....
 
    return aRet;
}

Pour nos essais nous n'implanterons pas cette interface.

Vous ne pouvez pas écrire un addin sans les fameuses functions C déjà mentionnée dans le chapitre précédent sur les composants.

Mon premier Add-In

J'ai réalisé mon premier Add-in en :

  • partant de l'exemple du composant (voir le chapitre sur les composants).
  • ajoutant deux interfaces lang::XServiceName et XAddIn et les fichiers correspondants dans le makefile, les fichiers IDL en ajoutant les directives d'inclusion correspondantes dans le code C++
  • implementant ces interfaces avec un minimum de lignes de code. A noter que lorsque vous ajoutez l'interface Xaddin il vous faut implanter automatiquement l'interface XInternationalize sans l'ajouter dans l'aide (helper).

L'add-in fonctionne dès que le service com.sun.star.sheet.AddIn était disponible (voir sal_Bool firstAddinImpl::supportsService( OUString const & serviceName) dans le Listing 1).

Compilation et installation des exemples

On choisit encore de partir d'un exemple du SDK concernant un composant (a noter qu'il n'y a pas d'exemple de addin C++ dans le SDK) : <OpenOffice.org_SDK>/examples/DevelopersGuide/Components/CppComponent

Je vous conseille de faire un répertoire <OpenOffice.org_SDK>/examples/DevelopersGuide/Components/Addin et d'y copier le contenu de CppComponent et de lire comment faire fonctionner nos exemples. Il faudra y adapter les fichiers parceque mon nom de service est maintenant "foo.FirstAddin" avec l'interface foo.XFirstAddin (mettre donc à jour "CppComponent.uno.xml"). Il faudra construire aussi quelques fichiers hpp que l'on n'a pas encore utilisés (encore mettre à jour "CppComponent.uno.xml") et pour savoir lesquels, il suffit de regarder la partie inclusion du code source. A adapter aussi le makefile à vos noms de fichier : les miens s'apellent FirstAddin.idl et firstAddin_impl.cxx

Commençons par donner notre cahier des charges sous la forme de fichier IDL.

Le fichier IDL

Voici notre fichier IDL de départ :

//Listing 8 My first Add-In IDL file
// IDL : FirstAddin.idl
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XServiceName.idl>
#include <com/sun/star/sheet/XAddIn.idl>
 
module foo
{
 
interface XFirstAddin : com::sun::star::uno::XInterface
{
	string methodOne( [in] string val );
	string methodTwo( [in] string val );
	long methodThree( [in] sequence< sequence< long > > aValList );
	sequence< sequence< long > > methodFour( [in] sequence< sequence< long > > aValList );
};
 
service FirstAddin
{
    interface XFirstAddin;
    interface com::sun::star::lang::XServiceName;
    interface com::sun::star::sheet::XAddIn;
};
 
};

On peut distinguer quatre méthodes methodOne, methodTwo, methodThree and methodFour. Les quatre méthodes peuvent être utilisées avec OOoCalc et aussi avec OOoBasic, ce dernier parce que mon code est tiré d'un exemple de composant.

Nous donnons le code le la classe automatiquement générée avec l'outil cppumaker.

class SAL_NO_VTABLE XFirstAddin : public ::com::sun::star::uno::XInterface
{
public:
 
    // Methods
    virtual ::rtl::OUString SAL_CALL methodOne( const ::rtl::OUString& val ) throw
                     (::com::sun::star::uno::RuntimeException) = 0; 
    virtual ::rtl::OUString SAL_CALL methodTwo( const ::rtl::OUString& val ) throw 
                     (::com::sun::star::uno::RuntimeException) = 0;
    virtual sal_Int32 SAL_CALL methodThree( const ::com::sun::star::uno::Sequence< 
                     ::com::sun::star::uno::Sequence< sal_Int32 > >& aValList ) throw
                           (::com::sun::star::uno::RuntimeException) = 0;
    virtual ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Sequence< 
                      sal_Int32 > > SAL_CALL methodFour( const ::com::sun::star::uno::Sequence<
                           ::com::sun::star::uno::Sequence< sal_Int32 > >& aValList ) throw 
                               (::com::sun::star::uno::RuntimeException) = 0;
};
Tip.png Si nous donnons les prototypes C++ des méthodes dans la même section que les méthode en écriture IDL c'est pour que vous preniez le temps de bien regarder la correspondance (présence des "const" et "&" dans les paramètres). Un oubli de ce genre et le message d'erreur se reportera sur l'appel du constructeur en vous disant que la méthode correspondante est abstraite. C'est pas souvent que je donne ce genre de conseil, mais faites volontairement une erreur pour voir le message d'erreur en question.


La classe C++

On utilise un helper pour implémenter notre classe et nous ne devons donc pas implémenter toutes les interfaces : pas besoin d'implémenter com.sun.star.uno.XInterface, com.sun.star.lang.XTypeProvider et com.sun.star.uno.XWeak par exemple. Avant de vous plonger dans le code C++ correspondant donné ci-dessous, vous pouvez aussi aller voir com.sun.star.lang.XServiceInfo, com.sun.star.lang.XServiceName, com.sun.star.lang.XLocalizable et com.sun.star.sheet.XAddIn. Rappelons de manière schématique ce que nous avons à faire quand on utilise un helper :

Notre premier Addin (avec Helper)

Il est temps de donner le code correspondant : vous voyez quatre interfaces (XLocalizable ne compte pas) sur la figure ci-dessus, alors vous utiliserez un "::cppu::WeakImplHelper4" : c'est la règle 4 décrite dans la création d'un composant.

//C++
class firstAddinImpl : public ::cppu::WeakImplHelper4<
      ::foo::XFirstAddin, lang::XServiceInfo, lang::XServiceName, XAddIn>
{
public:
    // XFirstAddin
    virtual OUString SAL_CALL methodOne( OUString const & str )
        throw (RuntimeException);
    virtual OUString SAL_CALL methodTwo( OUString const & str )
        throw (RuntimeException);
    virtual sal_Int32 SAL_CALL methodThree(const Sequence< Sequence< sal_Int32 > > &aValList )
        throw (RuntimeException);
    virtual Sequence< Sequence< sal_Int32 > > SAL_CALL methodFour(
				const Sequence< Sequence< sal_Int32 > > &aValList )
	throw (RuntimeException);
    // XServiceName
    virtual OUString SAL_CALL getServiceName() throw( uno::RuntimeException );
    // 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); 
    // XAddIn
    virtual OUString SAL_CALL getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    // XLocalizable
    virtual void SAL_CALL firstAddinImpl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException );
    virtual lang::Locale SAL_CALL firstAddinImpl::getLocale() throw( uno::RuntimeException );
};

Vous commencez à connaître la façon de trouver toutes les méthodes de votre classe : à l'aide d'une enquête minutieuse dans les fichiers IDL. Ce point a déjà largement été abordé dans ce document pour ne pas revenir dessus. Le nombre d'interfaces que vous avez à implémenter dépend de votre but final. Si vous voulez implémenter un composant il vous faudra implémenter l'interface XInterface ainsi que la votre, si vous voulez faire un composant visible d'un autre langage de programmation ("scriptable component") il faut ajouter les interfaces : XTypeprovider, XServiceInfo and XWeak) et si vous voulez créer un AddIn (scriptable) ajouter encore (XAddin, XServiceName). C'est expliqué dans le Developper's Guide.

Nous allons maintenant nous intéresser au code source de chacune des interfaces en commençant par la notre : XFirstAddin.

Implantation en C++ des fonctions membres

Nous donnons le code C++ des quatres fonctions membres présentées dans le fichier IDL.

Les deux premières fonctions membres

Les deux premières sont assez similaires :

// C++
OUString firstAddinImpl::methodOne( OUString const & str )
throw (RuntimeException)
{
  return OUString( RTL_CONSTASCII_USTRINGPARAM(
    "called methodOne() of foo.XFirstAddin: ") ) + str;
}
 
OUString firstAddinImpl::methodTwo( OUString const & str )throw (RuntimeException)
{
  return OUString( RTL_CONSTASCII_USTRINGPARAM(
    "called methodTwo() of foo.XFirstAddin: ") ) + str;
}

Elles prennent seulement une chaîne de caractères (d'une cellule OOoCalc) et ajoute un message en mettant le résultat total dans une cellule résultat.

La troisième fonction membre

La troisième fonction membre est un peu plus compliqué : elle retourne une valeur calculée à partir d'une zone de cellules (la somme en l'occurence).

// C++
sal_Int32 firstAddinImpl::methodThree(const Sequence< Sequence< sal_Int32 > > &aValList )
throw (RuntimeException)
{ sal_Int32 n1, n2;
  sal_Int32 nE1 = aValList.getLength();
  sal_Int32 nE2;
  sal_Int32 temp=0;
  for( n1 = 0 ; n1 < nE1 ; n1++ )
  {
    const Sequence< sal_Int32 > rList = aValList[ n1 ];
    nE2 = rList.getLength();
    const sal_Int32* pList = rList.getConstArray();
    for( n2 = 0 ; n2 < nE2 ; n2++ )
    {
      temp += pList[ n2 ];
    }
  }
  return temp;
}

La quatrième fonction membre

Le but de la quatrième fonction membre est de montrer comment on peut implanter une fonction matrice : partir d'une zone de cellules et en obtenir une autre.

//C++
//It's a matrix operation should be called like : {=METHODFOUR(A1:B4)}
Sequence< Sequence< sal_Int32 > > firstAddinImpl::methodFour(const Sequence< Sequence< sal_Int32 > > &aValList )
throw (RuntimeException)
{ sal_Int32 n1, n2;
  sal_Int32 nE1 = aValList.getLength();
  sal_Int32 nE2;
  Sequence< Sequence< sal_Int32 > > temp = aValList;
  for( n1 = 0 ; n1 < nE1 ; n1++ )
  {
    Sequence< sal_Int32 > rList = temp[ n1 ];
    nE2 = rList.getLength();
    for( n2 = 0 ; n2 < nE2 ; n2++ )
    {
      rList[ n2 ] += 4;
    }
    temp[n1]=rList;
  }
  return temp;
}

Ce qui peut être fait avec cet exemple n'est pas grandiose : seulement ajouter 4 à toutes les cellules de la zone de cellules et mettre le résultat dans une zone de cellules.

Nous allons maintenant explorer les interfaces à implanter une par une. On commence par l'interface typique : XAddin.

L'interface XAddIn

Avant de comprendre comment les choses marchent, demandons-nous ce que sont les paramètres OUString& aProgrammaticName que l'on trouve dans toutes les méthodes de l'interface com.sun.star.sheet.XAddin (voir aussi Listing 4). C'est seulement le nom des méthodes données dans le fichier IDL : par exemple le sous-programme getDisplayFunctionName( const OUString& aProgrammaticName ) sera appelé par l'extérieur avec comme valeur soit « methodOne » ou soit « methodTwo ». Comme programmeur vous devez répondre à tous les appels et donc tester les valeurs de cet argument.

Le code C++ correspondant est donné sans explication pour le moment

// C++
// XAddIn
OUString SAL_CALL firstAddinImpl::getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException )
{
    //  not used by calc
    //  (but should be implemented for other uses of the AddIn service)
    if (aDisplayName.equalsAscii("method1")) return OUString::createFromAscii("methodOne");
    if (aDisplayName.equalsAscii("method2")) return OUString::createFromAscii("methodTwo");
    if (aDisplayName.equalsAscii("method3")) return OUString::createFromAscii("methodThree");
    if (aDisplayName.equalsAscii("method4")) return OUString::createFromAscii("methodFour");
}
 
OUString SAL_CALL firstAddinImpl::getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    if (aProgrammaticName.equalsAscii("methodOne")) return OUString::createFromAscii("method1");
    if (aProgrammaticName.equalsAscii("methodTwo")) return OUString::createFromAscii("method2");
    if (aProgrammaticName.equalsAscii("methodThree")) return OUString::createFromAscii("method3");
    if (aProgrammaticName.equalsAscii("methodFour")) return OUString::createFromAscii("method4");
}
 
OUString SAL_CALL firstAddinImpl::getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    if (aProgrammaticName.equalsAscii("methodOne")) 
	return OUString::createFromAscii("methodOne() : 1st try");
    if (aProgrammaticName.equalsAscii("methodTwo")) 
	return OUString::createFromAscii("methodTwo() : 1st try");
    if (aProgrammaticName.equalsAscii("methodThree")) 
	return OUString::createFromAscii("methodThree() : 1st try");
    if (aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("methodFour() : 1st try");
}
 
OUString SAL_CALL firstAddinImpl::getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{   
    if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) 
	return OUString::createFromAscii("a string");
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("a Cell Range");
}
 
OUString SAL_CALL firstAddinImpl::getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{   if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) {
	return OUString::createFromAscii("method1/2:a string or a cell with a string is required");
    }
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("method3/4:a cell range is required");
}
 
OUString SAL_CALL firstAddinImpl::getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet( RTL_CONSTASCII_USTRINGPARAM("Add-In"));
    return aRet;
}
 
OUString SAL_CALL firstAddinImpl::getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    return getProgrammaticCategoryName( aProgrammaticName );
}

Pour comprendre à quoi est relié ce code nous vous proposons la copie d'écran suivante, regardez-la en détail pour voir les parties du codes qui sont concernées par l'autopilot :

Addin2.png

J'y ai utilisé methodOne comme vous pouvez le voir dans l'autopilot, et je suis prêt à utiliser la méthode deux. Ce qui apparaît dans l'autopilot vient directement du code C++ excepté le mot français "requis" qui est probablement traduit par "required" dans une version anglo-saxonne d'OpenOffice.

A propos de ce mot "requis", Christophe Devalland me fait savoir qu'il est transformé automatiquement en "optionnel" dès que l'argument est de type Any.

L'interface XServiceName

Commençons encore et toujours par le fichier IDL de l'interface com.sun.star.lang.XServiceName :

// IDL
module com { module sun { module star { module lang {
interface XServiceName: com::sun::star::uno::XInterface
{
  string getServiceName();
};
}; }; }; };

Cette interface est simple : une seule méthode :

//C++
// XServiceName
OUString SAL_CALL firstAddinImpl::getServiceName() throw( uno::RuntimeException )
{
    // name of specific AddIn service
    return OUString::createFromAscii( "foo.FirstAddin" );
}

L'interface XServiceInfo

L'implémentation de cette interface com.sun.star.lang.XServiceInfo n'est pas obligatoire. Vous devez l'implanter si vous voulez que votre add-in soit aussi "scriptable" : appelable depuis OOoBasic par exemple.

Documentation note.png En fait je n'ai pas réussi à supprimer cette interface d'un add-in, peut être parce que j'utilise un "helper". A fouiller un peu plus en détail. De toute façon, j'ai pris le parti dans ce document, de toujours montrer des add-ins scriptables donc supportant cette interface.

Voila donc le fichier IDL correspondant :

// 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();
};
}; }; }; };

Et voila le code C++ implémentant l'interface com.sun.star.lang.XServiceInfo :

// C++
// XServiceInfo implementation
OUString firstAddinImpl::getImplementationName()
    throw (RuntimeException)
{
    // unique implementation name
    return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
sal_Bool firstAddinImpl::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    // this object only supports one service, so the test is simple
    // modified *********
    if (serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("foo.FirstAddin") ))
      return sal_True;
    if (serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("com.sun.star.sheet.AddIn") ))
      return sal_True;
    else return sal_False;
}
Sequence< OUString > firstAddinImpl::getSupportedServiceNames()
    throw (RuntimeException)
{
	return getSupportedServiceNames_firstAddinImpl();
}
 
Reference< XInterface > SAL_CALL create_firstAddinImpl(
    Reference< XComponentContext > const & xContext )
    SAL_THROW( () )
{
    return static_cast< lang::XTypeProvider * >( new firstAddinImpl() );
}

Notez que le constructeur de notre classe est appelé sans paramètre.

L'interface XLocalizable

Cette interface com.sun.star.lang.XLocalizable est particulière : on l'a laissée dans la définition de la classe et on vous donne maintenant son code, mais puisque son code ne fait rien en fait, on ne l'ajoute pas comme cinquième interface quand on utilise l'aide (helper).

// C++
// XLocalizable 
void SAL_CALL firstAddinImpl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException )
{
//    aFuncLoc = eLocale;
//    InitData();     // change of locale invalidates resources!
}
 
lang::Locale SAL_CALL firstAddinImpl::getLocale() throw( uno::RuntimeException )
{
//    return aFuncLoc;
}
Documentation note.png On est obligé d'implanter le code correspondant à cette interface car l'interface XAddin hérite de XLocalizable.

L'interface XInitialization

L'interface com.sun.star.lang.XInitialization peut être retirée complètement. Je l'ai retiré du code complet. Voila quand même du code C++ qui pourrait être utilisé.

// C++
// XInitialization implemention
void firstAddinImpl::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
    }
}
Documentation note.png On se passera de cette interface dans ce chapitre. Si vous voulez voir comment elle fonctionne allez voir l'exemple "CppComponent" du SDK.

Le code complet

Il est maintenant temps de donner le code C++ complet :

//Listing 9 My first Add-In
//C++
/***************************************************************************************************
 ***************************************************************************************************
 *
 * service implementation:	 foo.FirstAddin
 * exported interfaces:		 foo.XFirstAddin
 *
 * simple example component implementing a counter
 *
 ***************************************************************************************************
 **************************************************************************************************/
#include <cppuhelper/implbase4.hxx> // "4" implementing four 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/XServiceName.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/sheet/XAddIn.hpp>
#include <com/sun/star/lang/XLocalizable.hpp>
#include <foo/XFirstAddin.hpp>
 
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for odk interfaces
using namespace ::com::sun::star::uno; // for basic types
using namespace ::com::sun::star::sheet;
 
 
namespace my_sc_impl
{  
static Sequence< OUString > getSupportedServiceNames_firstAddinImpl()
{
	static Sequence < OUString > *pNames = 0;
	if( ! pNames )
	{
//		MutexGuard guard( Mutex::getGlobalMutex() );
		if( !pNames )
		{
			static Sequence< OUString > seqNames(2);
			seqNames.getArray()[0] = OUString(RTL_CONSTASCII_USTRINGPARAM("foo.FirstAddin"));
                        seqNames.getArray()[1] = OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.sheet.AddIn"));
			pNames = &seqNames;
		}
	}
	return *pNames;
}
 
static OUString getImplementationName_firstAddinImpl()
{
	static OUString *pImplName = 0;
	if( ! pImplName )
	{
//		MutexGuard guard( Mutex::getGlobalMutex() );
		if( ! pImplName )
		{
			static OUString implName( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_implementation.MyService2") );
			pImplName = &implName;
		}
	}
	return *pImplName;
}
 
class firstAddinImpl : public ::cppu::WeakImplHelper4<
      ::foo::XFirstAddin, lang::XServiceInfo, lang::XServiceName, XAddIn>
{
public:
    // XFirstAddin
    virtual OUString SAL_CALL methodOne( OUString const & str )
        throw (RuntimeException);
    virtual OUString SAL_CALL methodTwo( OUString const & str )
        throw (RuntimeException);
    virtual sal_Int32 SAL_CALL methodThree(const Sequence< Sequence< sal_Int32 > > &aValList )
        throw (RuntimeException);
    virtual Sequence< Sequence< sal_Int32 > > SAL_CALL methodFour(
				const Sequence< Sequence< sal_Int32 > > &aValList )
	throw (RuntimeException);
    // XServiceName
    virtual OUString SAL_CALL getServiceName() throw( uno::RuntimeException );
    // 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); 
    // XAddIn
    virtual OUString SAL_CALL getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    virtual OUString SAL_CALL getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException );
    // XLocalizable
    virtual void SAL_CALL firstAddinImpl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException );
    virtual lang::Locale SAL_CALL firstAddinImpl::getLocale() throw( uno::RuntimeException );
};
 
// XServiceName
OUString SAL_CALL firstAddinImpl::getServiceName() throw( uno::RuntimeException )
{
    // name of specific AddIn service
    return OUString::createFromAscii( "foo.FirstAddin" );
}
// XSomething implementation
OUString firstAddinImpl::methodOne( OUString const & str )
    throw (RuntimeException)
{
    return OUString( RTL_CONSTASCII_USTRINGPARAM(
        "called methodOne() of foo.XFirstAddin: ") ) + str;
}
 
OUString firstAddinImpl::methodTwo( OUString const & str )
    throw (RuntimeException)
{
    return OUString( RTL_CONSTASCII_USTRINGPARAM(
        "called methodTwo() of foo.XFirstAddin: ") ) + str;
}
 
 
sal_Int32 firstAddinImpl::methodThree(const Sequence< Sequence< sal_Int32 > > &aValList )
    throw (RuntimeException)
{ 	sal_Int32		n1, n2;
	sal_Int32		nE1 = aValList.getLength();
	sal_Int32		nE2;
	sal_Int32 temp=0;
	for( n1 = 0 ; n1 < nE1 ; n1++ )
	{
		const Sequence< sal_Int32 >	rList = aValList[ n1 ];
		nE2 = rList.getLength();
		const sal_Int32*	pList = rList.getConstArray();
		for( n2 = 0 ; n2 < nE2 ; n2++ )
		{
			temp += pList[ n2 ];
		}
	}
	return temp;
}
 
//It's a matrix operation  should be called like : {=METHODFOUR(A1:B4)}
Sequence< Sequence< sal_Int32 > > firstAddinImpl::methodFour(
		const Sequence< Sequence< sal_Int32 > > &aValList )throw (RuntimeException)
{ 	sal_Int32		n1, n2;
	sal_Int32		nE1 = aValList.getLength();
	sal_Int32		nE2;
	Sequence< Sequence< sal_Int32 > > temp = aValList;
	for( n1 = 0 ; n1 < nE1 ; n1++ )
	{
		Sequence< sal_Int32 >	rList = temp[ n1 ];
		nE2 = rList.getLength();
		for( n2 = 0 ; n2 < nE2 ; n2++ )
		{
			rList[ n2 ] += 4;
		}
		temp[n1]=rList;
	}
	return temp;
}
 
// XAddIn
OUString SAL_CALL firstAddinImpl::getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException )
{
    //  not used by calc
    //  (but should be implemented for other uses of the AddIn service)
        if (aDisplayName.equalsAscii("method1")) return OUString::createFromAscii("methodOne");
    if (aDisplayName.equalsAscii("method2")) return OUString::createFromAscii("methodTwo");
    if (aDisplayName.equalsAscii("method3")) return OUString::createFromAscii("methodThree");
    if (aDisplayName.equalsAscii("method4")) return OUString::createFromAscii("methodFour");
}
 
OUString SAL_CALL firstAddinImpl::getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    if (aProgrammaticName.equalsAscii("methodOne")) return OUString::createFromAscii("method1");
    if (aProgrammaticName.equalsAscii("methodTwo")) return OUString::createFromAscii("method2");
    if (aProgrammaticName.equalsAscii("methodThree")) return OUString::createFromAscii("method3");
    if (aProgrammaticName.equalsAscii("methodFour")) return OUString::createFromAscii("method4");
}
 
OUString SAL_CALL firstAddinImpl::getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    if (aProgrammaticName.equalsAscii("methodOne")) 
	return OUString::createFromAscii("methodOne() : 1st try");
    if (aProgrammaticName.equalsAscii("methodTwo")) 
	return OUString::createFromAscii("methodTwo() : 1st try");
    if (aProgrammaticName.equalsAscii("methodThree")) 
	return OUString::createFromAscii("methodThree() : 1st try");
    if (aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("methodFour() : 1st try");
}
 
OUString SAL_CALL firstAddinImpl::getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{   
    if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) 
	return OUString::createFromAscii("a string");
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("a Cell Range");
}
 
OUString SAL_CALL firstAddinImpl::getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{   
    if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) 
	return OUString::createFromAscii("method1/2:a string or a cell with a string is required");
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	return OUString::createFromAscii("method3/4:a cell range is required");
}
 
OUString SAL_CALL firstAddinImpl::getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet( RTL_CONSTASCII_USTRINGPARAM("Add-In"));
    return aRet;
}
 
OUString SAL_CALL firstAddinImpl::getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    return getProgrammaticCategoryName( aProgrammaticName );
}
 
// XServiceInfo implementation
OUString firstAddinImpl::getImplementationName()
    throw (RuntimeException)
{
    // unique implementation name
    return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
sal_Bool firstAddinImpl::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    // this object only supports one service, so the test is simple
    // modified *********
    if (serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("foo.FirstAddin") ))
      return sal_True;
    if (serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("com.sun.star.sheet.AddIn") ))
      return sal_True;
    else return sal_False;
}
Sequence< OUString > firstAddinImpl::getSupportedServiceNames()
    throw (RuntimeException)
{
	return getSupportedServiceNames_firstAddinImpl();
}
 
Reference< XInterface > SAL_CALL create_firstAddinImpl(
    Reference< XComponentContext > const & xContext )
    SAL_THROW( () )
{
    return static_cast< lang::XTypeProvider * >( new firstAddinImpl() );
}
 
// XLocalizable 
 
void SAL_CALL firstAddinImpl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException )
{
//    aFuncLoc = eLocale;
//    InitData();     // change of locale invalidates resources!
}
 
lang::Locale SAL_CALL firstAddinImpl::getLocale() throw( uno::RuntimeException )
{
//    return aFuncLoc;
}
 
}
 
/* shared lib exports implemented without helpers in service_impl1.cxx */
namespace my_sc_impl
{
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
    {
        create_firstAddinImpl, getImplementationName_firstAddinImpl,
        getSupportedServiceNames_firstAddinImpl, ::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 );
}
}

Nous savons maintenant comment les choses fonctionnent et particulièrement comment gérer les séquences de séquences comme paramètre ou comme valeur retournée. Même si l'exemple ci-dessus est donné avec un type long, il est facile de le transposer avec un double.

Utilitaires

Dans le but de fournir des utilitaires pour interfacer Openoffice.org avec d'autres librairies externes, nous allons fournir dans cette section des utilitaires permettant de transformer les variables classiques en variables UNO.

Transformer une séquence de séquence en un tableau

Si vous utilisez une librairie externe, comme par exemple la librairie GSL (GNU Scientific Library) qui est une librairie écrite en C, vous vous apercevez qu'en général les données sont sous forme de tableaux de réels (en format double). Prenez le temps d'examiner le Listing 182 où vous voyez une méthode imaginaire appelée « foo ». Le point important est que l'on doit utiliser une séquence de séquence comme paramètre de la méthode alors que l'on dispose de tableaux à deux dimensions.

//Listing 10 Head of Method foo : Sequence of Sequence into Array
//C+
// ATTENTION ce n'est pas une fonction complète !!!!
Sequence< Sequence< double > > MyService2Impl::foo(
                      const Sequence< Sequence< double > > &aValList )
                                                     throw (RuntimeException)
{ 	sal_Int32		nE1 = aValList.getLength();
	sal_Int32		nE2;
 
	Sequence< double >	rList = aValList[ 0 ];
	nE2 = rList.getLength();
	double table[nE1][nE2];
	for( sal_Int32 n1 = 0 ; n1 < nE1; n1++ )
	{
		for (sal_Int32 n2=0;n2<nE2;n2++)
			table[n1][n2]=aValList[n1][n2];
	}
// we have a 2D array now
// !!!! En fait ce code n'est pas du bon C++ !!!!
// car la déclaration de la dimension du tableau
// n'est pas connue à la compilation

A la fin de ce programme vous avez un tableau à deux dimensions nommé « table » et nous montrons ainsi comment le construire. (J'ai essayé une autre méthode utilisant un getArray() mais sans succès)

Transformer un tableau en une séquence de séquence

Imaginons que votre calcul produise un tableau à une dimension nommé « z » et que vous désiriez construire une séquence de séquence avant de la retourner. Voici un chemin que vous pouvez suivre :

//Listing 11 Construire une séquence de séquence en partant d'un tableau « z »
//C+
...
// here a line is constructed of size (nE2-1)*2
	Sequence< double >	solList1D(z,(nE2-1)*2);
	Sequence< Sequence< double > > solList2D(1);
	solList2D[0]=solList1D;
	return solList2D;

Un problème similaire rencontré plus tard doit être résolu différemment  : je pars d'un tableau à une dimension, mais ce tableau contient une partie réelle et une partie imaginaire « lesThe n-1 rarines sont retournées dans un tableau de nombre complexes z de longueur 2(n-1), alternant les parties réelles et les parties imaginaires » Je veux montrer le résultat d'un tel calcul dans Calc mais avec deux colonnes (une pour la partie réelle, une pour la partie imaginaire) avec autant de lignes que nécessaire. Voici maintenant la façon d'y parvenir :

//Listing 12 Construire une séquence de séquence en partant d'un tableau « z »
//C+
...
// two rows and nE2-1 lines starting from an array "z" with (nE2-1)*2 lements
	Sequence< double >	solList1D(2);
		Sequence< Sequence< double > > solList2D(nE2-1);
		for( int j=0;j<((nE2-1)<<1);j+=2){ // (nE2-1)<<1 is faster than (nE2-1)*2
			for( int i=0; i<2 ; i++){
				solList1D[i]=z[i+j];
			}
			solList2D[j>>1]=solList1D;//j >> 1 is faster than j/2
		}
		return solList2D;

De temps en temps il nous faudra utiliser la STL (Standard Template Library) parce que la librairie que l'on décide d'interfacer est en C++. Eric Ehlers a donné un exemple en interfaçant une librairie C++ et a donc été obligé d'écrire quelques utilitaires et fournir ainsi du code intéressant.

Utilitaires d'Eric Ehlers

Le projet s'appelle QuantLibAddin - http://quantlib.org/quantlibaddin/ C'est une intégration de QuantLib, une librairie open source, à de nombreuses plates-formes dont calc. Les notes expliquant comment construire et installer QuantLibAddin pour Calc peuvent être trouvées ici : http://quantlib.org/quantlibaddin/calc.html

Sur lapage de téléchargement de QuantLib http://sourceforge.net/project/showfiles.php?group_id=12740 vous pouvez télécharger QuantLibAddin et regarder le code source de l' addin OOoCalc. Pour exécuter l'addin QuantLib vous devez construire QuantLib - http://quantlib.org, log4cxx - http://logging.apache.org/log4cxx/, ObjectHandler - http://quantlib.org/objecthandler et QuantLibAddin - http://quantlib.org/quantlibaddin/

Comme QuantLib est écrit en C++ avec la STL, des conversions de types doivent être fournies. Nous en présentons quelques unes maintenant :

//Listing 13 Uitilitaires de conversion d'Eric Ehlers
//C+
// from QuantLibAddin-0.3.10/Addins/Calc/calcutils.cpp
/*
 Copyright (C) 2004, 2005 Eric Ehlers
 
 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/
 
 QuantLib is free software: you can redistribute it and/or modify it under the
 terms of the QuantLib license.  You should have received a copy of the
 license along with this program; if not, please email quantlib-dev@lists.sf.net
 The license is also available online at http://quantlib.org/html/license.html
 
 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/
 
....
 
std::vector < double >SeqSeqToVectorDouble(const Sequence< Sequence < double > >& ss) {
    std::vector < double >v;
    for (int i=0; i<ss.getLength(); i++)
        for (int j=0; j<ss[i].getLength(); j++)
            v.push_back(ss[i][j]);
    return v;
}
 
std::vector < std::vector < double > >SeqSeqToMatrixDouble(
											const Sequence< Sequence < double > >& ss) {
    std::vector < std::vector < double > >vv;
    for (int i=0; i<ss.getLength(); i++) {
        std::vector < double >v;
        for (int j=0; j<ss[i].getLength(); j++)
            v.push_back(ss[i][j]);
        vv.push_back(v);
    }
    return vv;
}
 
Sequence< Sequence< double > > VectorDoubleToSeqSeq(const std::vector < double > &v) {
    Sequence< Sequence< double > > ss(v.size());
    for (unsigned int i=0; i<v.size(); i++) {
        Sequence< double > s(1);
        s[0] = v[i];
        ss[i] = s;
    }
    return ss;
}
 
Sequence< Sequence< double > > MatrixDoubleToSeqSeq(
									const std::vector < std::vector < double > >&vv) {
    Sequence< Sequence< double > > ss(vv.size());
    for (unsigned int i=0; i<vv.size(); i++) {
        std::vector < double > v = vv[i];
        Sequence< double > s(v.size());
        for (unsigned int j=0; j<v.size(); j++)
            s[j] = v[j];
        ss[i] = s;
    }
    return ss;
}

Le fichier calcutils.cpp fournit d'autres conversions bien utiles mais non présentées ici. Prenez le temps de jeter un coup d'oeil dans les codes sources du projet.

Notez aussi qu'au chapitre 3 nous avons présenté des conversions un peu plus abstraites en utilisant les templates. Il me semble que ce type de conversion est intégré dans la nouvelle version du projet maintenant.

Gérer simultanément des Séquences de Séquences et des Séquences simples

Vous avez certainement déjà rencontré des fonctions de OOoCalc capables de travailler sur des groupes de cellules ou sur une liste de valeurs (ou de cellules). La fonction addition en est un exemple. Le problème est que le groupe de cellule est une séquence de séquence tandis que la liste est une séquence (simple) de valeurs et la question est : comment gérer une telle situation ?

Christophe Devalland s'est trouvé face à ce problème quand il s'est intéressé à l'interfaçage de la librairie XCas/xgiac (Voir aussi la note ci-après) et a résolu le problème avec l'aide de Niklas Nebel de cette manière :

Any CASImpl::cadd(const Sequence< Any > &aValList )
    throw (RuntimeException)
{     sal_Int32 n1;
    sal_Int32 nE1 = aValList.getLength();
    Sequence< Sequence< Any > > aArraySeq;
    gen e;
    giac::context * contextptr=0;
    e=0;
    for( n1 = 0 ; n1 < nE1 ; n1++ )
    {
        try
        {
            if ( aValList[ n1 ] >>= aArraySeq )
            {
                // traiter la zone de cellules contenue dans aArraySeq
                //
                Somme_Array(e,aArraySeq);
            }
            else
            {
                // aValList[n1] ne contient qu'une valeur
                e=e+Cellule2Gen(aValList[ n1 ]);
            }
        }
        catch (std::runtime_error & erreur)
        {
            return makeAny(OUString::createFromAscii("Erreur : ")+OUString::createFromAscii(erreur.what()));
        }
    }
    e=giac::simplify(e,contextptr);
    return Gen2any(e);
}
void Somme_Array(gen & e, Sequence< Sequence< Any > > & aArraySeq)
{
    // somme le contenu de la zone de cellules désignée par aArraySeq
    sal_Int32        n1, n2;
    sal_Int32        nE1 = aArraySeq.getLength();
    sal_Int32        nE2;
    for( n1 = 0 ; n1 < nE1 ; n1++ )
    {
        const Sequence< Any >    rList = aArraySeq[ n1 ];
        nE2 = rList.getLength();
        const Any*    pList = rList.getConstArray();
        for( n2 = 0 ; n2 < nE2 ; n2++ )
        {
            // version modifiée de Cellule2Gen pour gérer les cellules vides
            OUString type_name=pList[n2].getValueTypeName();
            if (type_name==OUString::createFromAscii("double"))
            {
                double valeur;
                sal_Int32 partie_entiere;
                pList[n2] >>= valeur;
                partie_entiere=(sal_Int32)(valeur);
                if (partie_entiere==valeur)
                {
                    // retrouver le type int perdu par OOo
                    e+=(int)partie_entiere;       
                }
                else
                {
                    // non entier
                    // chaine=OUString::valueOf(valeur);
                    e+=valeur;
                }
            }
            else
            {   
                if (type_name==OUString::createFromAscii("string"))
                {
                    OUString chaine;
                    pList[n2] >>= chaine;
                    OString aOString;
                    if (chaine.getLength()!=0)
                    {
                        aOString = OUStringToOString (chaine,RTL_TEXTENCODING_UTF8);
                        gen temp(string(aOString),0);
                        e+=temp;
                    }
                    else
                    {
                    // ne rien faire, la cellule est vide
                    // cela peut se produire quand on somme une zone de cellules dont certaines seront amenées
                    // à se remplir plus tard.
                    // aOString = OUStringToOString (OUString::createFromAscii("nulle"),RTL_TEXTENCODING_UTF8);
                    }
                }
            }
        }
    }
}

Ceci représente beaucoup de code mais concentrez-vous sur :

try
        {
            if ( aValList[ n1 ] >>= aArraySeq )
            {
                // traiter la zone de cellules contenue dans aArraySeq
                //
                Somme_Array(e,aArraySeq);
            }
            else
            {
                // aValList[n1] ne contient qu'une valeur
                e=e+Cellule2Gen(aValList[ n1 ]);
            }
        }

et toutes ses variables associées : c'est là qu'est traité le problème. Bien sûr le problème que vous avez à résoudre est un peu différent, il n'empêche que tout est là.

Interfacer la librairie GSL (GNU Scientific Library)

Notre objectif est de fournir un exemple permettant l'utilisation de routines scientifiques C dans notre tableur. Interfacer complètement la librairie est un travail assez énorme puisqu'il faudra pour chaque fonction écrire la méthode correspondante dans un fichier IDL Je ne donnera qu'un exemple restreint seulement pour montrer la voie.

Première étape

La première étape est de naturellement installer la librairie GSL. Pour cela il faut lire la documentation correspondante.

Deuxième étape

Il nous faut trouver la fonction GSL que l'on veut utiliser et écrire alors le fichier IDL correspondant. Pour cet exemple, je me suis intéressé au calcul des racines complexes d'un polynôme de degré quelconque. La fonction correspondante s'appelle « gsl_poly_complex_solve ». Le fichier IDL correspondant est présenté maintenant : il est un peu alourdi par le fait que l'on a gardé les méthodes de la version précédente de notre add-in (methodOne ...methodFour) :

//Listing 14 Mon Add-in avec la GSL : le fichier IDL
// IDL
module my_module
{
 
interface XSomething : com::sun::star::uno::XInterface
{
	string methodOne( [in] string val );
	string methodTwo( [in] string val );
	long methodThree( [in] sequence< sequence< long > > aValList );
	sequence< sequence< long > > methodFour( [in] sequence< sequence< long > > aValList );
	sequence< sequence< double > > poly_complex_solve( 
					[in] sequence< sequence< double > > aValList );
};
 
service MyService2
{
    interface XSomething;
    interface com::sun::star::lang::XInitialization;
    interface com::sun::star::lang::XServiceName;
    interface com::sun::star::sheet::XAddIn;
};
};

Le nom de la nouvelle méthode sera donc « poly_complex_solve ».

Troisième étape

Il faut modifier le makefile pour que l'édition de liens utilise la librairie GSL en plus des autres librairies standards :

#Listing 15 Nouveau MakeFile
....
ifeq "$(OS)" "WIN"
$(SHAREDLIB_OUT)/%.$(SHAREDLIB_EXT) : $(SLOFILES) $(OUT_COMP_GEN)/%.def
	-$(MKDIR) $(subst /,$(PS),$(@D))
	$(LINK) $(LIBRARY_LINK_FLAGS) /OUT:$@ /MAP:$(OUT_COMP_GEN)/$(subst $(SHAREDLIB_EXT),map,$(@F)) \
	/DEF:$(OUT_COMP_GEN)/$(subst $(SHAREDLIB_EXT),def,$(@F)) $(SLOFILES) \
	$(CPPUHELPERLIB) $(CPPULIB) $(SALLIB) $(STLPORTLIB) msvcrt.lib kernel32.lib
else
$(SHAREDLIB_OUT)/%.$(SHAREDLIB_EXT) : $(SLOFILES)
	-$(MKDIR) $(subst /,$(PS),$(@D))
	$(LINK) $(LIBRARY_LINK_FLAGS) $(LINK_LIBS) -o $@ $^\
	$(CPPUHELPERLIB) $(CPPULIB) $(SALLIB) $(STLPORTLIB) $(STC++LIB) -lgsl -lgslcblas -lm 
#	$(CPPUHELPERLIB) 
endif	
....

Quatrième étape

Ecrire le code et compiler. Le code donné ci-dessous fonctionne correctement si les coefficients du polynôme sont donnés sur une ligne du tableur, donc plus exactement un groupe de cellules ne faisant qu'une ligne. Dans tous les autres cas il retourne les même valeurs que celle qui sont données en entrées.

//Listing 16 Nouveau Code
// C++
....
#include <gsl/gsl_poly.h>
....
Sequence< Sequence< double > > MyService2Impl::poly_complex_solve(
		const Sequence< Sequence< double > > &aValList )throw (RuntimeException)
{ 	
	sal_Int32		nE1 = aValList.getLength();
	sal_Int32		nE2;
 
	Sequence< double >	rList = aValList[ 0 ];
	nE2 = rList.getLength();
	double table[nE1][nE2];
	for( sal_Int32 n1 = 0 ; n1 < nE1; n1++ )
	{
		for (sal_Int32 n2=0;n2<nE2;n2++) 
			table[n1][n2]=aValList[n1][n2];
	}	
// I have a 2D nE1xnE2 table here 
	if (nE1==1){ // real coefficients horizontally disposed	
		double z[(nE2-1)*2];
		gsl_poly_complex_workspace * w = gsl_poly_complex_workspace_alloc(nE2);	
		gsl_poly_complex_solve(table[0],nE2,w,z);
		gsl_poly_complex_workspace_free(w);
		Sequence< double >	solList1D(2);
		Sequence< Sequence< double > > solList2D(nE2-1);
		for( int j=0;j<((nE2-1)<<1);j+=2){
			for( int i=0; i<2 ; i++){
				solList1D[i]=z[i+j];
			}
			solList2D[j>>1]=solList1D;
		}
		return solList2D;
	} else
	if (nE2==1){ // real coefficients vertically disposed	: doesn't work at the moment
		Sequence< Sequence< double > > temp = aValList; // return the coefficients
	  	return temp;
	} else {
	// If here : error 
	  Sequence< Sequence< double > > temp = aValList; // return the coefficients
	  return temp;
	}
}

Et voici le résultat présenté dans un tableau symbolisant votre tableur. La première ligne n'étant pas essentielle elle pourra être ou non présente dans votre feuille :

Résultats coefficients horizontaux et racines verticales
x^4 x^5
-1 0 0 0 0 1
-0.81 0.59
-0.81 -0.59
0.31 0.95
0.31 -0.95
1 0

où vous voyez en haut les coefficients sur la première ligne. Ils signifie que le polynôme qui nous intéresse est -1+x^5. Les cinq racines se retrouvent ensuite en dessous (avec la partie réelle à gauche et la partie imaginaire à droite).

Notes

Documentation caution.png Si vous installez un addin externe sous linux et rencontrez un problème, vérifiez les dépendances de votre librairie dynamique avec la commande ldd : ldd votre_addin.so pour voir si par hasard votre problème n'est pas dû à une librairie manquante.
Documentation note.png Au lieu d'aller plus en avant pour interfacer la librairie GSL prenez en considération le fait qu'un projet avancé d'interfaçage de la librairie giac existe (giac est un Computer Algebra System utilisé dans XCas). Voici la documentation en français de l'addin correspondant ainsi que l'addin proprement dit en version 1.0 pour Linux ou M$ Windows (même fichier pour les deux versions).

Retour à la page d'accueil

Page d'accueil du développement C++ à l'aide du SDK

Voir aussi

Personal tools