[ Previous document | Content Table | Next document ]
OpenOffice.org can be extended by UNO components. UNO components are shared libraries or jar files with the ability to instantiate objects that can integrate themselves into the UNO environment. A UNO component can access existing features of OpenOffice.org, and it can be used from within OpenOffice.org through the object communication mechanisms provided by UNO.
OpenOffice.org provides many entry points for these extensions.
Arbitrary objects written in Java or C++ can be called from the user interface, display their own GUI, and work with the entire application.
Calc Add-Ins can be used to create new formula sets that are presented in the formula autopilot.
Chart Add-Ins can insert new Chart types into the charting tool.
New database drivers can be installed into the office to extend data access.
Entire application modules are exchangeable, for instance the linguistics module.
It is possible to create new document types and add them to the office. For instance, a personal information manager could add message, calendar, task and journal document components, or a project manager could support a new project document.
Developers can leverage the OpenOffice.org XML file format to read and write new file formats through components.
From OpenOffice.org 1.1.0 there is comprehensive support for component extensions. The entire product cycle of a component is now covered:
The design and development of components has been made easier by adding wizards for components to the NetBeans IDE. You can find more detailed info under http://wiki.services.openoffice.org/wiki/OpenOffice_NetBeans_Integration.
Components can integrate themselves into the user interface, using simple configuration files. You can add new menus, toolbar items, and help items for a component simply by editing XML configuration files.
Components are deployed with the Extension Manager. See chapter 5 Extensions.
Last but not least, this is not the only way to add features to the office. Learning how to write components and how to use the OpenOffice.org API at the same time teaches you the techniques used in the OpenOffice.org code base, thus enabling you to work with the existing OpenOffice.org source code, extend it or introduce bug fixes.
Components are the basis for all of these extensions. This chapter teaches you how to write UNO components. It assumes that you have at least read the chapter 2 First Steps and—depending on your target language—the section about the Java or C++ language binding in 3 Professional UNO.
OpenOffice.org Software Development Kit (SDK)
The SDK provides a build environment for your projects, separate from the OpenOffice.org build environment. It contains the necessary tools for UNO development, C and C++ libraries, JARs , UNO type definitions and example code. But most of the necessary libraries and files are shared with an existing OpenOffice.org installation which is a prerequisite for a SDK.
The SDK development tools (executables) contained in the SDK are used in the following chapter. Become familiar with the following table that lists the executables from the SDK. These executables are found in the platform specific bin folder of the SDK installation. In Windows, they are in the folder <SDK>\windows\bin, on Linux they are stored in <SDK>/linux/bin and on Solaris in <SDK>/solaris/bin.
|
Executable |
Description |
|
idlc |
The UNOIDL compiler that creates binary type description files with the extension .urd for registry database files. |
|
idlcpp |
The idlc preprocessor used by idlc. |
|
cppumaker |
The C++ UNO maker that generates headers with UNO types mapped from binary type descriptions to C++ from binary type descriptions. |
|
javamaker |
Java maker that generates interface and class definitions for UNO types mapped from binary type descriptions to Java from binary type descriptions. |
|
xml2cmp |
XML to Component that can extract type names from XML object descriptions for use with cppumaker and javamaker, creates functions. |
|
regmerge |
The registry merge that merges binary type descriptions into registry files. |
|
regcomp |
The register component that tells a registry database file that there is a new component and where it can be found. |
|
unopkg |
The command line tool of the extension manager. |
|
regview |
The registry view that outputs the content of a registry database file in readable format. |
|
autodoc |
The automatic documentation tool that evaluates Javadoc style comments in idl files and generates documentation from them. |
|
rdbmaker |
The registry database maker that creates registry files with selected types and their dependencies. |
|
uno |
The UNO executable. It is a standalone UNO environment which is able to run UNO components supporting the com.sun.star.lang.XMain interface, one possible use is: |
GNU Make
The makefiles in the SDK assume that the GNU make is used. Documentation for GNU make command line options and syntax are available at www.gnu.org. In Windows, not every GNU make seems stable, notably some versions of Cygwin make were reported to have problems with the SDK makefiles. Other GNU make binaries, such as the one from unixutils.sourceforge.net work well even on the Windows command line. The package UnxUtils comes with a zsh shell and numerous utilities, such as find, sed. To install UnxUtils, download and unpack the archive, and add <UnxUtils>\usr\local\wbin to the PATH environment variable. Now launch sh.exe from <UnxUtils>\bin and issue the command make from within zsh or use the Windows command line to run make. For further information about zsh, go to zsh.sunsite.dk.
Component development does not necessarily start with the declaration of new interfaces or new types. Try to use the interfaces and types already defined in the OpenOffice.org API. If existing interfaces cover your requirements and you need to know how to implement them in your own component, go to section 4.3 Writing UNO Components - Component Architecture. The following describes how to declare your own interfaces and other types you might need.
UNO uses its own meta language UNOIDL (UNO Interface Definition Language) to specify types. Using a meta language for this purpose enables you to generate language specific code, such as header files and class definitions, to implement objects in any target language supported by UNO. UNOIDL keeps the foundations of UNO language independent and takes the burden of mechanic language adaptation from the developer's shoulders when implementing UNO objects.
To define a new interface, service or other entity, write its specification in UNOIDL, then compile it with the UNOIDL compiler idlc. After compilation, merge the resulting binary type description into a type library that is used during the make process to create necessary language dependent type representations, such as header or Java class files. The chapter 3 Professional UNO provides the various type mappings used by cppumaker and javamaker in the language binding sections. Refer to the section 4.9.2 Writing UNO Components - Deployment Options for Components - Background: UNO Registries - UNO Type Library for details about type information in the registry-based type library.
|
|
When writing your own specifications, please consult the chapter A IDL Design Guide which treats design principles and conventions used in API specifications. Follow the rules for universality, orthogonality, inheritance and uniformity of the API as described in the Design Guide. |
There are similarities between C++, CORBA IDL and UNOIDL, especially concerning the syntax and the general usage of the compiler. If you are familiar with reading C++ or CORBA IDL, you will be able to understand much of UNOIDL, as well.
As a first example, consider the IDL specification for the com.sun.star.bridge.XUnoUrlResolver interface. An idl file usually starts with a number of preprocessor directives, followed by module instructions and a type definition:
#ifndef __com_sun_star_bridge_XUnoUrlResolver_idl__
#define __com_sun_star_bridge_XUnoUrlResolver_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/IllegalArgumentException.idl>
#include <com/sun/star/connection/ConnectionSetupException.idl>
#include <com/sun/star/connection/NoConnectException.idl>
module com { module sun { module star { module bridge {
/** service <type scope="com::sun::star::bridge">UnoUrlResolver</type>
implements this interface.
*/
published interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
// method com::sun::star::bridge::XUnoUrlResolver::resolve
/** resolves an object, on the UNO URL.
*/
com::sun::star::uno::XInterface resolve( [in] string sUnoUrl )
raises (com::sun::star::connection::NoConnectException,
com::sun::star::connection::ConnectionSetupException,
com::sun::star::lang::IllegalArgumentException);
};
}; }; }; };
#endif
We will discuss this idl file step by step below, and we will write our own UNOIDL specification as soon as possible. The file specifying com.sun.star.bridge.XUnoUrlResolver is locatedin the idl folder of your SDK installation, <SDK>/idl/com/sun/star/bridge/XUnoUrlResolver.idl.
UNOIDL definition file names have the extension . idl by convention. The descriptions must use the US ASCII character set without special characters and separate symbols by whitespace, i.e. blanks, tabs or linefeeds.
Just like a C++ compiler, the UNOIDL compiler idlc can only use types it already knows. The idlc knows 15 simple types such as boolean, int or string (they are summarized below). Whenever a type other than a simple type is used in the idl file, its declaration has to be included first. For instance, to derive an interface from the interface XInterface, include the corresponding file XInterface.idl. Including means telling the preprocessor to read a given file and execute the instructions found in it.
#include <com/sun/star/uno/XInterface.idl> // searched in include path given in -I parameter
#include "com/sun/star/uno/XInterface.idl" // searched in current path, then in include path
There are two ways to include idl files. A file name in angled brackets is searched on the include path passed to idlc using its -I option. File names in double quotes are first searched on the current path and then on the include path.
The XUnoUrlResolver definition above includes com.sun.star.uno.XInterface and the three exceptions thrown by the method resolve(), com.sun.star.lang.IllegalArgumentException, com.sun.star.connection.ConnectionSetupException and com.sun.star.connection.NoConnectException.
In OpenOffice.org 2.0, it is no longer necessary to explicitly state that an interface type derives from XInterface—if an interface type derives from no other interface type, it is implicitly taken to derive from XInterface. However, even in such situations it is important to explicitly include the file XInterface.idl.
Furthermore, to avoid warnings about redefinition of already included types, use #ifndef and #define as shown above. Note how the entire definition for XUnoUrlResolver is enclosed between #ifndef and #endif. The first thing the preprocessor does is to check if the flag __com_sun_star_bridge_XUnoUrlResolver_idl__ has already been defined. If not, the flag is defined and idlc continues with the definition of XUnoUrlResolver.
Adhere to the naming scheme for include flags used by the OpenOffice.org developers: Use the file name of the IDL file that is to be included, add double underscores at the beginning and end of the macro, and replace all slashes and dots by underscores.
For other preprocessing instructions supported by idlc refer to Bjarne Stroustrup: The C++ Programming Language.
To avoid name clashes and allow for a better API structure, UNOIDL supports naming scopes. The corresponding instruction is module:
module mymodule {
};
Instructions are only known inside the module mymodule for every type defined within the pair of braces of this module {} . Within each module, the type identifiers are unique. This makes an UNOIDL module similar to a Java package or a C++ namespace.
Modules may be nested. The following code shows the interface XUnoUrlResolver contained in the module bridge that is contained in the module star, which is in turn contained in the module sun of the module com.
module com { module sun { module star { module bridge {
// interface XUnoUrlResolver in module com::sun::star::bridge
}; }; }; };
It is customary to write module names in lower case letters. Use your own module hierarchy for your IDL types. To contribute code to OpenOffice.org, use the org::openoffice namespace or com::sun::star. Discuss the name choice with the leader of the API project on www.openoffice.org to add to the latter modules. The com::sun::star namespace mirrors the historical roots of OpenOffice.org in StarOffice and will probably be kept for compatibility purposes.
Types defined in UNOIDL modules have to be referenced using full-type or scoped names, that is, you must enter all modules your type is contained in and separate the modules by the scope operator ::. For instance, to reference XUnoUrlResolver in another idl definition file, write com::sun::star::bridge::XUnoUrlResolver.
Besides, modules have an advantage when it comes to generating language specific files. The tools cppumaker and javamaker automatically create subdirectories for every referenced module, if required. Headers and class definitions are kept in their own folders without any further effort.
One potential source of confusion is that UNOIDL and C++ use “::” to separate the individual identifiers within a name, whereas UNO itself (e.g., in methods like com.sun.star.lang.XMultiComponentFactory:createInstanceWithContext) and Java use “.”.
Before we can go about defining our first interface, you need to know the simple types you may use in your interface definition. You should already be familiar with the simple UNO types from the chapters 2 First Steps and 3 Professional UNO. Since we have to use them in idl definition files, we repeat the type keywords and their meaning here.
|
simple UNO type |
Type description |
|
char |
16-bit unicode character type |
|
boolean |
boolean type; true and false |
|
byte |
8-bit ordinal integer type |
|
short |
signed 16-bit ordinal integer type |
|
unsigned short |
unsigned 16-bit ordinal integer type (deprecated) |
|
long |
signed 32-bit ordinal integer type |
|
unsigned long |
unsigned 32-bit integer type (deprecated) |
|
hyper |
signed 64-bit ordinal integer type |
|
unsigned hyper |
unsigned 64-bit ordinal integer type (deprecated) |
|
float |
processor dependent float |
|
double |
processor dependent double |
|
string |
string of 16-bit unicode characters |
|
any |
universal type, takes every simple or compound UNO type, similar to Variant in other environments or Object in Java |
|
void |
Indicates that a method does not provide a return value |
Interfaces describe aspects of objects. To specify a new behavior for the component, start with an interface definition that comprises the methods offering the new behavior. Define a pair of plain get and set methods in a single step using the attribute instruction. Alternatively, choose to define your own operations with arbitrary arguments and exceptions by writing the method signature, and the exceptions the operation throws. We will first write a small interface definition with attribute instructions, then consider the resolve() method in XUNoUrlResolver.
Let us assume we want to contribute an ImageShrink component to OpenOffice.org to create thumbnail images for use in OpenOffice.org tables. There is already a com.sun.star.document.XFilter interface offering methods supporting file conversion. In addition, a method is required to get and set the source and target directories, and the size of the thumbnails to create. It is common practice that a service and its prime interface have corresponding names, so our component shall have an org::openoffice::test::XImageShrink interface with methods to do so through get and set operations.
The attribute instruction creates these operations for the experimental interface definition:
Look at the specification for our XImageShrink interface: (Components/Thumbs/org/openoffice/test/XImageShrink.idl)
#ifndef __org_openoffice_test_XImageShrink_idl__
#define __org_openoffice_test_XImageShrink_idl__
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/awt/Size.idl>
module org { module openoffice { module test {
interface XImageShrink : com::sun::star::uno::XInterface
{
[attribute] string SourceDirectory;
[attribute] string DestinationDirectory;
[attribute] com::sun::star::awt::Size Dimension;
};
}; }; };
#endif
We protect the interface from being redefined using #ifndef, then added #include com.sun.star.uno.XInterface and the struct com.sun.star.awt.Size. These were found in the API reference using its global index. Our interface will be known in the org::openoffice::test module, so it is nested in the corresponding module instructions.
Define an interface using the interface instruction. It opens with the keyword interface, gives an interface name and derives the new interface from a parent interface (also called super interface). It then defines the interface body in braces. The interface instruction concludes with a semicolon.
In this case, the introduced interface is XImageShrink. By convention, all interface identifiers start with an X. Every interface must inherit from the base interface for all UNO interfaces XInterface or from one of its derived interfaces. The simple case of single inheritance is expressed by a colon : followed by the fully qualified name of the parent type. The fully qualified name of a UNOIDL type is its identifier, including all containing modules separated by the scope operator ::. Here we derive from com::sun::star::uno::XInterface directly. If you want to declare a new interface that inherits from multiple interfaces, you do not use the colon notation, but instead list all inherited interfaces within the body of the new interface:
interface XMultipleInheritance {
interface XBase1;
interface XBase2;
};
|
|
UNOIDL allows forward declaration of interfaces used as parameters, return values or struct members. However, an interface you want to derive from must be a fully defined interface. |
After the super interface the interface body begins. It may contain attribute and method declarations, and, in the case of a multiple-inheritance interface, the declaration of inherited interfaces. Consider the interface body of XImageShrink. It contains three attributes and no methods. Interface methods are discussed below.
An attribute declaration opens with the keyword attribute in square brackets, then it gives a known type and an identifier for the attribute, and concludes with a semicolon.
In our example, the string attributes named SourceDirectory and DestinationDirectory and a com::sun::star::awt::Size attribute known as Dimension were defined:
[attribute] string SourceDirectory;
[attribute] string DestinationDirectory;
[attribute] com::sun::star::awt::Size Dimension;
During code generation in Java and C++, the attribute declaration leads to pairs of get and set methods. For instance, the Java interface generated by javamaker from this type description contains the following six methods:
// from attribute SourceDir
public String getSourceDirectory();
public void setSourceDirectory(String _sourcedir);
// from attribute DestinationDir
public String getDestinationDirectory();
public void setDestinationDirectory(String _destinationdir);
// from attribute Dimension
public com.sun.star.awt.Size getDimension();
public void setDimension(com.sun.star.awt.Size _dimension);
As an option, define that an attribute cannot be changed from the outside using a readonly flag. To set this flag, write [attribute, readonly]. The effect is that only a get() method is created during code generation, but not a set() method. Another option is to mark an attribute as bound; that flag is of interest when mapping interface attributes to properties, see 4.5.6 Writing UNO Components - Simple Component in Java - Storing the Service Manager for Further Use and 4.6 Writing UNO Components - C++ Component.
Since OpenOffice.org 2.0, there can be exception specifications for attributes, individually for the operations of getting and setting an attribute:
[attribute] long Age {
get raises (DatabaseException); // raised when retrieving the age from the database fails
set raises (IllegalArgumentException, // raised when the new age is negative
DatabaseException); // raised when storing the new age in the database fails
};
If no exception specification is given, only runtime exceptions may be thrown.
When writing a real component, define the methods by providing their signature and the exceptions they throw in the idl file. Our XUnoUrlResolver example above features a resolve() method taking a UNO URL and throwing three exceptions.
interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
com::sun::star::uno::XInterface resolve( [in] string sUnoUrl )
raises (com::sun::star::connection::NoConnectException,
com::sun::star::connection::ConnectionSetupException,
com::sun::star::lang::IllegalArgumentException);
};
The basic structure of a method is similar to C++ functions or Java methods. The method is defined giving a known return type, the operation name, an argument list in brackets () and if necessary, a list of the exceptions the method may throw. The argument list, the exception clause raises () and an optional [ oneway ] flag preceding the operation are special in UNOIDL.
Each argument in the argument list must commence with one of the direction flags [ in ] , [ out ] or [ inout ] before a known type and identifier for the argument is given. The direction flag specifies how the operation may use the argument:
|
Direction Flags for Methods |
Description |
|
in |
Specifies that t he method shall evaluate the argument as input parameter, but it cannot change it. |
|
out |
Specifies that t he argument does not parameterize the method, instead the method uses the argument as output parameter. |
|
inout |
Specifies that the operation is parameterized by the argument and that the method uses the argument as output parameter as well. |
Try to avoid the [ inout ] and [ out ] qualifiers, as they are awkward to handle in certain language bindings, like the Java language binding. The argument list can be empty. Multiple arguments must be separated by commas.
Exceptions are given through an optional raises () clause containing a comma-separated list of known exceptions given by their full name. The presence of a raises() clause means that only the listed exceptions, com.sun.star.uno.RuntimeException and their descendants may be thrown by the implementation. By specifying exceptions for metnods, the implementer of your interface can return information to the caller, thus avoiding possible error conditions.
If you prepend a [ oneway ] flag to an operation, the operation can be executed asynchronously if the underlying method invocation system does support this feature. For example, a UNO Remote Protocol (URP) bridge is such a system that supports oneway calls. A oneway operation can not have a return value, or out or inout parameters. It must not throw other exceptions than com.sun.star.uno.RuntimeException.
|
|
Although there are no general problems with the specification and the implementation of the UNO oneway feature, there are several API remote usage scenarios where oneway calls cause deadlocks in OpenOffice.org. Therefore it is not recommended to introduce new oneway methods with new OpenOffice.org UNO APIs. |
|
|
You can not override an attribute or a method inherited from a parent interface, that would not make sense in an abstract specification anyway. Furthermore, overloading is not possible. The qualified interface identifier in conjunction with the name of the method creates a unique method name. |
UNOIDL Services combine interfaces and properties to specify a certain functionality. In addition, old-style services can include other services. For these purposes, interface, property and service declarations are used within service specifications. Usually services are the basis for an object implementation, although there are old-style services in the OpenOffice.org API that only serve as foundation or addition to other services, but are not meant to be implemented by themselves.
We are ready to assemble our ImageShrink service. Our service will read image files from a source directory and write shrinked versions of the found images to a destination directory. Our XImageShrink interface offers the needed capabilities, together with the interface com.sun.star.document.XFilter that supports two methods:
boolean filter( [in] sequence< com::sun::star::beans::PropertyValue > aDescriptor)
void cancel()
A new-style service can only encompass one interface, so we need to combine XImageShrink and XFilter in a single, multiple-inheritance interface:
#ifndef __org_openoffice_test_XImageShrinkFilter_idl__
#define __org_openoffice_test_XImageShrinkFilter_idl__
#include <com/sun/star/document/XFilter.idl>
#include <org/openoffice/test/XImageShrink.idl>
module org { module openoffice { module test {
interface XImageShrinkFilter {
interface XImageShrink;
interface com::sun::star::document::XFilter;
};
}; }; };
#endif
The following code shows the ImageShrink service specification: (Components/Thumbs/org/openoffice/test/ImageShrink.idl)
#ifndef __org_openoffice_test_ImageShrink_idl__
#define __org_openoffice_test_ImageShrink_idl__
#include <org/openoffice/test/XImageShrinkFilter.idl>
module org { module openoffice { module test {
service ImageShrink: XImageShrinkFilter;
}; }; };
#endif
Define a service using the service declaration. A new-style service opens with the keyword service, followed by a service name, a colon, the name of the interface supported by the service, and is terminated by a semicolon. The first letter of a service name should be an upper-case letter.
An old-style service is much more complex. It opens with the keyword service, followed by a service name and the service body in braces. The service instruction concludes with a semicolon. The body of a service can reference interfaces and services using interface and service instructions, and it can identify properties supported by the service through [property] instructions.
Interface keywords followed by interface names in a service body indicates that the service supports these interfaces. By default, the interface forces the developer to implement this interface. To suggest an interface for a certain service, prepend an [optional] flag in front of the keyword interface. This weakens the specification to a permission. An optional interface can be implemented. Use one interface declaration for each supported interface or give a comma-separated list of interfaces to be exported by a service. You must terminate the interface instruction using a semicolon.
service instructions in a service body include other services. The effect is that all interface and property definitions of the other services become part of the current service. A service reference can be optional using the [optional] flag in front of the service keyword. Use one declaration per service or a comma-separated list for the services to reference. The service declaration ends with a semicolon.
[property] declaration s describe qualities of a service that can be reached from the outside under a particular name and type. As opposed to interface attributes, these qualities are not considered to be a structural part of a service. Refer to the section 3.3.4 Professional UNO - UNO Concepts - Properties in the chapter 3 Professional UNO to determine when to use interface attributes and when to introduce properties in a service . The property keyword must be enclosed in square brackets, and continue with a known type and a property identifier. Just like a service and an interface, make a property non-mandatory writing [property, optional]. Besides optional,there is a number of other flags to use with properties. The following table shows all flags that can be used with [property]:
|
Property Flags |
Description |
|
optional |
Property is non-mandatory. |
|
readonly |
The value of the property cannot be changed using the setter methods for properties, such as setPropertyValue(string name). |
|
bound |
Changes of values are broadcast to com.sun.star.beans.XPropertyChangeListeners registered with the component. |
|
constrained |
The component must broadcast an event before a value changes, listeners can veto. |
|
maybeambiguous |
The value cannot be determined in some cases, for example, in multiple selections. |
|
maybedefault |
The value might come from a style or the application environment instead of from the object itself. |
|
maybevoid |
The property type determines the range of possible values, but sometimes there may be situations where there is no information available. Instead of defining special values for each type denoting that there are no meaningful values, the UNO type void can be used. Its meaning is comparable to null in relational databases. |
|
removable |
The property is removable. If a property is made removable, you must check for the existence of a property using hasPropertyByName() at the interface com.sun.star.beans.XPropertySetInfo and consider providing the capability to add or remove properties using com.sun.star.beans.XPropertyContainer. |
|
transient |
The property will not be stored if the object is serialized (made persistent). |
Several properties of the same type can be listed in one property declaration. Remember to add a semicolon at the end. Implement the interface com.sun.star.beans.XPropertySet when putting properties in your service, otherwise the properties specified will not work for others using the component.
|
|
Some old-style services, which specify no interfaces at all, only properties, are used as a sequence of com.sun.star.beans.PropertyValue in OpenOffice.org, for example, com.sun.star.document.MediaDescriptor. |
The following UNOIDL snippet shows the service, the interfaces and the properties supported by the old-style service com.sun.star.text.TextDocument as defined in UNOIDL. Note the optional interfaces and the optional and read-only properties.
service TextDocument
{
service com::sun::star::document::OfficeDocument;
interface com::sun::star::text::XTextDocument;
interface com::sun::star::util::XSearchable;
interface com::sun::star::util::XRefreshable;
interface com::sun::star::util::XNumberFormatsSupplier;
[optional] interface com::sun::star::text::XFootnotesSupplier;
[optional] interface com::sun::star::text::XEndnotesSupplier;
[optional] interface com::sun::star::util::XReplaceable;
[optional] interface com::sun::star::text::XPagePrintable;
[optional] interface com::sun::star::text::XReferenceMarksSupplier;
[optional] interface com::sun::star::text::XLineNumberingSupplier;
[optional] interface com::sun::star::text::XChapterNumberingSupplier;
[optional] interface com::sun::star::beans::XPropertySet;
[optional] interface com::sun::star::text::XTextGraphicObjectsSupplier;
[optional] interface com::sun::star::text::XTextEmbeddedObjectsSupplier;
[optional] interface com::sun::star::text::XTextTablesSupplier;
[optional] interface com::sun::star::style::XStyleFamiliesSupplier;
[optional, property] com::sun::star::lang::Locale CharLocale;
[optional, property] string WordSeparator;
[optional, readonly, property] long CharacterCount;
[optional, readonly, property] long ParagraphCount;
[optional, readonly, property] long WordCount;
};
|
|
You might encounter two more keywords in old-style service bodies. The keyword observes can stand in front of interface references and means that the given interfaces must be "observed". Since the observes concept is disapproved of, no further explanation is provided. If a service references another service using the keyword needs in front of the reference, then this service depends on the availability of the needed service at runtime. Services should not use needs as it is considered too implementation specific. |
A sequence in UNOIDL is an array containing a variable number of elements of the same UNOIDL type. The following is an example of a sequence term:
// this term could occur in a UNOIDL definition block somewhere
sequence< com::sun::star::uno::XInterface >
It starts with the keyword sequence and gives the element type enclosed in angle brackets <>. The element type must be a known type. A sequence type can be used as parameter, return value, property or struct member just like any other type. Sequences can also be nested, if necessary.
// this could be a nested sequence definition
sequence< sequence< long > >
// this could be an operation using sequences in some interface definition
sequence< string > getNamesOfIndex(sequence< long > indexes);
A struct is a compound type which puts together arbitrary UNOIDL types to form a new data type. Its member data are not encapsulated, rather they are publicly available. Structs are frequently used to handle related data easily, and the event structs broadcast to event listeners.
A plain struct instruction opens with the keyword struct, gives an identifier for the new struct type and has a struct body in braces. It is terminated by a semicolon. The struct body contains a list of struct member declarations that are defined by a known type and an identifier for the struct member. The member declarations must end with a semicolon, as well.
#ifndef __com_sun_star_reflection_ParamInfo_idl__
#define __com_sun_star_reflection_ParamInfo_idl__
#include <com/sun/star/reflection/ParamMode.idl>
module com { module sun { module star { module reflection {
interface XIdlClass; // forward interface declaration
struct ParamInfo
{
string aName;
ParamMode aMode;
XIdlClass aType;
};
}; }; }; };
#endif
UNOIDL supports inheritance of struct types. Inheritance is expressed by a colon : followed by the full name of the parent type. A struct type recursively inherits all members of the parent struct and their parents. For instance, derive from the struct com.sun.star.lang.EventObject to put additional information about new events into customized event objects to send to event listeners.
// com.sun.star.beans.PropertyChangeEvent inherits from com.sun.star.lang.EventObject
// and adds property-related information to the event object
struct PropertyChangeEvent : com::sun::star::lang::EventObject
{
string PropertyName;
boolean Further;
long PropertyHandle;
any OldValue;
any NewValue;
};
A new feature of OpenOffice.org 2.0 are polymorphic struct types. A polymorphic struct type template is similar to a plain struct type, but it has one or more type parameters enclosed in angle brackets <>, and its members can have these parameters as types:
// A polymorphic struct type template with two type parameters:
struct Poly<T,U> {
T member1;
T member2;
U member3;
long member4;
};
A polymorphic struct type template is not itself a UNO type—it has to be instantiated with actual type arguments to be used as a type:
// Using an instantiation of Poly as a type in UNOIDL:
interface XIfc { Poly<boolean, any> fn(); };
An exception type is a type that contains information about an error . If an operation detects an error that halts the normal process flow, it must raise an exception and send information about the error back to the caller through an exception object. This causes the caller to interrupt its normal program flow as well and react according to the information received in the exception object. For details about exceptions and their implementation, refer to the chapters 3.4 Professional UNO - UNO Language Bindings and 3.3.7 Professional UNO - UNO Concepts - Exception Handling.
There are a number of exceptions to use. The exceptions should be sufficient in many cases, because a message string can be sent back to the caller. When defining an exception, do it in such a way that other developers could reuse it in their contexts.
An exception declaration opens with the keyword exception, gives an identifier for the new exception type and has an exception body in braces. It is terminated by a semicolon. The exception body contains a list of exception member declarations that are defined by a known type and an identifier for the exception member. The member declarations must end with a semicolon, as well.
Exceptions must be based on com.sun.star.uno.Exception or com.sun.star.uno.RuntimeException, directly or indirectly through derived exceptions of these two exceptions. com.sun.star.uno.Exceptions can only be thrown in operations specified to raise them while com.sun.star.uno.RuntimeExceptions can always occur. Inheritance is expressed by a colon :, followed by the full name of the parent type.
// com.sun.star.uno.Exception is the base exception for all exceptions
exception Exception {
string Message;
XInterface Context;
};
// com.sun.star.lang.IllegalArgumentException tells the caller which
// argument caused trouble
exception IllegalArgumentException: com::sun::star::uno::Exception
{
/** identifies the position of the illegal argument.
<p>This field is -1 if the position is not known.</p>
*/
short ArgumentPosition;
};
// com.sun.star.uno.RuntimeException is the base exception for serious errors
// usually caused by programming errors or problems with the runtime environment
exception RuntimeException : com::sun::star::uno::Exception {
};
// com.sun.star.uno.SecurityException is a more specific RuntimeException
exception SecurityException : com::sun::star::uno::RuntimeException {
};
Predefined values can be provided, so that implementers do not have to use cryptic numbers or other literal values. There are two kinds of predefined values, constants and enums. Constants can contain values of any basic UNO type, except void. The enums are automatically numbered long values.
The constants type is a container for const types. A constants instruction opens with the keyword constants, gives an identifier for the new group of const values and has the body in braces. It terminates with a semicolon. The constants body contains a list of const definitions that define the values of the members starting with the keyword const followed by a known type name and the identifier for the const in uppercase letters. Each const definition must assign a value to the const using an equals sign. The value must match the given type and can be an integer or floating point number, or a character, or a suitable const value or an arithmetic term based on the operators in the table below. The const definitions must end with a semicolon, as well.
#ifndef __com_sun_star_awt_FontWeight_idl__
#define __com_sun_star_awt_FontWeight_idl__
module com { module sun { module star { module awt {
constants FontWeight
{
const float DONTKNOW = 0.000000;
const float THIN = 50.000000;
const float ULTRALIGHT = 60.000000;
const float LIGHT = 75.000000;
const float SEMILIGHT = 90.000000;
const float NORMAL = 100.000000;
const float SEMIBOLD = 110.000000;
const float BOLD = 150.000000;
const float ULTRABOLD = 175.000000;
const float BLACK = 200.000000;
};
}; }; }; };
|
Operators Allowed in const |
Meaning |
|
+ |
addition |
|
- |
subtraction |
|
* |
multiplication |
|
/ |
division |
|
% |
modulo division |
|
- |
negative sign |
|
+ |
positive sign |
|
| |
bitwise or |
|
^ |
bitwise xor |
|
& |
bitwise and |
|
~ |
bitwise not |
|
>> << |
bitwise shift right, shift left |
|
|
Use constants to group const types. In the Java language, binding a constants group leads to one class for all const members, whereas a single const is mapped to an entire class. |
An enum type holds a group of predefined long values and maps them to meaningful symbols. It is equivalent to the enumeration type in C++. An enum instruction opens with the keyword enum, gives an identifier for the new group of enum values and has an enum body in braces. It terminates with a semicolon. The enum body contains a comma-separated list of symbols in uppercase letters that are automatically mapped to long values counting from zero, by default.
#ifndef __com_sun_star_style_ParagraphAdjust_idl__
#define __com_sun_star_style_ParagraphAdjust_idl__
module com { module sun { module star { module style {
enum ParagraphAdjust
{
LEFT,
RIGHT,
BLOCK,
CENTER,
STRETCH
};
}; }; }; };
#endif
In this example, com.sun.star.style.ParagraphAdjust:LEFT corresponds to 0, ParagraphAdjust.RIGHT corresponds to 1 and so forth.
An enum member can also be set to a long value using the equals sign. All the following enum values are then incremented starting from this value. If there is another assignment later in the code, the counting starts with that assignment:
enum Error {
SYSTEM = 10, // value 10
RUNTIME, // value 11
FATAL, // value 12
USER = 30, // value 30
SOFT // value 31
};
|
|
The explicit use of enum values is deprecated and should not be used. It is a historical characteristic of the enum type but it makes not really sense and makes, for example language bindings unnecessarily complicated. |
Comments are code sections ignored by idlc. In UNOIDL, use C++ style comments. A double slash // marks the rest of the line as comment. Text enclosed between /* and */ is a comment that may span over multiple lines.
service ImageShrink
{
// the following lines define interfaces:
interface org::openoffice::test::XImageShrink; // our home-grown interface
interface com::sun::star::document::XFilter;
/* we could reference other interfaces, services and properties here.
However, the keywords uses and needs are deprecated
*/
};
Based on the above, there are documentation comments that are extracted when idl files are processed with autodoc, the UNOIDL documentation generator. Instead of writing /* or //to mark a plain comment, write /** or /// to create a documentation comment.
/** Don't repeat asterisks within multiple line comments,
* <- as shown here
*/
/// Don't write multiple line documentation comments using triple slashes,
/// since only this last line will make it into the documentation
Our XUnoUrlResolver sample idl file contains plain comments and documentation comments.
/** service <type scope="com::sun::star::bridge">UnoUrlResolver</type>
implements this interface.
*/
interface XUnoUrlResolver: com::sun::star::uno::XInterface
{
// method com::sun::star::bridge::XUnoUrlResolver::resolve
/** resolves an object, on the UNO URL.
*/
...
}
Note the additional <type/> tag in the documentation comment pointing out that the service UnoUrlResolver implements the interface XUnoUrlResolver. This tag becomes a hyperlink in HTML documentation generated from this file. The chapter B IDL Documentation Guide provides a comprehensive description for UNOIDL documentation comments.
A singleton declaration defines a global name for a UNO object and determines that there can only be one instance of this object that must be reachable under this name. The singleton instance can be retrieved from the component context using the name of the singleton. If the singleton has not been instantiated yet, the component context creates it. A new-style singleton declaration, that binds a singleton name to an object with a certain interface type, looks like this:
singleton thePackageManagerFactory: com::sun::star::depoyment::XPackageManager;
There are also old-style singletons, which reference (old-style) services instead of interfaces.
There are types in UNOIDL which are reserved for future use. The idlc will refuse to compile the specifications if they are tried.
The keyword array is reserved, but it cannot be used in UNOIDL. There will be sets containing a fixed number of elements, as opposed to sequences, that can have an arbitrary number of elements.
There is also a reserved keyword for union types that cannot be used in UNOIDL. A union will look at a variable value from more than one perspective. For instance, a union for a long value is defined and this same value is accessed as a whole, or accessed by its high and low part separately through a union.
A new feature of OpenOffice.org 2.0 is the UNOIDL published keyword. If you mark a declaration (of a struct, interface, service, etc.) as published, you give the guarantee that you will not change the declaration in the future, so that clients of your API can depend on that. On the other hand, leaving a declaration unpublished is like a warning to your clients that the declared entity may change or even vanish in a future version of your API. The idlc will give an error if you try to use an unpublished entity in the declaration of a published one, as that would not make sense.
The OpenOffice.org API has always been intended to never change in incompatible ways. This is now reflected formally by publishing all those entities of the OpenOffice.org 2.0 API that were already available in previous API versions. Some new additions to the API have been left unpublished, however, to document that they are probably not yet in their final form. When using such additions, keep in mind that you might need to adapt your code to work with future versions of OpenOffice.org. Generally, each part of the OpenOffice.org API should stabilize over time, however, and so each addition should eventually be published. Consider this as a means in attempting to make new functionality available as early as possible, and at the same time ensure that no APIs are fixed prematurely, before they have matured to a truly useful form.
The type description provided in .idl files is used in the subsequent process to create type information for the service manager and to generate header and class files. Processing the UNOIDL definitions is a three-step process.
Compile the .idl files using idlc . The result are .urd files (UNO reflection data) containing binary type descriptions.
Merge the .urd files into a registry database using regmerge . The registry database files have the extension .rdb (registry database). They contain binary data describing types in a tree-like structure starting with / as the root. The default key for type descriptions is the /UCR key (UNO core reflection).
Generate sources from registry files using javamaker or cppumaker . The tools javamaker and cppumaker map UNOIDL types to Java and C++ as described in the chapter 3.4 Professional UNO - UNO Language Bindings. The registries used by these tools must contain all types to map to the programming language used, including all types referenced in the type descriptions. Therefore, javamaker and cppumaker need the registry that was merged, but the entire office registry as well. OpenOffice.org comes with a complete registry database providing all types used by UNO at runtime. The SDK uses the database (type library) of an existing OpenOffice.org installation.
The following shows the necessary commands to create Java class files and C++ headers from .idl files in a simple setup under Linux. We assume the jars from <OFFICE_PROGRAM_PATH>/classes have been added to your CLASSPATH, the SDK is installed in /home/sdk, and /home/sdk/linux/bin is in the PATH environment variable, so that the UNO tools can be run directly. The project folder is /home/sdk/Thumbs and it contains the above .idl file XImageShrink.idl.
# make project folder the current directory
cd /home/sdk/Thumbs
# compile XImageShrink.idl using idlc
# usage: idlc [-options] file_1.idl ... file_n.idl
# -C adds complete type information including services
# -I includepath tells idlc where to look for include files
#
# idlc writes the resulting urds to the current folder by default
idlc -C -I../idl XImageShrink.idl
# create registry database (.rdb) file from UNO registry data (.urd) using regmerge
# usage: regmerge mergefile.rdb mergeKey regfile_1.urd ... regfile_n.urd
# mergeKey entry in the tree-like rdb structure where types from .urd should be recorded, the tree
# starts with the root / and UCR is the default key for type descriptions
#
# regmerge writes the rdb to the current folder by default
regmerge thumbs.rdb /UCR XImageShrink.urd
# generate Java class files for new types from rdb
# -B base node to look for types, in this case UCR
# -T type to generate Java files for
# -nD do not generate sources for dependent types, they are available in the Java UNO jar files
#
# javamaker creates a directory tree for the output files according to
# the modules the given types were placed in. The tree is created in the current folder by default
javamaker -BUCR -Torg.openoffice.test.XImageShrink -nD <OFFICE_PROGRAM_PATH>/types.rdb thumbs.rdb
# generate C++ header files (hpp and hdl) for new types and their dependencies from rdb
# -B base node to look for types, in this case UCR
# -T type to generate Java files for
#
# cppumaker creates a directory tree for the output files according to
# the modules the given types were placed in. The tree is created in the current folder by default
cppumaker -BUCR -Torg.openoffice.test.XImageShrink <OFFICE_PROGRAM_PATH>/types.rdb thumbs.rdb
After issuing these commands you have a registry database thumbs.rdb and a Java class file XImageShrink.class. (In versions of OpenOffice.org prior to 2.0, javamaker produced Java source files instead of class files; you therefore had to call javac on the source files in an additional step.) You can run regview against thumbs.rdb to see what regmerge has accomplished.
regview thumbs.rdb
The result for our interface XImageShrink looks like this:
Registry "file:///home/sdk/Thumbs/thumbs.rdb":
/
/ UCR
/ org
/ openoffice
/ test
/ XImageShrink
Value: Type = RG_VALUETYPE_BINARY
Size = 316
Data = minor version: 0
major version: 1
type: 'interface'
uik: { 0x00000000-0x0000-0x0000-0x00000000-0x00000000 }
name: 'org/openoffice/test/XImageShrink'
super name: 'com/sun/star/uno/XInterface'
Doku: ""
IDL source file: "/home/sdk/Thumbs/XImageShrink.idl"
number of fields: 3
field #0:
name='SourceDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #1:
name='DestinationDirectory'
type='string'
access=READWRITE
Doku: ""
IDL source file: ""
field #2:
name='Dimension'
type='com/sun/star/awt/Size'
access=READWRITE
Doku: ""
IDL source file: ""
number of methods: 0
number of references: 0
Source generation can be fully automated with makefiles. For details, see the sections 4.5.9 Writing UNO Components - Simple Component in Java - Running and Debugging Java Components and 4.6.10 Writing UNO Components - C++ Component - Building and Testing C++ Components below. You are now ready to implement your own types and interfaces in a UNO component. The next section discusses the UNO core interfaces to implement in UNO components.
UNO components are archive files or dynamic link libraries with the ability to instantiate objects which can integrate themselves into the UNO environment. For this purpose, components must contain certain static methods (Java) or export functions (C++) to be called by a UNO service manager. In the following, these methods are called component operations.
There must be a method to supply single-service factories for each object implemented in the component. Through this method, the service manager can get a single factory for a specific object and ask the factory to create the object contained in the component. Furthermore, there has to be a method which writes registration information about the component, which is used when a component is registered with the service manager. In C++, an additional function is necessary that informs the component loader about the compiler used to build the component.
The component operations are always necessary in components and they are language specific. Later, when Java and C++ are discussed, we will show how to write them.
Illustration 1: A Component implementing three UNO objects |
The illustration shows a component which contains three implemented objects. Two of them, srv1 and srv2 implement a single service specification (Service1 and Service2), whereas srv3_4 supports two services at once (Service3 and Service4).
The objects implemented in a component must support a number of core UNO interfaces to be fully usable from all parts of the OpenOffice.org application. These core interfaces are discussed in the next section. The individual functionality of the objects is covered by the additional interfaces they export. Usually these interfaces are enclosed in a service specification.
It is important to know where the interfaces to implement are located. The interfaces here are located at the object implementations in the component. When writing UNO components, the desired methods have to be implemented into the application and also, the core interfaces used to enable communication with the UNO environment. Some of them are mandatory, but there are others to choose from.
|
Interface |
Required |
Should be implemented |
Optional |
Special Cases |
Helper class available for C++ and Java |
|
XInterface |
● |
|
|
|
● |
|
XTypeProvider |
|
● |
|
|
● |
|
XServiceInfo |
|
● |
|
|
|
|
XWeak |
|
● |
|
|
● |
|
XComponent |
|
|
● |
|
● |
|
XInitialization |
|
|
● |
|
|
|
XMain |
|
|
|
● |
|
|
XAggregation |
|
|
|
● |
|
|
XUnoTunnel |
|
|
|
● |
|
The interfaces listed in the table above have been characterized here briefly. More descriptions of each interface are provided later, as well as if helpers are available and which conditions apply.
The component will not work without it. The base interface XInterface gives access to higher interfaces of the service and allows other objects to tell the service when it is no longer needed, so that it can destroy itself.
// com::sun::star::uno::XInterface
any queryInterface( [in] type aType );
[oneway] void acquire(); // increase reference counter in your service implementation
[oneway] void release(); // decrease reference counter, delete object when counter becomes zero
Usually developers do not call acquire() explicitly, because it is called automatically by the language bindings when a reference to a component is retrieved through UnoRuntime.queryInterface() or Reference<destInterface>(sourceInterface, UNO_QUERY) . The counterpart release() is called automatically when the reference goes out of scope in C++ or when the Java garbage collector throws away the object holding the reference.
com.sun.star.lang.XTypeProvider
This interface is used by scripting languages such as OpenOffice.org Basic to get type information. OpenOffice.org Basic cannot use the component without it.
// com::sun::star::lang::XTypeProvider
sequence<type> getTypes();
sequence<byte> getImplementationId();
It is possible that XTypeProvider and XServiceInfo (below) will be deprecated in the future, and that alternative, language-binding–specific mechanisms will be made available to query an object for its characteristics.
com.sun.star.lang.XServiceInfo
This interface is used by other objects to get information about the service implementation.
// com::sun::star::lang::XServiceInfo
string getImplementationName();
boolean supportsService( [in] string ServiceName );
sequence<string> getSupportedServiceNames();
This interface allows clients to keep a weak reference to the object. A weak reference does not prevent the object from being destroyed if another client keeps a hard reference to it, therefore it allows a hard reference to be retrieved again. The technique is used to avoid cyclic references. Even if the interface is not required by you, it could be implemented for a client that may want to establish a weak reference to an instance of your object.
// com.sun.star.uno.XWeak
com::sun::star::uno::XAdapter queryAdapter(); // creates Adapter
This interface is used if cyclic references can occur in the component holding another object and the other object is holding a reference to that component. It can be specified in the service description who shall destroy the object.
// com::sun::star::lang::XComponent
void dispose(); //an object owning your component may order it to delete itself using dispose()
void addEventListener(com::sun::star::lang::XEventListener xListener); // add dispose listeners
void removeEventListener (com::sun::star::lang::XEventListener aListener); // remove them
com.sun.star.lang.XInitialization
This interface is used to allow other objects to use createInstanceWithArguments() or createInstanceWithArgumentsAndContext() with the component. It should be implemented and the arguments processed in initialize():
// com::sun::star::lang::XInitialization
void initialize(sequence< any > aArguments) raises (com::sun::star::uno::Exception);
This interface is for use with the uno executable to instantiate the component independently from the OpenOffice.org service manager.
// com.sun.star.lang.XMain
long run (sequence< string > aArguments);
This interfaces makes the implementation cooperate in an aggregation. If implemented, other objects can aggregate to the implementation. Aggregated objects behave as if they were one. If another object aggregates the component, it holds the component and delegates calls to it, so that the component seems to be one with the aggregating object.
// com.sun.star.uno.XAggregation
void setDelegator(com.sun.star.uno.XInterface pDelegator);
any queryAggregation(type aType);
This interface provides a pointer to the component to another component in the same process. This can be achieved with XUnoTunnel. XUnoTunnel should not be used by new components, because it is to be used for integration of existing implementations, if all else fails.
By now you should be able to decide which interfaces are interesting in your case. Sometimes the decision for or against an interface depends on the necessary effort as well. The following section discusses for each of the above interfaces how you can take advantage of pre-implemented helper classes in Java or C++, and what must happen in a possible implementation, no matter which language is used.
All service implementations must implement com.sun.star.uno.XInterface. If a Java component is derived from a Java helper class that comes with the SDK, it supports XInterface automatically. Otherwise, it is sufficient to add XInterface or any other UNO interface to the implements list. The Java UNO runtime takes care of XInterface. In C++, there are helper classes to inherit that already implement XInterface. However, if XInterface is to be implemented manually, consider the code below.
The IDL specification for com.sun.star.uno.XInterface looks like this:
// module com::sun::star::uno
interface XInterface
{
any queryInterface( [in] type aType );
[oneway] void acquire();
[oneway] void release();
};
When queryInterface() is called, the caller asks the implementation if it supports the interface specified by the type argument. The UNOIDL base type stores the name of a type and its com.sun.star.uno.TypeClass. The call must return an interface reference of the requested type if it is available or a void any if it is not. There are certain conditions a queryInterface() implementation must meet:
Constant Behaviour
If queryInterface() on a specific object has once returned a valid interface reference for a given type, it must always return a valid reference for any subsequent queryInterface() call for the same type on this object. A query for XInterface must always return the same reference.
If queryInterface() on a specific object has once returned a void any for a given type, it must always return a void any for the same type.
Symmetry
If queryInterface() for XBar on a reference xFoo returns a reference xBar, then queryInterface() on reference xBar for type XFoo must return xFoo or calls made on the returned reference must be equivalent to calls to xFoo.
Object Identity
In C++, two objects are the same if their XInterface are the same. The queryInterface() for XInterface will have to be called on both. In Java, check for the identity by calling the runtime function com.sun.star.uni.UnoRuntime.areSame().
The reason for this specifications is that a UNO runtime environment may choose to cache queryInterface() calls. The rules are identical to the rules of the function QueryInterface() in MS COM.
|
|