Writing a simple UNO component |
Contents
Interfaces
Services
Implementation
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 ) ); }
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.
Author: Daniel Bölzle ($Date: 2004/11/17 12:41:00 $) |