Constructing Components

From Apache OpenOffice Wiki
Jump to: navigation, search


Edit-find-replace.png This article needs to be checked for accuracy and style.

You want to extend OpenOffice.org in a way. The first step is to define what your goals are; what do you want achieve ? You then produce a shared library with suffix uno.so under Linux or uno.dll under Windows. You load your library into OpenOffice.org by using a rdb file and an operation called registry. Registry basics have already been detailed in UNO registry and Bootstrapping.

Danny Brewer's Terminology

You can read first in this wiki the extensions terminology. You can also read here :

Let's clarify some terminology.

  • Add On

A menu item on the Tools menu, and an icon on the toolbar. By modifying the Configuration sub node for AddOn, you can add new menu items and toolbar icon items to the AddOn menu or toolbar icon. Basic programs can modify the Configuration to add some Add On items to the menu. It is not necessary to use pkgchk. Components (see below) can also add things to the AddOn menu or toolbar when the component is installed using pkgchk.

  • Add In

A component (see below) which provides new Calc spreadsheet functions. These functions show up on the Function List and Insert --> Function... dialog box. The dialog box can then fully describe your functions, their parameters, provide tooltips, etc.

An UNO Component, written in any supported programming language, which must be installed using the pkgchk command. (This would be "pkgchk.exe" for Windows users.) A component provides one or more new Services which are registered in the OOo registry. These services can be instantiated just like the factory installed services in OOo. For instance, in Basic, you can just call createUnoService( "name.DannyBrewer.magic.SomeService" ) to get an instance of the service, and then immediately call its methods or properties. Similarly, Python or Java, or Visual Basic, or any other language can use the newly installed service.

A component provides one or more services. See for instance Using Java Inspector component.

  • Service

An UNO abstraction for an Object. A service may not actually represent a single underlying object. A service may have many different interfaces which it implements. A service has properties. What methods are callable on the service are determined purely by what interfaces the service implements.

Since a Calc Add-In is just a component, which implements certain particular interfaces and services, the Calc AddIn is installed and registered in the same way as any other component. That is, a Calc AddIn is installed or removed by using pkgchk. Making a Calc Add-In is just like making any other service. You must be a particular com.sun.star.sheet.AddIn service, and must properly implement all of its interfaces, of which there are several. But once you do this, your service magically provides new functions to the spreadsheet. Calc Add-In are tackled the next chapter. You can find an introduction on addIn also here.

Compilation Chain of a Component

To create a UNO component, you require an IDL file and a cpp file. The entire process is illustrated in the figure below. The final goal is to create a uno.so and a rdb file. If you want more information on binary tools in the compilation chain, have a look at the corresponding chapter in Developer's Guide.


ComponentTools.png

The black arrows in the figure above indicate how to create one file type from another. For example, use idlc to create some.urd starting from some.idl. The red arrow indicates that regcomp is used to place the name of the library done into the rdb file rather than create the rdb file. Finally, the blue arrows indicate a dependency. The entire process is as follows :

  1. Create the idl and cpp files. Figure above shows these files as some.idl and some.cpp.
  2. Use idlc to compile the idl file to a urd file.
  3. Transform the urd file to the rdb file using regmerge. The rdb file is not yet complete because it must contain the name of the uno.so file, which is added at the end using regcomp.
  4. Use cppumaker to create the hpp and hdl header files.
  5. Use gcc to compile the cpp file.
  6. The final registry step has been discussed previously and is not shown in Figure above.
Documentation note.png Service Managers UNO uses a factory concept to instantiate components in a target environment. This factory is called the service manager. The service manager maintains a database of registered components. Your application can instantiate and communicate with any component known by the service manager, no matter what language it was implemented in. Communication takes place through interface calls specified by the UNO IDL. See Creating a ServiceManager from a Given Registry File in Developer's Guide.

To create a component, you must first create an idl file. The IDL file describes the interface. The goal of the following examples, is to create a component, visible to OpenOffice.org more specifically, visible from OOoBasic. The SDK contains an example in: “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent” and also described in Developer's Guide. The view described above is only important when you wonder how a component is constructed. If you want to know what you have to do when writing a component an other view is needed ; it is described later with the corresponding three rules.

First Example : accessing a Counter from OOoBasic

Our first goal is to transform a counter into a component. A counter is perhaps not great but a good start. The inspiration comes to me with the following two SDK's examples :

  • <OpenOffice.org1.1_SDK>/examples/DevelopersGuide/Components/CppComponent (used in previous section) and also descibed in Developer'sGuide.
  • <OpenOffice.org1.1_SDK>/examples/cpp/counter

The problem with the counter example given above with the SDK is its inaccessibility from OOoBasic. A component callable from OOoBasic and then from all other programming language is called scriptable. We begin describing the counter.

A very simple Counter

We give in this section our start : a counter.cxx file and a countermain.cxx file working all together. Please note that this example would be simpler if not constructed for OpenOffice.org. It's only given here to present our start. From this start we will wonder a lot of questions showing I hope how components work. I will use sometimes UML notations in this chapter.

