Calc/Add-In/CompleteAddIn

From Apache OpenOffice Wiki
< Calc‎ | Add-In
Jump to: navigation, search

An other C++ add-in example

In this article under construction, a complete and simple add-in in C++ is presented.

Introduction

Before reading this article, please read Constructing Components and SimpleCalcAddIn. In SimpleCalcAddin you will learn most of important things like the tools involved in constructing an addin, the services involved and also the corresponding references to read in the devlopper's guide.

Let's recall here how different files are created and what are the corresponding tools involved ? A drawing is beter than a long discussion :

ComponentTools.png

As you can see idlc is used to transform a .IDL-suffix file into a .urd-suffix file, regmerge to transform the previous .urd-suffix file into a .rdb-suffix file and so on... (see the corresponding SimpleCalcAddin compilation script and also Compilation Chain of a Component. The action done by the red arrow is not a file transformation but what is usually called registery. Before going further let's note that starting from an idl file, we will generate an hpp file : this file automatically defines a C++ class that we will use in the cxx file.

You can go further with component and Java here.

We choose here an other way : starting from an example of SDK slightly modified : <OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent

I mean I will use this example makefile only modified to take into account the fact I only use one file instead the two provided by the example. The makefile compiles the component and installs it.

IDL File

We begin with a simple IDL file with four methods. Here is the corresponding IDL file :

// IDL
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
#include <com/sun/star/lang/XServiceName.idl>
#include <com/sun/star/lang/XLocalizable.idl>
#include <com/sun/star/sheet/XAddIn.idl>
module my_module
{
  interface XSomething : com::sun::star::uno::XInterface
{ // our four methods
  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 MyService2
{
interface XSomething;
interface com::sun::star::lang::XInitialization;
interface com::sun::star::lang::XServiceName;
interface com::sun::star::sheet::XAddIn;
};
};

Four methods named methodOne, methodTwo, methodThree and methodFour can be seen in this IDL file and then will be implemented.

We give the automatically generated C++ class with the cppumaker tool.

class SAL_NO_VTABLE XSomething : 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;
};

where again the four methods are seen but in C++ language. Note before going further that parameters of the methods are always marked as const but passed by reference (in C++ style with & operator). It's time to give the corresponding code.

Implementing in C++ the corresponding four member Functions

We first give the C++ code of the four methods presented in the previous IDL file.

The two first member functions

The two first methods are similar :

// C++
OUString MyService2Impl::methodOne( OUString const & str )
throw (RuntimeException)
{
  return OUString( RTL_CONSTASCII_USTRINGPARAM(
    "called methodOne() of MyService2 implementation: ") ) + m_arg + str;
}
 
OUString MyService2Impl::methodTwo( OUString const & str )throw (RuntimeException)
{
  return OUString( RTL_CONSTASCII_USTRINGPARAM(
    "called methodTwo() of MyService2 implementation: ") ) + m_arg + str;
}

They only take a string (from a OOoCalc Cell) and add a message and put all the message+string in the result cell.

The third member Function

The third member function is more complicated : it returns a value calculed from a cell range (the sum).

// C++
sal_Int32 MyService2Impl::methodThree(const Sequence< Sequence< sal_Int32 > > &aValList )
throw (RuntimeException)
{ 
  sal_Int32 n1 = 0;
  sal_Int32 n2 = 0;
  sal_Int32 nE1 = aValList.getLength();
  sal_Int32 nE2 = 0;
  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;
}

The fourth member Function

The goal of the fourth member function is to show how we can implement a matrix function : starting from a cell range and obtaining a cell range.

//C++
//It's a matrix operation should be called like : {=METHODFOUR(A1:B4)}
Sequence< Sequence< sal_Int32 > > MyService2Impl::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;
}

What is done by this example is not great : only add four to every cells of the cell range and put the result in an other cell range.

Every method are related to a C++ class named MyService2Impl. Let's now give the corresponding class definition.

The C++ Class

We use an helper to implement our class and then we don't need to implement all the interfaces : no need to implement XInterface, XTypeProvider, XWeak for instance. We give now the corresponding C++ code.

//C++
class MyService2Impl : public ::cppu::WeakImplHelper5<
      ::my_module::XSomething, lang::XServiceInfo, lang::XInitialization, lang::XServiceName,
	XAddIn>
{
    OUString m_arg;
public:
    // 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);
    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 setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException );
    virtual lang::Locale SAL_CALL getLocale() throw( uno::RuntimeException );
};

