Uno/Cpp/Tutorials/component tutorial

From Apache OpenOffice Wiki
< Uno‎ | Cpp‎ | Tutorials
Jump to: navigation, search

Introduction

This tutorial is a general example of how to write a UNO component and how to use it in applications, for example StarOffice or OpenOffice.org. This example is written in C++, however differences for implementing the component in Java will be mentioned.

The component is a basic counter, whose value can be set, read, incremented and decremented.

Interfaces

The first step, in writing any component (in almost any language environment) is to specify one or more interfaces that the component must implement.

Interfaces are the key to hiding implementation details in any modern development environment. They are contracts between a client that wants to use a components' functionality and the component (serving this functionality). In UNO terminology, UNO components are called services.

Interfaces separate the specific implementation (there can more than one for a service) from the usage. The client using the component does not have any insight how the component is implemented.

Interfaces are specified using an Interface definition language (IDL). UNO uses UNO-IDL as the interface definition language. An interface for a Counter might look like this:

file counter.idl:

#include <com/sun/star/uno/XInterface.idl>
module foo
{
   /**
     * Interface to count things.
     */
    [ uik(3806AFF0-75A0-11d3-87B300A0-24494732), ident("XCountable", 1.0) ]
    interface XCountable : com::sun::star::uno::XInterface
    {
        long getCount();
        void setCount( [in] long nCount );
        long increment();
        long decrement();
    };
};

Any interface that is specified is derived from XInterface, the basic interface in UNO. The XInterface has methods for lifetime control of the interface (acquire() and release()) and the ability to query for further interfaces of the UNO object (queryInterface()). Once you have an interface of an object, you can query for any others the object provides. If there are no acquired interfaces (references) left on an UNO object, the object might disappear. Interfaces (and services) can be grouped in modules to avoid pollution of the global namespace.

Services

Any UNO component (service) exports one or more interfaces that the clients are using. All services are specified using the service directive in an IDL file:

module foo 
 
{
    service Counter
    {
        // exported interfaces:
        interface XCountable;
    };
};

The service declaration introduces a service called foo.Counter which supports the XCountable interface.

There are some more IDL features, e.g. attributes, structs, enums, that are omitted at this time. All IDL declarations are put into a typelibrary file (rdb file). This speeds up the back end generation of the language specific files.

To implement the interfaces, the appropriate interface code for the implementation language has to be generated from the rdb file.

In C++ a tool called cppumaker generates pure abstract classes that the component has to implement. This is a common way to describe interfaces in C++. The Java language directly supports interfaces as a language feature (javamaker will generate Java interfaces).

Implementation

A component that is implemented in C++ is normally packaged in a shared library. This shared lib exports two symbols, which are explained further below. Java implementations are normally packaged in a JAR file. In this case the manifest file identifies a class implementing two methods with similar semantics.

A simple implementation of the counting UNO service foo.Counter might be:

//file foo.cxx:
 
#include <rtl/ustring.hxx>
#include <cppuhelper/implbase1.hxx>
#include <cppuhelper/factory.hxx>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/registry/XRegistryKey.hpp>
#include <foo/XCountable.hpp>
 
using namespace rtl;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace com::sun::star::registry;
using namespace foo;
 
//==================================================================================================
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);
};
//__________________________________________________________________________________________________
MyCounterImpl::MyCounterImpl( const Reference< XMultiServiceFactory > & xServiceManager )
	: _xServiceManager( xServiceManager )
{
	cerr << "< MyCounterImpl ctor called >" << endl;
 
	if (_xServiceManager.is())
	{
		Reference< XComponentLoader > xLoader( _xServiceManager->createInstance(
			L"com.sun.star.frame.Desktop" ), UNO_QUERY );
		if (xLoader.is())
		{
			Reference< XTextDocument > xDoc( xLoader->loadComponentFromURL(
				L"private:scalc/factory", L"_blank", 0, Sequence< PropertyValue >() ), UNO_QUERY );
			if (xDoc.is())
				_xText = xTextDoc->getText();
		}
	}
}
//__________________________________________________________________________________________________
MyCounterImpl::~MyCounterImpl()
{
	cerr << "< MyCounterImpl dtor called >" << endl;
}
//__________________________________________________________________________________________________
void MyCounterImpl::dump( sal_Int32 nValue ) const
{
	if (_xText.is())
		_xText->setValue( nValue );
	else
		cerr << endl << nValue;
}
 