Single File Implementation

We plan to realize our counter with one file (except the header file) in this section :

//Listing 1 The Counter
//c++
#include <stdio.h>
#include "counter.hxx"
 
	MyCounterImpl::MyCounterImpl()
		: m_nCount( 0 )
		{ printf( "< MyCounterImpl ctor called >\n" ); }
	MyCounterImpl::~MyCounterImpl()
		{ printf( "< MyCounterImpl dtor called >\n" ); }
	int MyCounterImpl::getCount() 
		{ return m_nCount; }
	void MyCounterImpl::setCount( int nCount ) 
		{ m_nCount = nCount; }
	int MyCounterImpl::increment() 
		{ return (++m_nCount); }
	int MyCounterImpl::decrement() 
		{ return (--m_nCount); }
 
int main(int argc, char **argv)
{
	MyCounterImpl Cnt;
	Cnt.setCount(50);
	printf("-- %d\n",Cnt.getCount());
	Cnt.increment();
	printf("-- %d\n",Cnt.getCount());
	Cnt.decrement();
	printf("-- %d\n",Cnt.getCount());
	return 0;
}

with the header file

//Listing 2 The header File of the Counter
// C++
// counter.hxx
class MyCounterImpl {
	int m_nCount;
public:
	MyCounterImpl();
	~MyCounterImpl();
	int getCount();
	void setCount( int nCount ); 
	int increment(); 
	int decrement() ;
};

What we want is to separate the code in two files : one for the class : counter.cxx and a second for the utilisation of the class countermain.cxx. The counter.cxx will be compiled in counter.so file and countermain will use this dynamic library.

Two Files Implementation

The corresponding way in C++ is again illustrated. First the dynamic library :

//Listing 3 Dynamic library
//c++
//counter.cxx
#include <stdio.h>
#include "counter.hxx"
 
MyCounterImpl::MyCounterImpl()
	: m_nCount( 0 )
	{ printf( "< MyCounterImpl ctor called >\n" ); }
MyCounterImpl::~MyCounterImpl()
	{ printf( "< MyCounterImpl dtor called >\n" ); }
int MyCounterImpl::getCount() 
	{ return m_nCount; }
void MyCounterImpl::setCount( int nCount ) 
	{ m_nCount = nCount; }
int MyCounterImpl::increment() 
	{ return (++m_nCount); }
int MyCounterImpl::decrement() 
	{ return (--m_nCount); }

This file is compiled with :

 gcc -shared -fPIC -o counter.uno.so counter.cxx

To use it I write this code :

//Listing 4 Main executable
//c++
//countermain.cxx
#include "counter.hxx"
#include <stdio.h>
int main(int argc, char **argv)
{
	MyCounterImpl Cnt;
	Cnt.setCount(50);
	printf("-- %d\n",Cnt.getCount());
	Cnt.increment();
	printf("-- %d\n",Cnt.getCount());
	Cnt.decrement();
	printf("-- %d\n",Cnt.getCount());
	return 0;
}

The compilation is then done with

 gcc -o test1 countermain.cxx -L ./ counter.uno.so -lstdc++

Running this example gives :

[smoutou@p3 component]$ ./test1
< MyCounterImpl ctor called >
-- 50
-- 51
-- 50
< MyCounterImpl dtor called >

This example is done without Openoffice.org. In the contrary the example given in the SDK make exacly the same but needs OpenOffice.org. Problems with shared library and C++ are examined here.

Making the Counter registrable

All dynamic libraries are not registrable. Taking the one constructed in previous section and give it to register, what's happening ?

[smoutou@p3 bin]$ regcomp -register -r counter.uno.rdb -c counter.uno.so
Aborted

Regcomp is completely unable to do correctly its job. Why ? If you want to be able to registry something you have to follow strict rules.

  • Rule 1

You have to construct first an IDL file. This IDL file allows you to construct an urd file and to add it in a rdb file. This has already been discussed above. See the figure below.

Registery.png

When examining this figure you see that a some.idl file is required (and naturally a uno.so file).

  • Rule 2

Your dynamic library have to contain extern C subprograms. Their names are :

// C++
extern "C" void SAL_CALL component_getImplementationEnvironment(
    sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv );
 
extern "C" sal_Bool SAL_CALL component_writeInfo(
    lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry );
 
extern "C" void * SAL_CALL component_getFactory(
    sal_Char const * implName, lang::XMultiServiceFactory * xMgr, void * );

First and second subroutines are for registry and third is for allowing UNO_QUERY calls. More information here where a fourth optional extern C subprogram is presented : getDescription.

  • Rule 3 (provisional rule)

Your component has to provide some other interfaces that those described in your IDL file : com.sun.star.uno.XInterface, com.sun.star.lang.XServiceInfo. The counter example provide these interfaces as shown in the listing below :