The code is not complete with the four methods as you can see in the class declaration. Only your own XSomething interface is already implemented (see above). You can wonder how is it possible to find all the methods in the class ? As you will learn below, the IDL files corresponding to interfaces have to be read for that. The number of interfaces you have to implement is dependant of your goal. If you want to create a component you have to implement XInterface interface and add more code, if you want to make a scriptable component you still add more code (and more interfaces : XTypeprovider, XServiceInfo and XWeak) and if you want to create an AddIn you have to add more (XAddin, XServiceName). This is explained in Developper's Guide. We first begin with a typical addin interface : XAddin.

The XAddIn Interface

Every add-in have to implement the com.sun.star.sheet.XAddIn interface. As usual what you have to do is described by an IDL file (or you can read the previous C++ class definition) :

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

The corresponding C++ code is given now without explanation (for the moment)

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

To understand what is this code related to, we provide a snapshot : read it carefully to see where the code works and provides the strings to print out in the autopilot frame :

Addin2.png

I have used the method one as you can see above the autopilot frame, and I am ready to use the method two. What you can read in the autopilot comes directly from the C++ code except the French word "requis" which is probably translated by "required" in an English OpenOffice version.

The XServiceName Interface

Again the corresponding IDL file indicates what we have to do :

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

This interface is simple : one method :

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

See com.sun.star.lang.XServiceName for the complete documentation.

The XServiceInfo Interface

This com.sun.star.lang.XServiceInfo interface may be avoided. You have to implement it only if you want that your add-in is also scriptable : you can call it from OOoBasic for instance. It's time to present the corresponding IDL file :

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

Here is the corresponding C++ code :

// C++
// XServiceInfo implementation
OUString MyService2Impl::getImplementationName()
throw (RuntimeException)
{
// unique implementation name
  return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
 
sal_Bool MyService2Impl::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.MyService2") ) ||
         serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("com.sun.star.sheet.AddIn") );
}
 
Sequence< OUString > MyService2Impl::getSupportedServiceNames()
throw (RuntimeException)
{
  return getSupportedServiceNames_MyService2Impl();
}
 
Reference< XInterface > SAL_CALL create_MyService2Impl(Reference< XComponentContext > const & xContext)
SAL_THROW( () )
{
  return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}

XInitialisation Interface

This com.sun.star.lang.XInitialization interface could be removed without problem. I will remove it completely in the futur. Here is the corresponding C++ code.

// C++
// XInitialization implemention
void MyService2Impl::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
    }
}

XLocalizable Interface

This com.sun.star.lang.XLocalizable interface is particular : we have let it in the class definition and give now the corresponding code but because the code do nothing, we don't add it as sixth interface when using the helper.

// C++
// XLocalizable
void SAL_CALL MyService2Impl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException )
{
//    aFuncLoc = eLocale;
//    InitData();     // change of locale invalidates resources!
}
 
lang::Locale SAL_CALL MyService2Impl::getLocale() throw( uno::RuntimeException )
{
//    return aFuncLoc;
}

Making a registerable component

An add-in has to be registered. In fact if you want to use external code, you have to work with a registry. If you use your external code with OOoBasic you call it a component and if you use it with OOoCalc you call it an add-in. But both are very similar. In my knowledge the only exception to this registry rule is OOoBasic (under Windows) which can call directly an external DLL.

To supplement the above code we have to add :

// C++
/* shared lib exports implemented without helpers in service_impl1.cxx */
namespace my_sc_impl
{
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
    {
        create_MyService2Impl, getImplementationName_MyService2Impl,
        getSupportedServiceNames_MyService2Impl, ::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 );
}
}

The complete Code

It's time to give the C++ complete code :

//Listing 9 My first Add-In
//C++
#include <cppuhelper/implbase5.hxx> // "5" implementing five 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 <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
using namespace ::com::sun::star::sheet;
 
 
namespace my_sc_impl
{
 
static Sequence< OUString > getSupportedServiceNames_MyService2Impl()
{
	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("my_module.MyService2"));
                        seqNames.getArray()[1] = OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.sheet.AddIn"));
			pNames = &seqNames;
		}
	}
	return *pNames;
}
 
static OUString getImplementationName_MyService2Impl()
{
	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 MyService2Impl : public ::cppu::WeakImplHelper5<
      ::my_module::XSomething, lang::XServiceInfo, lang::XInitialization, lang::XServiceName,
	/*lang::XLocalizable, */XAddIn>
{
    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);
    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);
    // ********************** END ADDED
    // 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 MyService2Impl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException );
    virtual lang::Locale SAL_CALL MyService2Impl::getLocale() throw( uno::RuntimeException );
};
 
// XInitialization implemention
void MyService2Impl::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
    }
}
 