// XCountable implementation
//__________________________________________________________________________________________________
sal_Int32 MyCounterImpl::getCount() throw (RuntimeException)
{
	return _nCount;
}
//__________________________________________________________________________________________________
void MyCounterImpl::setCount( sal_Int32 nCount ) throw (RuntimeException)
{
	_nCount = nCount;
	dump( _nCount );
}
//__________________________________________________________________________________________________
sal_Int32 MyCounterImpl::increment() throw (RuntimeException)
{
	++_nCount;
	dump( _nCount );
	return _nCount;
}
//__________________________________________________________________________________________________
sal_Int32 MyCounterImpl::decrement() throw (RuntimeException)
{
	--_nCount;
	dump( _nCount );
	return _nCount;
}
 
/**
 * 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 > MyCounterImpl_create(
	const Reference< XMultiServiceFactory > & xMgr )
{
	return Reference< XInterface >( new MyCounterImpl( xMgr ) );
}


Documentation caution.png The instanciation of a component has changed since this article has been written. The constructor has to use a context instead of a service manager (see Developer's Guide).


The generated header file declares an abstract C++ class and a function called getCppuType(). Any generated type has its getCppuType() describing the type, for example.

const com::sun::star::uno::Type & SAL_CALL getCppuType( const com::sun::star::uno::Reference< foo::XCountable > * );

describes the XCountable interface. Only the type of the parameter is of importance. By using overloaded getCppuType() functions for any type, it is possible to get runtime type information. It is also possible to use little helpers like a template queryInterface() function used for the implementation of XInterface::queryInterface().

So how is the UNO component instantiated? As mentioned above, four symbols are exported by the shared library, providing four functions: component_getDescriptionFunc(), component_getImplementationEnvironment(), component_writeInfo() and component_getFactory().

The first is called to get a description of the component. This function returns a XML formatted string which describes the component. This function could be generated from the XML description with the xml2cmp tool. Each component should provide such a XML description. The second is called from a loader service to get information about the used environment of the component. The third is called whenever the component is registered in some registry file and the latter is called to obtain a factory to get instances of the component.

They are typically implemented as follows:

/**
 * This function returns the name of the used environment.
 * @param ppEnvTypeName name of the environment
 * @param ppEnv could be point to a special environment, this parameter is normally set to null
 */
extern "C" void SAL_CALL component_getImplementationEnvironment(
    const sal_Char ** ppEnvTypeName, uno_Environment ** ppEnv )
{
    *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
 
/**
 * This function creates an implementation section in the registry and another subkey
 * for each supported service.
 * @param pServiceManager generic uno interface providing a service manager
 * @param pRegistryKey generic uno interface providing registry key to write
 */
extern "C" sal_Bool SAL_CALL component_writeInfo( void* pServiceManager, void* pRegistryKey )
{
    if (pRegistryKey)
    {
        try
        {
           Reference< XRegistryKey > xNewKey(
              reinterpret_cast< XRegistryKey * >( pRegistryKey )->createKey(
                 OUString( RTL_CONSTASCII_USTRINGPARAM("/foo.MyCounterImpl/UNO/SERVICES") ) ) );
           xNewKey->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM("foo.Counter") ) );
           return sal_True;
        }
    }
    return sal_False;
}

component_writeInfo() will write information about all service implementations that are in the shared library. Each service implementation is registered under its implementation name in the implementation section, followed by its service name. The hierarchical structure of the registry is as follows:

/IMPLEMENTATIONS/
    /foo.MyCounterImpl                    // implementation name
        /UNO
            /SERVICES
                /foo.Counter        // service name
    /bar.AnotherCounterImpl           // implementation name
       /UNO
           /SERVICES
               /foo.Counter        // service name
    ...
/**
 * 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 > MyCounterImpl_create( const Reference< XMultiServiceFactory > & xMgr )
{
    return Reference< XInterface >( new MyCounterImpl( xMgr ) );
}
 
/**
 * This function is called to get service factories for an implementation.
 * @param pImplName name of implementation
 * @param pServiceManager generic uno interface providing a service manager to instantiate components
 * @param pRegistryKey registry data key to read and write component persistent data
 * @return a component factory (generic uno interface)
 */
extern "C" void * SAL_CALL component_getFactory(
    const sal_Char * pImplName, void * pServiceManager, void * pRegistryKey )
{
    void * pRet = 0;
    // which implementation is required?
    if (pServiceManager && rtl_str_compare( pImplName, "foo.MyCounterImpl" ))
    {
        rtl::OUString aServiceName( RTL_CONSTASCII_USTRINGPARAM("foo.Counter") );
        Reference< XSingleServiceFactory > xFactory(
           cppu::createSingleFactory( // helper function from cppuhelper lib
           reinterpret_cast< XMultiServiceFactory * >( pServiceManager ),
           OUString( RTL_CONSTASCII_USTRINGPARAM("foo.MyCounterImpl") ),
           MyCounterImpl_create,
           Sequence< rtl::OUString >( &aServiceName, 1 ) ) );
        if (xFactory.is())
        {
           xFactory.acquire();
           pRet = xFactory.get();
        }
    }
    return pRet;
}

component_getFactory() demands a certain implementation from the shared library. The returned uno interface is a component factory that is used to produce service instances of the component implementation. The first parameter identifies the name of the required implementation. This name must correspond to the registered one from component_writeInfo(). The second parameter provides a service manager instance, components can use this for getting further components. The last parameter provides a registry key to the implementation section of the component, so it can read and write persistent data to the registry, e.g. for booting.

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)

Attentions after Passive Component Registration

1. create a new .component file,for example, create scfilt.component in sc/util,

<component loader="com.sun.star.loader.SharedLibrary"
 
xmlns="http://openoffice.org/2010/uno-components">
 
<implementation name="com.sun.star.comp.ExcelFilterExport">
 
<service name="com.sun.star.document.ExportFilter"/>
 
</implementation>
 
</component>

2. add the new component in the make file,for example, add scfilt.component,the code should like this:

ALLTAR : $(MISC)/sc.component $(MISC)/scfilt.component $(MISC)/scd.component $(MISC)/vbaobj.component
 
$(MISC)/sc.component .ERRREMOVE : $(SOLARENV)/bin/createcomponent.xslt \
        sc.component
    $(XSLTPROC) --nonet --stringparam uri \
        '$(COMPONENTPREFIX_BASIS_NATIVE)$(SHL1TARGETN:f)' -o $@ \
        $(SOLARENV)/bin/createcomponent.xslt sc.component
 
$(MISC)/scfilt.component .ERRREMOVE : $(SOLARENV)/bin/createcomponent.xslt \
        scfilt.component
    $(XSLTPROC) --nonet --stringparam uri \
        '$(COMPONENTPREFIX_BASIS_NATIVE)$(SHL6TARGETN:f)' -o $@ \
        $(SOLARENV)/bin/createcomponent.xslt scfilt.component

3. deliver the .component file.for example, in sc/prj/d.lst

..\%__SRC%\misc\scfilt.component %_DEST%\xml%_EXT%\scfilt.component

4. if your service implement is a DLL,then add the module component to the list in

postprocess/packcomponets/makefile.mk,for example, add 'scfilt'to the list of my_components.


Author: Daniel Bölzle ($Date: 2004/11/17 12:41:00 $) Copyright 2001 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA.

See also

Personal tools