//Listing 5 The counter interfaces
// C++
class MyCounterImpl
	: public XCountable
	, public XServiceInfo
{
....

I suppose XCountable is an inheritance from XInterface. The figure above shows us a component in a schematic view.

ComponentInterface.png

The external rectangle in Figure above represents the component. In this component, we see a XCountable interface inheriting from XInterface and XServiceInfo and the three extern C callable functions.

The definitive rule 3 is presented later: please be patient or read the Developer's Guide on the subject.

The Counter Example (from SDK)

The Counter is provided with SDK here : <OpenOffice.org_SDK>/examples/cpp/counter. See a description of the SDK counter example.

Modifying the simple Counter to see registry at Work

Again, we start with the counter example included in the SDK at <OpenOffice.org1.1_SDK>/examples/cpp/counter. We modify it to see in the console how registry works and particularly the necessity of the rule 2 described in a previous section. The modification only consists of adding printf as shown in the listing below. The goal is to help us to see how it works.

//Listing 7 Modified Counter Component (extract)
// C++
.....
 
//*************************************************************************
OUString SAL_CALL MyCounterImpl::getImplementationName(  )
	throw(RuntimeException)
{
	printf("MyCounterImpl::getImplementationName(  ) called \n");
	return OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
}
//*************************************************************************
sal_Bool SAL_CALL MyCounterImpl::supportsService( const OUString& ServiceName )
	throw(RuntimeException)
{
	printf("MyCounterImpl::supportsService called\n");
	Sequence< OUString > aSNL = getSupportedServiceNames();
	const OUString * pArray = aSNL.getArray();
	for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
		if( pArray[i] == ServiceName )
			return sal_True;
	return sal_False;
}	
 
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames(  )
	throw(RuntimeException)
{
	printf("MyCounterImpl::getSupportedServiceNames(  ) called \n");
	return getSupportedServiceNames_Static();
}
 
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames_Static(  )
{
	OUString aName( RTL_CONSTASCII_USTRINGPARAM(SERVICENAME) );
	printf("MyCounterImpl::getSupportedServiceNames_Static(  ) called with 
                                         %s\n",RTL_CONSTASCII_USTRINGPARAM(SERVICENAME));
	return Sequence< OUString >( &aName, 1 );
}
 
/**
 * Function to create a new component instance; is needed by factory helper implementation.
 * @param xMgr service manager to if the components needs other component instances
 */
Reference< XInterface > SAL_CALL MyCounterImpl_create(
	const Reference< XMultiServiceFactory > & xMgr )
{	printf("MyCounterImpl_create called\n");
	return Reference< XCountable >( new MyCounterImpl( xMgr ) );
}
 
//#####################################################################################
//#### EXPORTED ####################################################################################
//######################################################################################
/**
 * Gives the environment this component belongs to.
*/
extern "C" void SAL_CALL component_getImplementationEnvironment(const sal_Char ** ppEnvTypeName, 
                                                                  uno_Environment ** ppEnv) {
	printf("getImplementationEnvironnement return %s\n",CPPU_CURRENT_LANGUAGE_BINDING_NAME);
	*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
 
/**
 * This function creates an implementation section in the registry and another subkey
 *
 * for each supported service.
 * @param pServiceManager   the service manager
 * @param pRegistryKey      the registry key
*/
extern "C" sal_Bool SAL_CALL component_writeInfo(void * pServiceManager, void * pRegistryKey)
{
	sal_Bool result = sal_False;
	printf("component_writeInfo called\n");
	if (pRegistryKey)
	{
	try
	{
		Reference< XRegistryKey > xNewKey(
		  reinterpret_cast< XRegistryKey * >( pRegistryKey )->createKey(
			OUString( RTL_CONSTASCII_USTRINGPARAM("/" IMPLNAME "/UNO/SERVICES") ) ) );
		printf("New key : %s\n",RTL_CONSTASCII_USTRINGPARAM("/" IMPLNAME "/UNO/SERVICES"));
		printf("--component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static()\n");
		const Sequence< OUString > & rSNL =
			MyCounterImpl::getSupportedServiceNames_Static();
		const OUString * pArray = rSNL.getConstArray();
		for ( sal_Int32 nPos = rSNL.getLength(); nPos--; ) {
			xNewKey->createKey( pArray[nPos] );
			printf("----Sub-Key : %s build\n",OUStringToOString(pArray[nPos],
									RTL_TEXTENCODING_ASCII_US)
			.pData->buffer);
		}
		return sal_True;
	}
	catch (InvalidRegistryException &)
	{
		// we should not ignore exceptions
	}
	}
	return result;
}
 
/**
 * This function is called to get service factories for an implementation.
 *
 * @param pImplName       name of implementation
 * @param pServiceManager a service manager, need for component creation
 * @param pRegistryKey    the registry key for this component, need for persistent data
 * @return a component factory
 */
/**/
extern "C" void * SAL_CALL component_getFactory(const sal_Char * pImplName, 
                                              void * pServiceManager, void * pRegistryKey){
	void * pRet = 0;
	printf("component_getFactory called\n");
	if (rtl_str_compare( pImplName, IMPLNAME ) == 0)
	{
		Reference< XSingleServiceFactory > xFactory( createSingleFactory(
			reinterpret_cast< XMultiServiceFactory * >( pServiceManager ),
			OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) ),
			MyCounterImpl_create,
			MyCounterImpl::getSupportedServiceNames_Static() ) );
 
		if (xFactory.is())
		{
			xFactory->acquire();
			pRet = xFactory.get();
		}
	}
	return pRet;
}

With countermain slightly modified to know where the registry begins, this piece of code produces the following output :

[smoutou@p3 counter]$ make countermain.run
cd ../../../LINUXexample.out/bin && countermain
Here begin registry
getImplementationEnvironnement return gcc3
component_writeInfo called
New key : /com.sun.star.comp.example.cpp.Counter/UNO/SERVICES
--component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static()
MyCounterImpl::getSupportedServiceNames_Static(  ) called with foo.Counter
----Sub-Key : foo.Counter build
Here ends registry and begin instanciation
getImplementationEnvironnement return gcc3
component_getFactory called
MyCounterImpl::getSupportedServiceNames_Static(  ) called with foo.Counter
MyCounterImpl_create called
< MyCounterImpl2 ctor called >
42,43,42
Another registry use : getImplementations
-- com.sun.star.comp.bridge.UnoUrlResolver
< MyCounterImpl2 dtor called >
[smoutou@p3 counter]$

while a direct regcomp call produces this following output :

[smoutou@p3 bin]$ regcomp -register -r counter.uno.rdb -c counter.uno.so
getImplementationEnvironnement return gcc3
component_writeInfo called
New key : /com.sun.star.comp.example.cpp.Counter/UNO/SERVICES
--component_writeInfo calls MyCounterImpl::getSupportedServiceNames_Static()
MyCounterImpl::getSupportedServiceNames_Static(  ) called with foo.Counter
----Sub-Key : foo.Counter build
register component 'counter.uno.so' in registry 'counter.uno.rdb' succesful!
[smoutou@p3 bin]$

We can draw from both shell ouputs that registry works the same way, in the both case as expected. The first call is asking for Environment which return “gcc3” here, and after componentwriteinfo which is responsible of registry itself. A question : how do we see our counter with OOoBasic at this step ? Because our counter is registered we see it (partially) but we cannot use it. (Note you have to stop countermain with a getchar to have time launch a OOoBasic program.)

'Listing 8  Little OOoBasic Program to see our Counter.
 
REM  *****  BASIC  *****
 
Sub Main
	ocmpt = createUnoService("foo.Counter")
	XRay oCmpt
End Sub

Remark about OpenOffice build framework

When working in the OpenOffice build framework, make sure to add these functions to the util/NAME.map file, eg when adding the first UNO service to calc's scfilt library, do

--- sc/util/scfilt.map
+++ sc/util/scfilt.map
@@ -1,6 +1,9 @@
 SCFILT_1_0 {
   global:
     ScFilterCreate;
+    component_getImplementationEnvironment;
+    component_writeInfo;
+    component_getFactory;
   local:
     *;
 };

and that you tell scp2 the library is a UNO library, in the case of scfilt, do

--- scp2/source/calc/file_calc.scp
+++ scp2/source/calc/file_calc.scp
@@ -49,7 +49,7 @@ STD_UNO_LIB_FILE_PATCH( gid_File_Lib_Sc, sc)
 
 STD_LIB_FILE_PATCH( gid_File_Lib_Scui, scui)
 
-STD_LIB_FILE( gid_File_Lib_Scfilt, scfilt)
+STD_UNO_LIB_FILE( gid_File_Lib_Scfilt, scfilt)
 
 STD_UNO_LIB_FILE( gid_File_Lib_Scd, scd)

Results

XRay is a OOoBasic tool very easy to use for introspection. Bernard Marcelly's XRay tool description can be found in this Wiki.

XRaying this object as shown in the above listing, gives us :

properties

--- Object internal name : ( no name )
Dbg_Methods 
        string <...> basic prop, read-only
Dbg_Properties            
        string <...> basic prop, read-only
Dbg_SupportedInterfaces
        string <...> basic prop, read-only

methods

--- Object internal name : ( no name )
queryInterface  ( aType as type )  AS variant
acquire         (  )
release         (  )

supported interfaces

--- List of supported interfaces ---
com.sun.star.uno.XInterface

As shown above, only the XInterface can bee seen : not great ! We can therefore instantiate it but not call one of its own method. The next step is to use the counter with OOoBasic : in other words, to make it scriptable.

Using an Helper to construct the scriptable Counter

Starting from the Counter example we hope to modify the counter class and make it scriptable.

If you want to make your component scriptable you have to modify the previous rule 3 as this new one :

  • Rule 3

Your component has to provide some interfaces : com.sun.star.uno.XInterface, com.sun.star.lang.XServiceInfo, com.sun.star.uno.XWeak, and com.sun.star.lang.XTypeProvider. The last one is then a characteristic of scriptable component. See also the Developer's Guide.

A Scriptable Component

The Figure above shows us a scriptable component with a schematic view. Implementing the class Myclass means then implement all the virtual methods because of inheritance (and also external C functions). As we will show later in fact we are using an helper and this means we haven't to implement all Interfaces : only com.sun.star.lang.XServiceInfo is needed : others are implemented automatically with the helper as mentionned in the Developer's Guide.

Again we subsume the situation with a figure : how to construct a component with an helper. As shown in the figure below we have to implement seven methods for our counter.

A Scriptable Component with an Helper
  • Rule 4

Your component has to provide two interfaces as can be shown in the above figure, you will then use a "::cppu::WeakImplHelper2" when defining your class.

What about our makefile ? We are no more interested in calling this counter with countermain.cxx but using it in OOoBasic that means we have to register our dynamic library. Two solutions :

  1. we let again countermain to register the counter and we stop it after registered and then try to launch OOoBasic program,
  2. we look for an example which correctly registers. One is provided with the SDK as mentioned above :<OOo_SDK>/examples/DevelopersGuide/Components/CppComponent

and we can use the makefile of this component.

Documentation caution.png For a try I use the first solution but never make it working. There was probably a problem with registry. If a second program manage registry as in the counter example, I am unable to see the component correctly.

It works when using the makefile of CppComponent renaming the IDL file and cxx file in Makefile as mentioned below.

To do : to go further with the registry problem !!!


Starting with the CppComponent example

To explain how to construct our component I will start from the SDK example in <OOo_SDK>/examples/DevelopersGuide/Components/CppComponent directory. First copy this folder in <OOo_SDK>/examples/DevelopersGuide/Components/MyComponent for instance. The point is to keep a subdirectory of <OOo_SDK>/examples/DevelopersGuide/Components to minimize the change in makefile but every name can be used.

CppComponent.uno.xml File

In your new <OOo_SDK>/examples/DevelopersGuide/Component/MyComponent folder, you will find a file CppComponent.uno.xml. Open it and replace

 <type>my_module.XSomething</type>
 <type>my_module.MyService1</type>
 <type>my_module.MyService2</type>

by

 <type>foo.XCountable</type>
 <type>foo.Counter</type>

This is the corresponding counter service and interface I want to add to OpenOffice.org.

Makefile File

In the original Makefile, replace

 
  IDLFILES = some.idl

by

  IDLFILES = Counter.idl

and replace also

  CXXFILES = service1_impl.cxx \
	service2_impl.cxx

by

  CXXFILES = service_impl.Last.OK.cxx 
  #	service2_impl.cxx

where you adapt your own source file name.

IDL file

You have no change to do in the IDL file for Counter example.

Counter C++ Source File

To access the counter interface in Basic, you require the same IDL names as in Listing 6 ; we give now the complete C++ counter program.

//Listing 9 The complete Counter Program
// C++
// counter.cxx
/***************************************************************************************************
 ***************************************************************************************************
 *
 * service implementation:	 foo.Counter
 * exported interfaces:		 foo.XCounter
 *
 * simple example component implementing a counter
 *
 ***************************************************************************************************
 **************************************************************************************************/
#ifndef _RTL_USTRING_HXX_
#include <rtl/ustring.hxx>
#endif
 
#ifndef _CPPUHELPER_QUERYINTERFACE_HXX_
#include <cppuhelper/queryinterface.hxx> // helper for queryInterface() impl
#endif
#ifndef _CPPUHELPER_FACTORY_HXX_
#include <cppuhelper/factory.hxx> // helper for component factory
#endif
// New
#include <cppuhelper/implbase2.hxx> // "2" implementing three interfaces
#include <cppuhelper/implementationentry.hxx>
 
// generated c++ interfaces
#ifndef _COM_SUN_STAR_LANG_XSINGLESERVICEFACTORY_HPP_
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XMULTISERVICEFACTORY_HPP_
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#endif
#ifndef _COM_SUN_STAR_LANG_XSERVICEINFO_HPP_
#include <com/sun/star/lang/XServiceInfo.hpp>
#endif
#ifndef _COM_SUN_STAR_REGISTRY_XREGISTRYKEY_HPP_
#include <com/sun/star/registry/XRegistryKey.hpp>
#endif
#ifndef _FOO_XCOUNTABLE_HPP_
#include <foo/XCountable.hpp>
#endif
 
#define SERVICENAME "foo.Counter"
#define IMPLNAME	"com.sun.star.comp.example.cpp.Counter"
 
using namespace ::rtl;
using namespace ::osl;
using namespace ::cppu;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::registry;
using namespace ::foo;
//==================================================================================================
 
namespace my_sc_impl
{
static Sequence< OUString > getSupportedServiceNames_MyCounterImpl()
{
	static Sequence < OUString > *pNames = 0;
	if( ! pNames )
	{
//		MutexGuard guard( Mutex::getGlobalMutex() );
		if( !pNames )
		{
			static Sequence< OUString > seqNames(1);
			seqNames.getArray()[0] = OUString(RTL_CONSTASCII_USTRINGPARAM(SERVICENAME));
			pNames = &seqNames;
		}
	}
	return *pNames;
}
 
static OUString getImplementationName_MyCounterImpl()
{
	static OUString *pImplName = 0;
	if( ! pImplName )
	{
//		MutexGuard guard( Mutex::getGlobalMutex() );
		if( ! pImplName )
		{
			static OUString implName( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
			pImplName = &implName;
		}
	}
	return *pImplName;
}
 
// New
class MyCounterImpl : public ::cppu::WeakImplHelper2<
      XCountable, XServiceInfo >
{
	// to obtain other services if needed
	Reference< XMultiServiceFactory > m_xServiceManager;
	sal_Int32 m_nCount;	
public:
    // XServiceInfo	implementation
    virtual OUString SAL_CALL getImplementationName(  ) throw(RuntimeException);
    virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) throw(RuntimeException);
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames(  ) throw(RuntimeException);
    static Sequence< OUString > SAL_CALL getSupportedServiceNames_Static(  );
 
	// XCountable implementation
	virtual sal_Int32 SAL_CALL getCount() throw (RuntimeException)
		{ return m_nCount; }
	virtual void SAL_CALL setCount( sal_Int32 nCount ) throw (RuntimeException)
		{ m_nCount = nCount; }
	virtual sal_Int32 SAL_CALL increment() throw (RuntimeException)
		{ return (++m_nCount); }
	virtual sal_Int32 SAL_CALL decrement() throw (RuntimeException)
		{ return (--m_nCount); }
};
 
//*************************************************************************
OUString SAL_CALL MyCounterImpl::getImplementationName(  )
	throw(RuntimeException)
{
	return OUString( RTL_CONSTASCII_USTRINGPARAM(IMPLNAME) );
}
 
//*************************************************************************
sal_Bool SAL_CALL MyCounterImpl::supportsService( const OUString& ServiceName )
	throw(RuntimeException)
{
	Sequence< OUString > aSNL = getSupportedServiceNames();
	const OUString * pArray = aSNL.getArray();
	for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
		if( pArray[i] == ServiceName )
			return sal_True;
	return sal_False;
}	
 
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames(  )
	throw(RuntimeException)
{
	return getSupportedServiceNames_Static();
}
 
//*************************************************************************
Sequence<OUString> SAL_CALL MyCounterImpl::getSupportedServiceNames_Static(  )
{
	OUString aName( RTL_CONSTASCII_USTRINGPARAM(SERVICENAME) );
	return Sequence< OUString >( &aName, 1 );
}
 
/**
 * Function to create a new component instance; is needed by factory helper implementation.
 * @param xMgr service manager to if the components needs other component instances
 */
//***********NEW
Reference< XInterface > SAL_CALL MyCounterImpl_create(
    Reference< XComponentContext > const & xContext )
    SAL_THROW( () )
{
    return static_cast< XTypeProvider * >( new MyCounterImpl() );
}
 
}
//##################################################################################################
//#### EXPORTED ####################################################################################
//##################################################################################################
 
/* New ****/
namespace my_sc_impl
{
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
    {
        MyCounterImpl_create, getImplementationName_MyCounterImpl,
        getSupportedServiceNames_MyCounterImpl, ::cppu::createSingleComponentFactory,
        0, 0
    },
    { 0, 0, 0, 0, 0, 0 }
};
}
 