// XServiceName
OUString SAL_CALL MyService2Impl::getServiceName() throw( uno::RuntimeException )
{
    // name of specific AddIn service
    return OUString::createFromAscii( "my_module.MyService2" );
}
// XSomething implementation
OUString MyService2Impl::methodOne( OUString const & str )
    throw (RuntimeException)
{
    return OUString( RTL_CONSTASCII_USTRINGPARAM(
        "called methodOne() of MyService2 implementation: ") ) + m_arg + str;
}
// **********************ADDED 
 
OUString MyService2Impl::methodTwo( OUString const & str )
    throw (RuntimeException)
{
    return OUString( RTL_CONSTASCII_USTRINGPARAM(
        "called methodTwo() of MyService2 implementation: ") ) + m_arg + str;
}
 
 
sal_Int32 MyService2Impl::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 > > MyService2Impl::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;
}
 
// ********************** END ADDED 
 
// XAddIn
 
OUString SAL_CALL MyService2Impl::getProgrammaticFuntionName( const OUString& aDisplayName ) throw( uno::RuntimeException )
{
    //  not used by calc
    //  (but should be implemented for other uses of the AddIn service)
    return OUString();
}
 
OUString SAL_CALL MyService2Impl::getDisplayFunctionName( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aProgName, aRet;
    aProgName = aProgrammaticName;
    if (aProgName.equalsAscii("methodOne")) aRet = OUString::createFromAscii("method1");
    if (aProgName.equalsAscii("methodTwo")) aRet = OUString::createFromAscii("method2");
    if (aProgName.equalsAscii("methodThree")) aRet = OUString::createFromAscii("method3");
    if (aProgName.equalsAscii("methodFour")) aRet = OUString::createFromAscii("method4");
 
    return aRet;
}
 
OUString SAL_CALL MyService2Impl::getFunctionDescription( const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet;
    if (aProgrammaticName.equalsAscii("methodOne")) 
	aRet = OUString::createFromAscii("methodOne() : 1st try");
    if (aProgrammaticName.equalsAscii("methodTwo")) 
	aRet = OUString::createFromAscii("methodTwo() : 1st try");
    if (aProgrammaticName.equalsAscii("methodThree")) 
	aRet = OUString::createFromAscii("methodThree() : 1st try");
    return aRet;
}
 
OUString SAL_CALL MyService2Impl::getDisplayArgumentName(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{   
    OUString aRet;
    if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) 
	aRet = OUString::createFromAscii("a string");
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	aRet = OUString::createFromAscii("a Cell Range");
    return aRet;
}
 
OUString SAL_CALL MyService2Impl::getArgumentDescription(
        const OUString& aProgrammaticName, sal_Int32 nArgument ) throw( uno::RuntimeException )
{
    OUString aRet;
    if (aProgrammaticName.equalsAscii("methodOne")||aProgrammaticName.equalsAscii("methodTwo")) 
	aRet = OUString::createFromAscii("method1/2:a string or a cell with a string is required");
    if (aProgrammaticName.equalsAscii("methodThree")||aProgrammaticName.equalsAscii("methodFour")) 
	aRet = OUString::createFromAscii("method3/4:a cell range is required");
    return aRet;
}
 
OUString SAL_CALL MyService2Impl::getProgrammaticCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    OUString aRet( RTL_CONSTASCII_USTRINGPARAM("Add-In"));
    return aRet;
}
 
OUString SAL_CALL MyService2Impl::getDisplayCategoryName(
        const OUString& aProgrammaticName ) throw( uno::RuntimeException )
{
    return getProgrammaticCategoryName( aProgrammaticName );
}
 
// XServiceInfo implementation
OUString MyService2Impl::getImplementationName()
    throw (RuntimeException)
{
    // unique implementation name
    return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
sal_Bool MyService2Impl::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    // this object only supports one service, so the test is simple
    // modified *********
    return serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("my_module.MyService2") ) ||
           serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("com.sun.star.sheet.AddIn") );
}
Sequence< OUString > MyService2Impl::getSupportedServiceNames()
    throw (RuntimeException)
{
	return getSupportedServiceNames_MyService2Impl();
}
 
Reference< XInterface > SAL_CALL create_MyService2Impl(
    Reference< XComponentContext > const & xContext )
    SAL_THROW( () )
{
    return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}
 
// XLocalizable
void SAL_CALL MyService2Impl::setLocale( const lang::Locale& eLocale ) throw( uno::RuntimeException )
{
//    aFuncLoc = eLocale;
//    InitData();     // change of locale invalidates resources!
}
 