extern "C"
{
void SAL_CALL component_getImplementationEnvironment(
    sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv )
{
    *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
sal_Bool SAL_CALL component_writeInfo(
    XMultiServiceFactory * xMgr, XRegistryKey * xRegistry )
{
    return ::cppu::component_writeInfoHelper(
        xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
void * SAL_CALL component_getFactory(
    sal_Char const * implName, XMultiServiceFactory * xMgr,
    XRegistryKey * xRegistry )
{
    return ::cppu::component_getFactoryHelper(
        implName, xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
}
Documentation note.png The use of helper (::cppu::WeakImplHelper2) provide a default class constructor. If you need a constructor managing a service manager or better a context, you have rewrite a corresponding constructor. See also obtaining a context, difference between helper and not helper and also Daniel Bölzle's tutorial where helper is used with explicit constructor.

TestCppComponent.cxx File

In your directory you have a file named TestCppComponent.cxx which doesn't work anymore because written for an other service. You can set all the "main()" content in comment. Then your counter check could not be done with a command :

make TestCppComponent.run

but only with command

make SimpleComponent.odt.load

and you have to modify the OOoBasic program of SimpleComponent.odt document as shown below. We will learn how to modify TestCppComponent.cxx later.

OOoBasic program for a test

The OOoBasic program to test this example is as follows:

'Listing 10 OOoBasic test Program
REM  *****  BASIC  *****
Sub Main
	Dim oSimpleComponent
	oSimpleComponent = CreateUnoService( "foo.Counter" )
	oSimpleComponent.setCount(5)
	print oSimpleComponent.getCount()
	oSimpleComponent.increment()
	print oSimpleComponent.getCount()
	XRay oSimpleComponent
End Sub

Inspecting the new created service with XRay shows :

properties

---- Object internal name : com.sun.star.comp.example.cpp.Counter
Count                     long          
ImplementationName        string 
SupportedServiceNames  ]  string               
Types                     []type                 
ImplementationId          []byte                 
Dbg_Methods               string       
Dbg_Properties            string       
Dbg_SupportedInterfaces   string       

methods

--- Object internal name : com.sun.star.comp.example.cpp.Counter
queryInterface            ( aType as type )                
acquire                   (  ) 
release                   (  ) 
getCount                  (  )                             
setCount                  ( nCount as long ) 
increment                 (  )                             
decrement                 (  )                             
getImplementationName     (  )                             
supportsService           ( ServiceName as string )        
getSupportedServiceNames  (  )                             
getTypes                  (  )                             
getImplementationId       (  )                             
queryAdapter              (  )

You see now the counter methods in OOoBasic (XRay is a OOoBasic tool)

Documentation note.png To conlude I can mention that XRay tool allow you to make the counter working with XRaying the "increment" method for instance. Java inspector Java allows also the call of "setCount" method which ask a parameter.

The use of Java inspector could be done with the OOoBasic program :

'Listing 10b Programme OOoBasic de test
REM  *****  BASIC  *****
Dim oSimpleComponent
	oSimpleComponent = CreateUnoService( "foo.Counter" )
	oInspector = createUnoService("org.openoffice.InstanceInspector")
	oInspector.inspect(oSimpleComponent, "MyCounter")
End Sub
Documentation note.png Please note that the tutorial (from Daniel Bölzle) here presents a scriptable counter too but using less interfaces (cppu::WeakImplHelper1< XCountable > instead of cppu::WeakImplHelper2< XCountable, XServiceInfo >) :
// C++ Daniel Bölzle
....
class MyCounterImpl : public cppu::WeakImplHelper1< XCountable >
{
	// to obtain other services if needed
	Reference< XMultiServiceFactory > _xServiceManager;
 
	sal_Int32 _nCount;
 
	Reference< XText > _xText;
	void dump( sal_Int32 nValue ) const;
 
public:
	MyCounterImpl( const Reference< XMultiServiceFactory > & xServiceManager );
	virtual ~MyCounterImpl();
 
	// XCountable implementation
	virtual sal_Int32 SAL_CALL getCount() throw (RuntimeException);
	virtual void SAL_CALL setCount( sal_Int32 nCount ) throw (RuntimeException);
	virtual sal_Int32 SAL_CALL increment() throw (RuntimeException);
	virtual sal_Int32 SAL_CALL decrement() throw (RuntimeException);
};
...

First Variation : a Counter with Attribute

A little variation on the counter component. I decide to use the attribute possibility in an IDL file. Bear in mind an attribute is automatically accessed with set/get methods. These methods are automatically created. We have then to remove the corresponding methods from the IDL file. My Counter IDL file becomes then :

//Listing 11 Our new IDL File
//IDL
#include <com/sun/star/uno/XInterface.idl>
//#include <com/sun/star/lang/XInitialization.idl>
 
module foo
{
 
interface XCountable : com::sun::star::uno::XInterface
{
	// long getCount(); ************* generee automatiquement
	// void setCount( [in] long nCount ); ********** generee automatiquement
    [attribute] long Count;
	long increment();
	long decrement();
};
 
service Counter
{
    interface XCountable;
};
};

How is this new counter seen with OooBasic ? We don't see getCount and setCount anymore ! That doesn't mean they don't exist anymore. To prove their existence we program this OooBasic program and run it successfully.

'Listing 12 
REM  *****  BASIC  *****
 
Sub Main
	Dim oSimpleComponent
	oSimpleComponent = CreateUnoService( "foo.counter" )
	oSimpleComponent.Count = 5
	print oSimpleComponent.Count
	oSimpleComponent.increment()
	print oSimpleComponent.Count
	XRay.XRay oSimpleComponent
End Sub

XRaying this object gives us : properties

--- Object internal name : com.sun.star.comp.example.cpp.Counter
Count                     long                    
ImplementationName        string       <...>     pseudo-prop, read-only 
SupportedServiceNames     []string               pseudo-prop, read-only 
Types                     []type                 pseudo-prop, read-only 
ImplementationId          []byte                 pseudo-prop, read-only 
Dbg_Methods               string       <...>     basic prop, read-only
Dbg_Properties            string       <...>     basic prop, read-only
Dbg_SupportedInterfaces   string       <...>     basic prop, read-only  

methods

--- Object internal name : com.sun.star.comp.example.cpp.Counter
queryInterface            ( aType as type )                AS variant 
acquire                   (  ) 
release                   (  ) 
increment                 (  )                             AS long 
decrement                 (  )                             AS long 
getImplementationName     (  )                             AS string 
supportsService           ( ServiceName as string )        AS boolean 
getSupportedServiceNames  (  )                             AS []string 
getTypes                  (  )                             AS []type 
getImplementationId       (  )                             AS []byte 
queryAdapter              (  )                             AS object 
Documentation note.png Don't change the C++ source code source of preceding example to make this counter with attribute working. I mean you have a "getCount()" and "setCount()" even if these functions are not in IDL file. If you XRay the Count property, you see the counter value, even if the Count field doesn't exist (in fact the implementation uses an other field m_nCount which is of type sal_Int32).

Second Variation : using the Service Manager in the Counter

You can find a tutorial describing a scriptable counter example (from Daniel Bölzle) here where the counter is opening a OOoCalc document, that means it uses OOo services and interfaces.

The Service Manager management in counter component has been postponed later here.

Constructing the scriptable Counter without Helper

We have seen how using helper avoid any interfaces coding. We want construct the counter but without helper, that means we will write everything we need. At the moment see the Developper's Guide.

TODO: There's more to come here ...


Windows specific problems

Documentation windows.png This section relates problems you can encounter when linking a component to a Unix-like library. Bear in mind that components are always constructed with Visual C++. But there is a lot of external free libraries which are Unix-like and then can only be constructed under cygwin or something similar (MinGw). Porting them under VC++ is a too heavy task. I want to give an example to show a way I spent few days to discover. I will not construct a component in the example below but only show how to construct a binary executable with VC++ using a dll constructed under cygwin.


Our example

Our example is again based on the counter. I give the complete source code of the example. The include file is as follow :

//Listing 20 counter.hxx
// C++
class MyCounterImpl {
        int m_nCount;
public:
        MyCounterImpl();
        ~MyCounterImpl();
        int getCount();
        void setCount( int nCount );
        int increment();
        int decrement() ;
};

and the corresponding C++ source code :

// counter.cxx Listing 21
#include <stdio.h>
#include "counter.hxx"
MyCounterImpl::MyCounterImpl()
        : m_nCount( 0 )
        { printf( "< MyCounterImpl ctor called >\n" ); }
MyCounterImpl::~MyCounterImpl()
        { printf( "< MyCounterImpl dtor called >\n" ); }
int MyCounterImpl::getCount()
        { return m_nCount; }
void MyCounterImpl::setCount( int nCount )
        { m_nCount = nCount; }
int MyCounterImpl::increment()
        { return (++m_nCount); }
int MyCounterImpl::decrement()
        { return (--m_nCount); }
 
extern "C" void essai1(int *a){
  MyCounterImpl count;
  count.setCount( *a);
  count.increment();
  *a=count.getCount();
}

Note I have added an external C function named "essai1". The point is I cannot use directly the class because C++ compilers give different names to the member functions when compiling.

Documentation note.png VC++ adds a "_" as a prefix of the function name and a "@n" as suffix with n the size in octets of the parameter(s). In our example "_essai1@4" is exported.

And now the listing of the main part :

//Listing 22 Main Program
//c++
//countermain.cxx
#include <stdio.h>
 
extern "C" void essai1(int *a);
// the __declspec(dllimport) doesn't work
//__declspec(dllimport) void essai1(void);
 
int main(int argc, char **argv)
{   int b=12;
    essai1(&b);
    printf("expected result : 13, computed result : %d\n",b);
    return 0;
}

We want now describe the compilation phases.

Compilation

Lsiting 21 is compiled under cygwin and transformed as a true dll.

gcc -c counter.cxx -o counter.o
dlltool --export-all --output-def counter.def counter.o
dllwrap counter.o --dllname counter.dll --def counter.def

At this point you have a counter.dll file. Copy this file and counter.def in the directory you use with VC++.

Documentation windows.png If you can directly give a dynamic library in a Linux compiling process it's not the case under Windows. Using a dll is done with a static library file created with lib tool.


The compilation under VC++ is as follow

lib /machine:i386 /def:counter.def
cl countermain.cxx counter.lib

It's done : if your cygwin1.dll is available you only launch countermain and see what happens.

MakeFile

Documentation caution.png

The chapter on MakeFile is under construction but we hope to add an example of makefile for this kind of situation in the future.


Going further with components and dialog

Developer's Guide states it is possible to construct a component with a dialog that has been created with the Dialog Editor integrated in the OpenOffice.org Basic IDE.

In the following chapters we will go on to study dialog and components.

Tip.png Dialogs at run time are probably possible to be created in a component. Installing Event listener for buttons is probably possible with the Component helper, but what a hard task !


See also Generic UNO Interfaces for complex toolbar controls.

Home Page

HomePageCpp.png Return to Document Home page

See also

Personal tools