lang::Locale SAL_CALL MyService2Impl::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_MyService2Impl, getImplementationName_MyService2Impl,
        getSupportedServiceNames_MyService2Impl, ::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 );
}
}

We have learnt now how things works and particularly how to manage sequence of sequence parameters or returned value.

Documentation note.png This code is to set in <OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent path because there is no add-in exemple in the SDK. You have to slightly modify the corresponding makefile remove a source file to check this example.

Utilities

In order to provide utilities to interface OOo to other external libraries, we will provide in this section any utilities to transform classical variables into UNO variables.

Transforming sequence of sequence into array

If you plan to extend OOoCalc in any way, you will probably use an external library, for instance a C library like GSL. You will notice that the variables are in general arrays of double and not sequence. Have a look to the case of Listing below where you see an imaginary method « foo ». The point is we use a sequence of sequence as parameter and we want to transform it into 2D array.

//C+
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
//!!!!!!!! This code is wrong : double table[nE1][nE2]; with nE1 and nE2 
//!!!!!!!! unknown at the compilation time. !!!!!!!!
// I have used this code with success but I don't know why ? 
// Perhaps the compiler use a constructor in this case ?
// I will have a look as sooner as possible

At the end of this program we have a 2D array named « table » and we show how to construct it. (I have tried a way using getArray() but without success).

As mentioned in the comments, this code has a poor quality in my knowledge. At the moment, I provide other code here

Transforming an Array into Sequence of Sequence

Imagine your calculus provides a 1D array named « z » and you want to construct a sequence of sequence before returning it. Here is what you could do :

//C+
...
// here a line is constructed with size (nE2-1)*2
	Sequence< double >	solList1D(z,(nE2-1)*2);
	Sequence< Sequence< double > > solList2D(1);
	solList2D[0]=solList1D;
	return solList2D;

A similar problem encountered later has to be solved differently : I start from an 1D array but this array contains, real part and imaginary part « The n-1 roots are returned in the packed complex array z of length 2(n-1), alternating real and imaginary parts » I want to show the result in Calc but with two rows (real part and imaginary part) with as many lines as necessary. Here is the way :

//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 >> 2 is faster than j/2
		}
		return solList2D;

Sometimes we have to use Standard Template Library because the library we want to interface is in C++. Eric Ehlers has interfaced a C++ library (QuantLib project) and then provide any interesting code here. See particularly the file calcutils.cpp. I have reproduct part of the code here but with using template.

Managing simultaneously Sequence of Sequence and Sequence

It's possible to encounter any buildin functions which can cope with cell range and with a list of values. Because the former is a Sequence of Sequence and the latter a simple Sequence, the question is how to manage such a data structure in an addin function ?

Christophe Devalland was facing this problem when interfacing the XCas library (See the note below) and solves it with help of Niklas Nebel in this way :

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

This is a lot of code, but have a look to this snippet :

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 ]);
            }
        }

and all the corresponding variables : here is the trick. Of course your problem is different, but start with this snippet.

Interfacing the GSL (GNU Scientific Library)

Our goal is now to provide an example of intarfacing OOo with a scientific library. To give the possibility of accessing all the library is an enormous task that I cannot realize. I only want to show the way.

First step

The first step is to install the GSL. See the GSL documentation

Second step

Find what gsl function do you want to wrapp and write the corresponding IDL file. For this example I am interested in finding the complex roots of a polynom. The corresponding function is « gsl_poly_complex_solve ».

The corresponding IDL file is (keeping all the old methods of the previous sections) :

//Listing 14.14 My SDL interfacing Add-In : the IDL File
// 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;
};
};

The name of the new method will be « poly_complex_solve »

Third Step

You have to modify the makefile to link your addin with the GSL libraries :

#Listing 14.15 New 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
....

Step Four

Write the code and compile. The code given below only works when the polynomial coefficients are given in a line. In all the other cases it returns the same values as given in entry.

//Listing 14.16 New 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;
  }
}

Here is the result :

Résults
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

where you see horizontally the polynomial coefficients and vertically the five roots with real part (left) and imaginary part (right).

Notes

Documentation caution.png If you are installing an external addin under linux and encounter a problem verify the dynamic library dependance with ldd command : ldd your_addin.so to see if a dynamic library fails.
Documentation note.png Instead of going further with interfacing the GSL, bear in mind there is an advanced project of interfacing the giac library (giac is a Computer Algebra System used in XCas) which is already working. Here is the French Documentation of the corresponding addin. You can download the version 1.0 for Linux or M$ Windows (same file for both version).

Home Page

HomePageCpp.png Return to Document Home page

See also

This article is under construction and if you want to go further, for the moment have a look to the following references :

Personal tools