OpenOffice.org can be extended by UNO components. UNO components are shared libraries or jar files with the ability to instantiate objects which 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. That way, OpenOffice.org keeps the promise to be open for modular 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. This chapter provides insights into the UNOIDL language and the inner workings of the service manager, especially if you plan to write your own UNO components.
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 and include files, Java packages, UNO type definitions and example code. But most of the necessary libraries and Java UNO packages 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. |
pkgchk |
The package check that installs components into an installed OpenOffice.org. |
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 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 or other types.
UNO uses its own meta language UNOIDL (UNO Interface Definition Language) to specify interfaces and other 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 compound type,write its specification in UNOIDL, then compile it with the UNOIDL compiler idlc. After compilation, merge the resulting binary type description into a registry database that is used by cppumaker and javamaker during the make process to create necessary header and class files, and used by UNO during runtime to provide runtime type information. 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.7.2 Writing UNO Components - Deployment Options for Components - Background: UNO Registries - UNO Type Library for the details about type information in a registry database..
This section teaches you how to write and compile an UNOIDL specification. When writing your own specifications, please read the chapter Appendix A: API 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 familiar with reading C++ or CORBA IDL, you will be able to read UNOIDL. UNOIDL uses the US ASCII character set without special characters and separates symbols by whitespace, that is, blanks, tabs or linefeeds. Each UNOIDL instruction ends with a semicolon. 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.
*/
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
This idl file is discussed step-by-step below, and eventually write a UNOIDL specification. The file specifying com.sun.star.bridge.XUnoUrlResolver located in the idl folder of your SDK installation, <SDK>/idl/com/sun/star/bridge/XUnoUrlResolver.idl. UNOIDL definition file names use the extension .idl.
Just like a C++ compiler, the UNOIDL compiler idlc can only use types it already knows. The idlc knows 15 fundamental types such as boolean, int or string (they are summarized below). Whenever a type other than a fundamental type is used in the idl file, include its declaration 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.
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, enter all modules for the type it is contained in and separate the modules by the scope operator ::. For instance, to reference XUnoUrlResolver in an other idl definition, write com::sun::star::bridge::XUnoUrlResolver.
Modules have an influence 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.
Before defining the first interface, you should be familiar with the fundamental types that were described in the chapters 2 First Steps and 3 Professional UNO. The table below repeats the type keywords and their meanings.
Fundamental 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 |
long |
signed 32-bit ordinal integer type |
unsigned long |
unsigned 32-bit integer type |
hyper |
signed 64-bit ordinal integer type |
unsigned hyper |
unsigned 64-bit ordinal integer type |
float |
processor dependent float |
double |
processor dependent double |
string |
string of 16-bit unicode characters |
any |
universal type, takes every fundamental 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 operation signature, and the exceptions the operation throws. We will first write a small interface definition with attribute instructions, then consider the resolve() operation 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 methods.
The attribute instruction creates these methods 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
|
OpenOffice.org API interfaces do not use attributes anymore, because it entices programmers into ignoring exceptions. They are confusing, because attributes are mapped as prefixed get/set methods in an implementation language like Java or C++. It is sometimes difficult to match these methods with the original attribute declaration. Also note, that attribute definitions in UNOIDL interfaces do not declare any data fields, just the access methods. |
---|
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. UNO supports single inheritance, so you may only inherit from one interface. 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.
|
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 instructions or operations. Consider the interface body of XImageShrink. It contains three attributes and no operation. The operations are discussed below.
An attribute instruction 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, the attribute instruction 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. Note that no exceptions can be specified for attribute 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.
When writing a real component, define the operations by providing their signature and the exceptions they throw in the idl file. Our XUnoUrlResolver example above features a resolve() operation 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 an operation is similar to C++ functions or Java methods. The operation is defined giving a known return type, the operation name, an argument list in brackets () and if necessary, a list of the exceptions the operation 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 Operations |
Description |
---|---|
in |
Specifies that the operation shall evaluate the argument as input parameter, but it cannot change it. |
out |
Specifies that the argument does not parameterize the operation, instead the operation uses the argument as output parameter. |
inout |
Specifies that the operation is parameterized by the argument and that the operation uses the argument as output parameter as well. |
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 operations, 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 must perform its task asynchronously, that is, it should spawn a thread and return immediately. The argument list may be empty. Multiple arguments must be separated by commas. A oneway operation can not have a return value, or out or inout parameters.
|
You may not override an attribute or an operation 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, services can include other services. For these purposes, the instructions interface, property and service are used within service specifications. Usually services are the basis for an object implementation, although there are 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()
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/XImageShrink.idl>
module org { module openoffice { module test {
service ImageShrink
{
interface org::openoffice::test::XImageShrink;
interface com::sun::star::document::XFilter;
};
}; }; };
#endif
Define a service using the service instruction. It opens with the keyword service, followed by a service name and the service body in braces. The service instruction concludes with a semicolon. Here we defined a service ImageShrink. The first letter of a service name should be an upper-case letter. 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 instructions 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 instruction 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 instruction per service or a comma-separated list for the services to reference. The service instruction ends with a semicolon.
[property] instructions 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 instruction 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). |
|
Some 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 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 instructions in service bodies. The instruction observes can stand in front of interface references and means that the given interfaces must be "observed". Since the observes instruction is disapproved of, no further explanation is provided. |
---|
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 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;
};
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.6 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 instruction 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 fundamental UNOIDL type, except string. 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 Appendix B: API Documentation Guide provides a comprehensive description for UNOIDL documentation comments.
A singleton instruction defines a global name for a service instance and determines that there can only be one instance of this service that must be reachable under this name. In the future, there will be the capability of retrieving the singleton instance from the component context using the name of the singleton. If the singleton has not been instantiated yet, the component context creates it. A singleton instruction looks like this:
singleton theServiceManager {
service com::sun::star::lang::ServiceManager;
};
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.
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 source 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>/applicat.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>/applicat.rdb thumbs.rdb
# compile Java class for new type
javac -g org/openoffice/test/XImageShrink.java
After issuing these commands you have a registry database thumbs.rdb and a Java class file XImageShrink.class. 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 - Testing 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 package 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 discuss, we will show how to write them.
|
---|
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.
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).
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();
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.
|
If you want to implement queryInterface() in Java, for example, you want to export less interfaces than you implement, your class must implement the Java interface com.sun.star.uno.IQueryInterface. |
---|
The methods acquire() and release() handle the lifetime of the UNO object. This is discussed in detail in chapter 3.3.7 Professional UNO - UNO Concepts - Lifetime of UNO Objects. Acquire and release must be implemented in a thread-safe fashion. This is demonstrated in C++ in the section about C++ components below.
Every UNO object should implement the com.sun.star.lang.XTypeProvider interface.
Some applications need to know which interfaces an UNO object supports, for example, the OpenOffice.org Basic engine or debugging tools, such as the InstanceInspector. The com.sun.star.lang.XTypeProvider interface was introduced to avoid going through all known interfaces calling queryInterface() repetitively. The XTypeProvider interface is implemented by Java and C++ helper classes. If the XTypeProvider must be implemented manually, use the following methods:
// module com::sun::star::lang
interface XTypeProvider: com::sun::star::uno::XInterface
{
sequence<type> getTypes();
sequence<byte> getImplementationId();
};
The sections about Java and C++ components below show examples of XTypeProvider implementations.
The com.sun.star.lang.XTypeProvider:getTypes() method must return a list of types for all interfaces that queryInterface() provides. The OpenOffice.org Basic engine depends on this information to establish a list of method signatures that can be used with an object.
For caching purposes, the getImplementationId() method has been introduced. The method must return a byte array containing an identifier for the implemented set of interfaces in this implementation class. It is important that one ID maps to one set of interfaces, but one set of interfaces can be known under multiple IDs. Every implementation class should generate a static ID.
Every service implementation should export the com.sun.star.lang.XServiceInfo interface. XServiceInfo must be implemented manually, because only the programmer knows what services the implementation supports. The sections about Java and C++ components below show examples for XServiceInfo implementations.
This is how the IDL specification for XServiceInfo looks like:
// module com::sun::star::lang
interface XServiceInfo: com::sun::star::uno::XInterface
{
string getImplementationName();
boolean supportsService( [in] string ServiceName );
sequence<string> getSupportedServiceNames();
};
The method getImplementationName() provides access to the implementation name of a service implementation. The implementation name uniquely identifies one implementation of service specifications in a UNO object. The name can be chosen freely by the implementation alone, because it does not appear in IDL. However, the implementation should adhere to the following naming conventions:
company prefix |
dot |
"comp" |
dot |
module name |
dot |
unique object name in module |
implemented service(s) |
com.sun.star |
. |
comp |
. |
forms |
. |
ODataBaseForm |
com.sun.star.forms.DataBaseForm |
org.openoffice |
. |
comp |
. |
test |
. |
OThumbs |
org.openoffice.test.ImageShrink |
If an object implements one single service, it can use the service name to derive an implementation name. Implementations of several services should use a name that describes the entire object.
If a createInstance() is called at the service manager using an implementation name, an instance of exactly that implementation is received. An implementation name is equivalent to a class name in Java. A Java component simply returns the fully qualified class name in getImplementationName().
|
It is good practice to program against the specification and not against the implementation, otherwise, your application could break with future versions. OpenOffice.orgs API implementation is not supposed to be compatible, only the specification is. |
---|
The methods getSupportedServiceNames() and supportsService() deal with the availability of services in an implemented object. Note that the supported services are the services implemented in one class that supports these services, not the services of all implementations contained in the component file. If the illustration : , XServiceInfo is exported by the implemented objects in a component, not by the component. That means, srv3_4 must support XServiceInfo and return "Service3" and "Service4" as supported service names.
The service name identifies a service as it was specified in IDL. If an object is instantiated at the service manager using the service name, an object that complies to the service specification is returned.
|
The single service factories returned by components that are used to create instances of an implementation through their interfaces com.sun.star.lang.XSingleComponentFactory or com.sun.star.lang.XSingleServiceFactory must support XServiceInfo. The single factories support this interface to allow UNO to inspect the capabilities of a certain implementation before instantiating it. You can take advantage of this feature through the com.sun.star.container.XContentEnumerationAccess interface of a service manager. |
---|
A component supporting XWeak offers other objects to hold a reference on itself without preventing it from being destroyed when it is no longer needed. Thus, cyclic references can be avoided easily. The chapter 3.3.7 Professional UNO - UNO Concepts - Lifetime of UNO Objects discusses this in detail. In Java, derive from the Java helper class com.sun.star.lib.uno.helper.WeakBase to support XWeak. If a C++ component is derived from one of the ::cppu::Weak...ImplHelperNN template classes as proposed in the section 4.6 Writing UNO Components - C++ Component, a XWeak support is obtained, virtually for free. For the sake of completeness, this is the XWeak specification:
// module com::sun::star::uno::XWeak
interface XWeak: com::sun::star::uno::XInterface
{
com::sun::star::uno::XAdapter queryAdapter();
};
If the implementation holds a reference to another UNO object internally, there may be a problem of cyclic references that might prevent your component and the other object from being destroyed forever. If it is probable that the other object may hold a reference to your component, implement com.sun.star.lang.XComponent that contains a method dispose(). Chapter 3.3.7 Professional UNO - UNO Concepts - Lifetime of UNO Objects discusses the intricacies of this issue.
Supporting XComponent in a C++ or Java component is simple, because there are helper classes to derive from that implement XComponent. The following code is an example if you must implement XComponent manually.
The interface XComponent specifies these operations:
// module com::sun::star::lang
interface XComponent: com::sun::star::uno::XInterface
{
void dispose();
void addEventListener( [in] XEventListener xListener );
void removeEventListener( [in] XEventListener aListener );
};
XComponent uses the interface com.sun.star.lang.XEventListener:
// module com::sun::star::lang
interface XEventListener: com::sun::star::uno::XInterface
{
void disposing( [in] com::sun::star::lang::EventObject Source );
};
The idea behind XComponent is that the object is instantiated by a third object that makes the third object the owner of first object. The owner is allowed to call dispose(). When the owner calls dispose() at your object, it must do three things:
Release all references it holds.
Inform registered XEventListeners that it is being disposed of by calling their method disposing().
Behave as passive as possible afterwards. If the implementation is called after being disposed, throw a com.sun.star.lang.DisposedException if you cannot fulfill the method specification.
That way the owner of XComponent objects can dissolve a possible cyclic reference.
The interface com.sun.star.lang.XInitialization is usually implemented manually, because only the programmer knows how to initialize the object with arguments received from the service manager through createInstanceWithArguments() or createInstanceWithArgumentsAndContext(). In Java, XInitialization is used as well, but know that the Java factory helper provides a shortcut that uses arguments without implementing XInitialization directly. The Java factory helper can pass arguments to the class constructor under certain conditions. Refer to the section 4.5.7 Writing UNO Components - Simple Component in Java - Create Instance With Arguments for more information.
The specification for XInitialization looks like this:
// module com::sun::star::lang
interface XInitialization : com::sun::star::uno::XInterface
{
void initialize(sequence< any > aArguments) raises (com::sun::star::uno::Exception);
};
Specify in the idl service specification which arguments and in which order are expected within the any sequence.
The implementation of com.sun.star.lang.XMain is used for special cases. Its run() operation is called by the uno executable. The section 4.8 Writing UNO Components - The UNO Executable below discusses the use of XMain and the uno executable in detail.
// module com::sun::star::lang
interface XMain: com::sun::star::uno::XInterface
{
long run( [in] sequence< string > aArguments );
};
A concept called aggregation is commonly used to plug multiple objects together to form one single object at runtime. The main interface in this context is com.sun.star.uno.XAggregation. After plugging the objects together, the reference count and the queryInterface() method is delegated from multiple slave objects to one master object.
It is a precondition that at the moment of aggregation, the slave object has a reference count of exactly one, which is the reference count of the master. Additionally, it does not work on proxy objects, because in Java, multiple proxy objects of the same interface of the same slave object might exist.
While aggregation allows more code reuse than implementation inheritance, the facts mentioned above, coupled with the implementation of independent objects makes programming prone to errors. Therefore the use of this concept is discourage and not explained here. For further information visit http://udk.openoffice.org/common/man/concept/unointro.html#aggregation.
The com.sun.star.lang.XUnoTunnel interface allows access to the this pointer of an object. This interface is used to cast a UNO interface that is coming back to its implementation class through a UNO method. Using this interface is a result of an unsatisfactory interface design, because it indicates that some functionality only works when non-UNO functions are used. In general, these objects cannot be replaced by a different implementation, because they undermine the general UNO interface concept. This interface can be understood as admittance to an already existing code that cannot be split into UNO components easily. If designing new services, do not use this interface.
interface XUnoTunnel: com::sun::star::uno::XInterface
{
hyper getSomething( [in] sequence< byte > aIdentifier );
};
The byte sequence contains an identifier that both the caller and implementer must know. The implementer returns the this pointer of the object if the byte sequence is equal to the byte sequence previously stored in a static variable. The byte sequence is usually generated once per process per implementation.
|
Note that the previously mentioned 'per process' is important because the this pointer of a class you know is useless, if the instance lives in a different process. |
---|
This section shows how to write Java components. The examples in this chapter are in the samples folder that was provided with the programmer's manual.
A Java component is a library of Java classes (a jar) containing objects that implement arbitrary UNO services. For a service implementation in Java, implement the necessary UNO core interfaces and the interfaces needed for your purpose. These could be existing interfaces or interfaces defined by using UNOIDL.
Besides these service implementations, Java components need two methods to instantiate the services they implement in a UNO environment: one to get single factories for each service implementation in the jar, and another one to write registration information into a registry database. These methods are called static component operations in the following:
The method that provides single factories for the service implementations in a component is __getServiceFactory():
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey)
In theory, a client obtains a single factory from a component by calling __getServiceFactory() on the component implementation directly. This is rarely done because in most cases service manager is used to get an instance of the service implementation. The service manager uses __getServiceFactory() at the component to get a factory for the requested service from the component, then asks this factory to create an instance of the one object the factory supports.
To find a requested service implementation, the service manager searches its registry database for the location of the component jar that contains this implementation. For this purpose, the component must have been registered beforehand. UNO components are able to write the necessary information on their own through a function that performs the registration and which can be called by the registration tool regcomp. The function has this signature:
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
These two methods work together to make the implementations in a component available to a service manager. The method __writeRegistryServiceInfo() tells the service manager where to find an implementation while __getServiceFactory() enables the service manager to instantiate a service implementation, once found.
The necessary steps to write a component are:
Define service implementation classes.
Implement UNO core interfaces.
Implement your own interfaces.
Provide static component operations to make your component available to a service manager.
The OpenOffice.org Java UNO environment contains Java helper classes that implement the majority of the core interfaces that are implemented by UNO components. There are two helper classes:
The helper com.sun.star.lib.uno.helper.WeakBase is the minimal base class and implements XInterface, XTypeProvider and Xweak.
The helper com.sun.star.lib.uno.helper.ComponentBase that extends WeakBase and implements XComponent.
The com.sun.star.lang.XServiceInfo is the only interface that should be implemented, but it is not part of the helpers.
Use the naming conventions described in section 4.4.3 Writing UNO Components - Core Interfaces to Implement - XServiceInfo for the service implementation. Following the rules, a service org.openoffice.test.ImageShrink should be implemented in org.openoffice.comp.test.ImageShrink.
A possible class definition that uses ComponentBase could look like this: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
package org.openoffice.comp.test;
public class ImageShrink extends com.sun.star.lib.uno.helper.ComponentBase
implements com.sun.star.lang.XServiceInfo,
org.openoffice.test.XImageShrink,
com.sun.star.document.XFilter {
com.sun.star.uno.XComponentContext xComponentContext = null;
/** Creates a new instance of ImageShrink */
public ImageShrink(com.sun.star.uno.XComponentContext XComponentContext xContext) {
this.xComponentContext = xContext;
}
...
}
If the implementation only supports one service, use the following code to implement XServiceInfo: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
...
//XServiceInfo implementation
// hold the service name in a private static member variable of the class
protected static final String __serviceName = "org.openoffice.test.ImageShrink";
public String getImplementationName( ) {
return getClass().getName();
}
public boolean supportsService(String serviceName) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
public String[] getSupportedServiceNames( ) {
String[] retValue= new String[0];
retValue[0]= __serviceName;
return retValue;
}
...
An implementation of more than one service in one UNO object is more complex. It has to return all supported service names in getSupportedServiceNames(), furthermore it must check all supported service names in supportsService(). Note that several services packaged in one component file are not discussed here, but objects supporting more than one service. Refer to : for the implementation of srv3_4.
The functionality of a component is accessible only by its interfaces. When writing a component, choose one of the available API interfaces or define an interface. IDL types are used as method arguments to other UNO objects. Java does not support unsigned data types, so their use is discouraged. In the chapter 4.2 Writing UNO Components - Using UNOIDL to Specify new Components, the org.openoffice.test.XImageShrink interface specification was written and an interface class file was created. Its implementation is straightforward, you create a class that implements your interfaces: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
package org.openoffice.comp.test;
public class ImageShrink extends com.sun.star.lib.uno.helper.ComponentBase
implements com.sun.star.lang.XServiceInfo,
org.openoffice.test.XImageShrink,
com.sun.star.document.XFilter {
...
String destDir = "";
String sourceDir = "";
boolean cancel = false;
com.sun.star.awt.Size dimension = new com.sun.star.awt.Size();
// XFilter implementation
public void cancel() {
cancel = true;
}
public boolean filter(com.sun.star.beans.PropertyValue[] propertyValue) {
// while cancel = false,
// scale images found in sourceDir according to dimension and
// write them to destDir, using the image file format given in
// []propertyValue
// (implementation omitted)
cancel = false;
return true;
}
// XIMageShrink implementation
public String getDestinationDirectory() {
return destDir;
}
public com.sun.star.awt.Size getDimension() {
return dimension;
}
public String getSourceDirectory() {
return sourceDir;
}
public void setDestinationDirectory(String str) {
destDir = str;
}
public void setDimension(com.sun.star.awt.Size size) {
dimension = size;
}
public void setSourceDirectory(String str) {
sourceDir = str;
}
...
}
For the component to run, the new interface class file must be accessible to the Java Virtual Machine. That is, it has to be in its CLASSPATH. All commonly used interfaces are contained in ridl.jar and unoil.jar that are always in the CLASSPATH because of the OpenOffice.org setup program.
The recommended method is to deliver the interface together with the component in the same jar file, or to have the interface in a separate jar or class file. In both cases, put the corresponding class with the interface into the CLASSPATH. This is achieved by editing the file java(.ini|rc) in <officepath>\user\config or through the options dialog. The java(.ini|rc) contains a SystemClasspath entry that you append the path pointing to the class or jar file. In the Options dialog, expand the OpenOffice.org node in the tree on the left-hand side and choose Security. One the right-hand side, there is a field User Classpath to add the jar or class file containing the interface.
|
It is also important that the binary type library of the new interfaces are provided together with the component, otherwise the component is not accessible from OpenOffice.org Basic. Basic uses the UNO core reflection service to get type information at runtime. The core reflection is based on the binary type library. |
---|
The component must be able to create single factories for each service implementation it contains and return them in the static component operation __getServiceFactory(). The OpenOffice.org Java UNO environment provides a Java class com.sun.star.comp.loader.FactoryHelper that creates a default implementation of a single factory through its method getServiceFactory(). The following example could be written: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
package org.openoffice.comp.test;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.comp.loader.FactoryHelper;
public class ImageShrink ... {
...
// static __getServiceFactory() implementation
// static member __serviceName was introduced above for XServiceInfo implementation
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
com.sun.star.registry.XRegistryKey regKey) {
com.sun.star.lang.XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( ImageShrink.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory(ImageShrink.class,
ImageShrink.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
...
}
The FactoryHelper is contained in the jurt jar file. The getServiceFactory() method takes as a first argument a Class object. When createInstance() is called on the default factory, it creates an instance of that Class using newInstance() on it and retrieves the implementation name through getName(). The second argument is the service name. The multiFactory and regKey arguments were received in __getServiceFactory() and are passed to the FactoryHelper.
|
In this case, the implementation name, which the default factory finds through Class.getName() is org.openoffice.comp.test.ImageShrink and the service name is org.openoffice.test.ImageShrink. The implementation name and the service name are used for the separate XServiceInfo implementation within the default factory. Not only do you support the XServiceInfo interface in your service implementation, but the single factory must implement this interface as well. |
---|
The default factory created by the FactoryHelper expects a public constructor in the implementation class of the service and calls it when it instantiates the service implementation. The constructor can be a default constructor, or it can take a com.sun.star.uno.XComponentContext or a com.sun.star.lang.XMultiServiceFactory as an argument. Refer to 4.5.7 Writing UNO Components - Simple Component in Java - Create Instance With Arguments for other arguments that are possible.
Java components are housed in jar files. When a component has been registered, the registry contains the name of the jar file, so that the service manager can find it. However, because a jar file can contain several class files, the service manager must be told which one contains the __getServiceFactory() method. That information has to be put into the jar's Manifest file, for example:
RegistrationClassName: org.openoffice.comp.test.ImageShrink
UNO components have to be registered with the registry database of a service manager. In an office installation, this is the file applicat.rdb for all predefined services. A service manager can use this database to find the implementations for a service. For instance, if an instance of your component is created using the following call.
Object imageShrink = xRemoteServiceManager.createInstance("org.openoffice.test.ImageShrink");
Using the given service or implementation name, the service manager looks up the location of the corresponding jar file in the registry and instantiates the component.
|
If you want to use the service manager of the Java UNO runtime, com.sun.star.comp.servicemanager.ServiceManager (jurt.jar), to instantiate your service implementation, then you would have to create the service manager and add the factory for “org.openoffice.test.ImageShrink” programmatically, because the Java service manager does not use the registry. Alternatively, you can use com.sun.star.comp.helper.RegistryServiceFactory from juh.jar which is registry-based. Its drawback is that it delegates to a C++ implementation of the service manager through the java-bridge. |
---|
During the registration, a component writes the necessary information into the registry. The process to write the information is triggered externally when a client calls the __writeRegistryServiceInfo() method at the component.
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
The caller passes an com.sun.star.registry.XRegistryKey interface that is used by the method to write the registry entries. Again, the FactoryHelper class offers a way to implement the method: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
...
// static __writeRegistryServiceInfo implementation
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
return FactoryHelper.writeRegistryServiceInfo( ImageShrink.class.getName(),
__serviceName, regKey);
}
The writeRegistryServiceInfo method takes three arguments:
implementation name
service name
XRegistryKey
Use tools, such as regcomp or the Java application com.sun.star.tools.uno.RegComp to register a component. These tools take the path to the jar file containing the component as an argument. Since the jar can contain several classes, the class that implements the __writeRegistryServiceInfo() method must be pointed out by means of the manifest. Again, the RegistrationClassName entry determines the correct class. For example:
RegistrationClassName: org.openoffice.comp.test.ImageShrink
The above entry is also necessary to locate the class that provides __getServiceFactory(), therefore the functions __writeRegistryServiceInfo() and __getServiceFactory() have to be in the same class.
As soon as the component implements any UNO interface, com.sun.star.uno.XInterface is included automatically. The Java interface definition generated by javamaker for com.sun.star.uno.XInterface contains a TypeInfo member used by Java UNO internally to store certain IDL type information (Refer to 3.4.1 Professional UNO - UNO Language Bindings - Java Language Binding):
// source file com/sun/star/uno/XInterface.java generated by javamaker
package com.sun.star.uno;
public interface XInterface
{
// static Member
public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = null;
}
Note that XInterface does not have any methods, in contrast to its IDL description. That means, if implements com.sun.star.uno.XInterface is added to a class definition, there is nothing to implement.
The method queryInterface() is unnecessary in a service implementation, because the Java UNO runtime environment obtains interface references without being helped by the components. Within Java, the method UnoRuntime.queryInterface() is used to obtain interfaces instead of calling com.sun.star.uno.XInterface:queryInterface(), and the Java UNO language binding hands out interfaces for services to other processes on its own as well.
The methods acquire() and release() are used for reference counting and control the lifetime of an object, because the Java garbage collector does this, there is no reference counting in Java components.
Helper classes with default com.sun.star.lang.XTypeProvider implementations are still under development for Java. Meanwhile, every Java UNO object implementation can implement the XTypeProvider interface as shown in the following code. In your implementation, adjust getTypes(): (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
...
// XTypeProvider implementation
// maintain a static implementation id for all instances of ImageShrink
// initialized by the first call to getImplementationId()
protected static byte[] _implementationId;
public com.sun.star.uno.Type[] getTypes() {
com.sun.star.uno.Type[] retValue = new com.sun.star.uno.Type[4];
// instantiate Type instances for each interface you support and add them to Type[] array
// this object implements XServiceInfo, XTypeProvider and XImageShrink
retValue[0]= new com.sun.star.uno.Type( com.sun.star.lang.XServiceInfo.class);
retValue[1]= new com.sun.star.uno.Type( com.sun.star.lang.XTypeProvider.class);
retValue[3]= new com.sun.star.uno.Type( com.sun.star.document.XFilter);
retValue[2]= new com.sun.star.uno.Type( org.openoffice.test.XImageShrink.class);
// inherited interfaces, like XInterface, are recognized implicitely
return retValue;
}
synchronized public byte[] getImplementationId() {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode(); // hashCode of this object
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
...
The suggested implementation of the getImplementationId() method is not optimal, it uses the hashCode() of the first instance that initializes the static field. The future UNO helper class will improve this.
XComponent is an optional interface that is useful when other objects hold references to the component. The notification mechanism of XComponent enables listener objects to learn when the component stops to provide its services, so that the objects drop their references to the component. This enables the component to delete itself when its reference count drops to zero. From section 4.4 Writing UNO Components - Core Interfaces to Implement, there must be three things done when dispose() is called at an XComponent:
Inform registered XEventListeners that the object is being disposed of by calling their method disposing().
Release all references the object holds, including all XEvenListener objects.
On further calls to the component, throw an com.sun.star.lang.DisposedException in case the required task can not be fulfilled anymore, because the component was disposed.
In Java, the object cannot be deleted, but the garbage collector will do this. It is sufficient to release all references that are currently being held to break the cyclic reference, and to call disposing() on all com.sun.star.lang.XEventListeners.
The registration and removal of listener interfaces is a standard procedure in Java. Some IDEs even create the necessary methods automatically. The following example could be written: (Components/Thumbs/org/openoffice/comp/test/ImageShrink.java)
...
//XComponent implementation
// hold a Vector of eventListeners in the class
private transient Vector eventListeners;
void dispose {
fireDisposing(new com.sun.star.lang.EventObject(this))
releaseReferences();
}
public synchronized void addEventListener(XEventListener listener) {
if ( eventListeners == 0 )
eventListeners = new Vector(2);
if ( !eventListeners.contains(listener) )
eventListeners.addElement(listener);
}
public synchronized void removeEventListener(XEventListener listener) {
if ( eventListeners != 0 )
eventListeners.removeElement(listener);
}
protected void fireDisposing(com.sun.star.lang.EventObject e) {
if (eventListeners != null) {
Vector listeners = eventListeners ;
int count = listeners.size();
for (int i = 0; i < count; i++) {
((XEventListener) listeners.elementAt(i)).disposing(e);
}
}
}
protected void releaseReferences() {
xComponentContext = null;
// ...
}
...
A component usually runs in the office process. There is no need to create an interprocess channel explicitly. A component does not have to create a service manager, because it is provided to the single factory of an implementation by the service manager during a call to createInstance() or createInstanceWithContext(). The single factory receives an XComponentContext or an XMultiServiceFactory, and passes it to the corresponding constructor of the service implementation. From the component context, the implementation gets the service manager using getServiceManager() at the com.sun.star.uno.XComponentContext interface.
A factory can create an instance of components and pass additional arguments. To do that, a client calls the createInstanceWithArguments() function of the com.sun.star.lang.XSingleServiceFactory interface or the createInstanceWithArgumentsAndContext() of the com.sun.star.lang.XSingleComponentFactory interface.
//javamaker generated interface
//XSingleServiceFactory interface
public java.lang.Object createInstanceWithArguments(java.lang.Object[] aArguments)
throws com.sun.star.uno.Exception;
//XSingleComponentFactory
public java.lang.Object createInstanceWithArgumentsAndContext(java.lang.Object[] Arguments,
com.sun.star.uno.XComponentContext Context)
throws com.sun.star.uno.Exception;
Both functions take an array of values as an argument. A component implements the com.sun.star.lang.XInitialization interface to receive the values. A factory passes the array on to the single method initialize() supported by XInitialization.
public void initialize(java.lang.Object[] aArguments) throws com.sun.star.uno.Exception;
Alternatively, a component may also receive these arguments in its constructor. If a factory is written, determine exactly which arguments are provided by the factory when it instantiates the component. When using the FactoryHelper, implement the constructors with the following arguments:
First Argument |
Second Argument |
Third Argument |
---|---|---|
com.sun.star.uno.XComponentContext |
com.sun.star.registry.XRegistryKey |
java.lang.Object[] |
com.sun.star.uno.XComponentContext |
com.sun.star.registry.XRegistryKey |
|
com.sun.star.uno.XComponentContext |
java.lang.Object[] |
|
com.sun.star.uno.XComponentContext |
|
|
java.lang.Object[] |
|
|
The FactoryHelper automatically passes the array of arguments it received from the createInstanceWithArguments[AndContext]() call to the appropriate constructor. Therefore, it is not always necessary to implement XInitialization to use arguments.
The implementation of a component depends on the needs of the implementer. The following examples show some possible ways to assemble a component. There can be one implemented object or several implemented objects per component file.
There are additional options if implementing one service per component file:
Use a flat structure with the static component operations added to the service implementation class directly.
Reserve the class with the implementation name for the static component operation and use an inner class to implement the service.
An implementation class contains the static component operations. The following sample implements an interface com.sun.star.test.XSomething in an implementation class JavaComp.TestComponent:
// UNOIDL: interface example specification
module com { module sun { module star { module test {
interface XSomething: com::sun::star::uno::XInterface
{
string methodOne([in]string val);
};
}; }; }; };
A component that implements only one service supporting XSomething can be assembled in one class as follows:
package JavaComp;
...
public class TestComponent implements XSomething, XTypeProvider, XServiceInfo {
public static final String __serviceName="com.sun.star.test.JavaTestComponent";
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory, XRegistryKey regKey) {
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( TestComponent.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory( TestComponent.class,
TestComponent.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey){
return FactoryHelper.writeRegistryServiceInfo( TestComponent.class.getName(),
TestComponent.__serviceName, regKey);
}
// XSomething
string methodOne(String val) {
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
...
}
// XTypeProvider
public byte[] getImplementationId( ) {
...
}
//XServiceInfo
public String getImplementationName( ) {
...
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
...
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
...
}
}
The class implements the XSomething interface. The IDL description and documentation provides information about its functionality. The class also contains the functions for factory creation and registration, therefore the manifest entry must read as follows:
RegistrationClassName: JavaComp.TestComponent
To implement the component as inner class of the one that provides the service factory through __getServiceFactory(), it must be a static inner class, otherwise the factory provided by the FactoryHelper cannot create the component. An example for an inner implementation class is located in the sample com.sun.star.comp.demo.DemoComponent.java provided with the SDK. The implementation of __getServiceFactory() and __writeRegistryServiceInfo() is omitted here, because they act the same as in the implementation class with component operations above.
package com.sun.star.comp.demo;
public class DemoComponent {
...
// static inner class implements service com.sun.star.demo.DemoComponent
static public class _Implementation implements XTypeProvider,
XServiceInfo, XInitialization, XWindowListener,
XActionListener, XTopWindowListener {
static private final String __serviceName = "com.sun.star.demo.DemoComponent";
private XMultiServiceFactory _xMultiServiceFactory;
// Constructor
public _Implementation(XMultiServiceFactory xMultiServiceFactory) {
}
}
// static method to get a single factory creating the given service from the factory helper
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey) {
...
}
// static method to write the service information into the given registry key
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
...
}
}
The manifest entry for this implementation structure again has to point to the class with the static component operations:
RegistrationClassName: com.sun.star.comp.demo.DemoComponent
To assemble several service implementations in one component file, implement each service in its own class and add a separate class containing the static component operations. The following code sample features two services: TestComponentA and TestComponentB implementing the interfaces XSomethingA and XSomethingB with a separate static class TestServiceProvider containing the component operations.
The following are the UNOIDL specifications for XSomethingA and XSomethingB:
module com { module sun { module star { module test {
interface XSomethingA: com::sun::star::uno::XInterface
{
string methodOne([in]string value);
};
}; }; }; };
module com { module sun { module star { module test {
interface XSomethingB: com::sun::star::uno::XInterface
{
string methodTwo([in]string value);
};
}; }; }; };
TestComponentA implements XSomethingA: (Components/JavaComp/TestComponentA.java):
package JavaComp;
public class TestComponentA implements XTypeProvider, XServiceInfo, XSomethingA {
static final String __serviceName= "JavaTestComponentA";
static byte[] _implementationId;
public TestComponentA() {
}
// XSomethingA
public String methodOne(String val) {
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
Type[] retValue= new Type[3];
retValue[0]= new Type( XServiceInfo.class);
retValue[1]= new Type( XTypeProvider.class);
retValue[2]= new Type( XSomethingA.class);
return retValue;
}
//XTypeProvider
synchronized public byte[] getImplementationId( ) {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode();
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
//XServiceInfo
public String getImplementationName( ) {
return getClass().getName();
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
String[] retValue= new String[0];
retValue[0]= __serviceName;
return retValue;
}
}
TestComponentB implements XSomethingB. Note that it receives the component context and initialization arguments in its constructor. (Components/JavaComp/TestComponentB.java)
package JavaComp;
public class TestComponentB implements XTypeProvider, XServiceInfo, XSomethingB {
static final String __serviceName= "JavaTestComponentB";
static byte[] _implementationId;
private XComponentContext context;
private Object[] args;
public TestComponentB(XComponentContext context, Object[] args) {
this.context= context;
this.args= args;
}
// XSomethingB
public String methodTwo(String val) {
if (args.length > 0 && args[0] instanceof String )
return (String) args[0];
return val;
}
//XTypeProvider
public com.sun.star.uno.Type[] getTypes( ) {
Type[] retValue= new Type[3];
retValue[0]= new Type( XServiceInfo.class);
retValue[1]= new Type( XTypeProvider.class);
retValue[2]= new Type( XSomethingB.class);
return retValue;
}
//XTypeProvider
synchronized public byte[] getImplementationId( ) {
if (_implementationId == null) {
_implementationId= new byte[16];
int hash = hashCode();
_implementationId[0] = (byte)(hash & 0xff);
_implementationId[1] = (byte)((hash >>> 8) & 0xff);
_implementationId[2] = (byte)((hash >>> 16) & 0xff);
_implementationId[3] = (byte)((hash >>>24) & 0xff);
}
return _implementationId;
}
//XServiceInfo
public String getImplementationName( ) {
return getClass().getName();
}
// XServiceInfo
public boolean supportsService( /*IN*/String serviceName ) {
if ( serviceName.equals( __serviceName))
return true;
return false;
}
//XServiceInfo
public String[] getSupportedServiceNames( ) {
String[] retValue= new String[0];
retValue[0]= __serviceName;
return retValue;
}
}
TestServiceProvider implements __getServiceFactory() and __writeRegistryServiceInfo(): (Components/JavaComp/TestServiceProvider.java)
package JavaComp;
...
public class TestServiceProvider
{
public static XSingleServiceFactory __getServiceFactory(String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey) {
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals( TestComponentA.class.getName()) )
xSingleServiceFactory = FactoryHelper.getServiceFactory( TestComponentA.class,
TestComponentA.__serviceName, multiFactory, regKey);
else if (implName.equals(TestComponentB.class.getName()))
xSingleServiceFactory= FactoryHelper.getServiceFactory( TestComponentB.class,
TestComponentB.__serviceName, multiFactory, regKey);
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey){
boolean bregA= FactoryHelper.writeRegistryServiceInfo( TestComponentA.class.getName(),
TestComponentA.__serviceName, regKey);
boolean bregB= FactoryHelper.writeRegistryServiceInfo( TestComponentB.class.getName(),
TestComponentB.__serviceName, regKey);
return bregA && bregB;
}
}
The corresponding manifest entry must point to the static class with the component operations, in this case JavaComp.TestServiceProvider:
RegistrationClassName: JavaComp.TestServiceProvider
The service manager with a registry database containing information about the location of the component file and the types used must be provided to test the component with the office. There are several ways to accomplish the registration. The following is a possibility.
A .rdb file is created with all the necessary information and the service manager is told to use it in addition to the standard applicat.rdb. The advantage of proceeding this way is that the applicat.rdb does not become cluttered with the production office installation with test registrations and abandoned type information. Follow the steps below:
Note, it errors are encountered, refer to the troubleshooting section at the end of this chapter.
Register Component File
This step creates a registry file that contains the location of the component file and all the necessary type information. To register, place a few files to the proper locations:
Copy the regcomp tool from the SDK distribution to <OfficePath>/program.
Copy the component jar to <OfficePath>/program/classes.
Copy the .rdb file containing the new types created to <OfficePath>/program. If new types were not defined, dismiss this step. In this case, regcomp automatically creates a new rdb file with registration information.
On the command prompt, change to <OfficePath>/program, then run regcomp with the following options. Line breaks were applied to improve readability, but the command must be entered in a single line:
$ regcomp -register -r <your_registry>.rdb
-br applicat.rdb
-l com.sun.star.loader.Java2
-c file:///<OfficePath>/program/classes/<your_component>.jar
For the org.openoffice.test.ImageShrink service whose type description was merged into thumbs.rdb , which is implemented in thumbs.jar, the corresponding command would be:
$ regcomp -register -r thumbs.rdb
-br applicat.rdb
-l com.sun.star.loader.Java2
-c file:///i:/StarOffice6.0/program/classes/thumbs.jar
Instead of regcomp, there is also a Java tool to register components, however, it can only write to the same registry it reads from. It cannot be used to create a separate registry database. For details, see the section 4.7 Writing UNO Components - Deployment Options for Components.
Make Registration available to OpenOffice.org
OpenOffice.org must be told to use the registry. Close all OpenOffice.org parts, including the Quickstarter that runs in the Windows task bar. Edit the file uno(.ini|rc) in <OfficePath>/program as follows:
[Bootstrap]
UNO_TYPES=$SYSBINDIR/applicat.rdb $SYSBINDIR/<your_registry>.rdb
UNO_SERVICES=$SYSBINDIR/applicat.rdb $SYSBINDIR/<your_registry>.rdb
For details about the syntax of uno(.ini|rc) and alternative registration procedures, refer to the section 4.7 Writing UNO Components - Deployment Options for Components. If OpenOffice.org is restarted, the component should be available.
Test the Registration
A short OpenOffice.org Basic program indicates if the program runs went smoothly, by selecting Tools – Macro and entering a new macro name on the left, such as TestImageShrink and click New to create a new procedure. In the procedure, enter the appropriate code of the component. The test routine for ImageShrink would be:
Sub TestImageShrink
oTestComp = createUnoService("org.openoffice.test.ImageShrink")
MsgBox oTestComp.dbg_methods
MsgBox oTestComp.dbg_properties
MsgBox oTestComp.dbg_supportedInterfaces
end sub
The result should be three dialogs showing the methods, properties and interfaces supported by the implementation. Note that the interface attributes do not appear as get/set methods, but as properties in Basic. If the dialogs do not show what is expected, refer to the section 4.5.9 Writing UNO Components - Simple Component in Java - Testing and Debugging Java Components - Troubleshooting.
To increase turnaround cycles and source level debugging, configure the IDE to use GNU makefiles for code generation and prepare OpenOffice.org for Java debugging. If NetBeans are used, the following steps are necessary:
Support for GNU make
A NetBeans extension, available on makefile.netbeans.org, that adds basic support for GNU makefiles. When it is enabled, edit the makefile in the IDE and use the makefile to build. To install and enable this module, select Tools – Setup Wizard and click Next to go to the Module installation page. Find the module Makefiles and change the corresponding entry to True in the Enabled column. Finish using the setup wizard. If the module is not available in the installation, use Tools – Update Center to get the module from www.netbeans.org. A new entry, Makefile Support, appears in the online help when Help – Contents is selected. Makefile Support provides further configuration options. The settings Run a Makefile and Test a Makefile can be found in Tools – Options – Uncategorized – Compiler Types and – Execution Types.
Put the makefile into the project source folder that was mounted when the project was created. To build the project using the makefile, highlight the makefile in the Explorer and press F11.
Documentation for GNU make command-line options and syntax are available at www.gnu.org. The sample Thumbs in the samples folder along with this manual contains a makefile that with a few adjustments is useful for Java components.
Component Debugging
If NetBeans or Forte for Java is used, the Java Virtual Machine (JVM) that is launched by OpenOffice.org can be attached. Configure the JVM used by OpenOffice.org to listen for debugger connections. First close any open OpenOffice.org windows including the Quickstarter, then edit the section [JAVA] of the file java(.ini|rc) in <OfficePath>/user/config by adding:
-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
The last line causes the JVM to listen for a debugger on port 8000. The JVM starts listening as soon as it runs and does not wait until a debugger connects to the JVM. Launch the office and instantiate the Java component, so that the office invokes the JVM in listening mode.
Once a Java component is instantiated, the JVM keeps listening even if the component goes out of scope. Open the appropriate source file in the NetBeans editor and set breakpoints as needed. Choose Debug - Attach, select Java Platform Debugger Architecture (JPDA) as debugger type and SocketAttach (Attaches by socket to other VMs) as the connector. The Host should be localhost and the Port must be 8000. Click OK to connect the Java Debugger to the JVM the office has started previously step.
Once the debugger connects to the running JVM, NetBeans switches to debug mode, the output windows shows a message that a connection on port 8000 is established and threads are visible, as if the debugging was local. If necessary, start your component once again. As soon as the component reaches a breakpoint in the source code, the source editor window opens with the breakpoint highlighted by a green arrow.
If the component encounters problems, review the following checklist to check if the component is configured correctly.
Check Registry Keys
To check if the registry database is correctly set up, run regview against the three keys that make up a registration in the /UCR, /SERVICES and /IMPLEMENTATIONS branch of a registry database. The following examples show how to read the appropriate keys and how a proper configuration should look. In our example, service ImageShrink, and the key /UCR/org/openoffice/test/XImageShrink contain the type information specified in UNOIDL:
# dump XImageShrink type information
$ regview thumbs.rdb /UCR/org/openoffice/test/XImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/UCR/org/openoffice/test/XImageShrink
Value: Type = RG_VALUETYPE_BINARY
Size = 364
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: "X:\SO\sdk\examples\java\Thumbs\org\openoffice\test\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
The /SERVICES/org.openoffice.test.ImageShrink key must point to the implementation name org.openoffice.comp.test.ImageShrink that was chosen for this service:
# dump service name registration
$ regview thumbs.rdb /SERVICES/org.openoffice.test.ImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/SERVICES/org.openoffice.test.ImageShrink
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 45
Len = 1
Data = 0 = "org.openoffice.comp.test.ImageShrink"
Finally, the /IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink key must contain the loader and the location of the component jar:
# dump implementation name registration
$ regview thumbs.rdb /IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink
Registry "file:///X:/office60eng/program/thumbs.rdb":
/IMPLEMENTATIONS/org.openoffice.comp.test.ImageShrink
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 26
Data = "com.sun.star.loader.Java2"
/ SERVICES
/ org.openoffice.test.ImageShrink
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 50
Data = "file:///X:/office60eng/program/classes/thumbs.jar"
If the UCR key is missing, the problem is with regmerge. The most probable cause are missing .urd files. Be careful when writing the makefile. If .urd files are missing when regmerge is launched by the makefile, regmerge continues and creates a barebone .rdb file, sometimes without any type info.
If regview can not find the /SERVICES and /IMPLEMENTATIONS keys or they have the wrong content, the problem occurred when regcomp was run. This can be caused by wrong path names in the regcomp arguments.
Also, a wrong SystemClasspath setup in java(.ini|rc) could be the cause of regcomp error messages about missing classes. Check what the SystemClasspath entry in java(.ini|rc) specifies for the Java UNO runtime jars.
Ensure that regcomp is being run from the current directory when registering Java components. In addition, ensure <OfficePath>/program is the current folder when regcomp is run. Verify that regcomp is in the current folder.
Check the Java VM settings
Whenever the VM service is instantiated by OpenOffice.org, it uses the Java configuration settings in OpenOffice.org. This happens during the registration of Java components, therefore make sure that Java is enabled. Choose Tools-Options in OpenOffice.org, so that the dialog appears. Expand the OpenOffice.org node and select Security. Select the Enable checkbox in the Java section and click OK.
Check the Manifest
Make sure the manifest file contains the correct entry for the registration class name. The file must contain the following line:
RegistrationClassName: <full name of package and class>
The registration class name must be the one that implements the __writeRegistryServiceInfo() and __getServiceFactory() methods. The RegistrationClassName to be entered in the manifest for our example is org.openoffice.comp.test.ImageShrink.
Adjust CLASSPATH for Additional Classes
OpenOffice.org maintains its own system classpath and a user classpath when it starts the Java VM for Java components. The jar file that contains the service implementation is not required in the system or user classpath. If other jar files or classes are depended on and they are not part of the Java UNO runtime jars, they must be in the classpath. To correct the problem, edit the java(.ini|rc) file in <OfficePath>/user/config and add the jars or directories to the SystemClasspath entry or use Tools – Options – OpenOffice.org - Security to add them to the user classpath.
Disable Debug Options
If the debug options (-Xdebug, -Xrunjdwp) are in the java(.ini|rc) file, disable them by putting semicolons at the beginning of the respective lines. The regcomp may hang, because the JVM is waiting for a debugger to be attached.
In this section, a sample component containing two service implementations with helpers and without helpers implemented are presented. The complete source code and the gnu makefile are in samples/simple_cpp_component.
The first step for the C++ component is to define a language-independent interface, so that the UNO object can communicate with others. The IDL specification for the component defines one interface my_module.XSomething and two services implementing this interface. In addition, the second service called my_module.MyService2 implements the com.sun.star.lang.XInitialization interface, so that MyService2 can be instantiated with arguments passed to it during runtime.
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/lang/XInitialization.idl>
module my_module
{
interface XSomething : com::sun::star::uno::XInterface
{
string methodOne( [in] string val );
};
service MyService1
{
interface XSomething;
};
service MyService2
{
interface XSomething;
interface com::sun::star::lang::XInitialization;
};
};
This IDL is compiled to produce a binary type library file (.urd file), by executing the following commands. The types are compiled and merged into a registry simple_component.rdb, that will be linked into the OpenOffice.org installation later.
$ idlc -I<SDK>/idl some.idl
$ regmerge simple_component.rdb /UCR some.urd
The cppumaker tool must be used to map IDL to C++:
$ cppumaker -BUCR -Tmy_module.XSomething <SDK>/bin/applicat.rdb simple_component_rdb
For each given type, a pair of header files is generated, a .hdl and a .hpp file. To avoid conflicts, all C++ declarations of the type are in the .hdl and all definitions, such as constructors, are in the .hpp file. The .hpp is the one to include for any type used in C++.
The next step is to implement the core interfaces, and the implementation of the component operations component_getFactory(), component_writeInfo() and component_getImplementationEnvironment()with or without helper methods.
The SDK offers helpers for ease of developing. There are implementation helper template classes that deal with the implementation of com.sun.star.uno.XInterface and com.sun.star.lang.XTypeProvider, as well as com.sun.star.uno.XWeak. These classes let you focus on the interfaces you want to implement.
The implementation of my_module.MyService2 uses the ::cppu::WeakImplHelper3<> helper. The “3” stands for the number of interfaces to implement. The class declaration inherits from this template class which takes the interfaces to implement as template parameters. (Components/simple_cpp_component/service2_impl.cxx)
#include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <my_module/XSomething.hpp>
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for sdk interfaces
using namespace ::com::sun::star::uno; // for basic types
namespace my_sc_impl {
class MyService2Impl : public ::cppu::WeakImplHelper3< ::my_module::XSomething,
lang::XServiceInfo,
lang::XInitialization >
{
...
};
}
The next section focusses on coding com.sun.star.lang.XServiceInfo, com.sun.star.lang.XInitialization and the sample interface my_module.XSomething.
The cppuhelper shared library provides additional implementation helper classes, for example, supporting com.sun.star.lang.XComponent. Browse the ::cppu namespace in the C++ reference of the SDK or on udk.openoffice.org.
An UNO service implementation supports com.sun.star.lang.XServiceInfo providing information about its implementation name and supported services. The implementation name is a unique name referencing the specific implementation. In this case, my_module.my_sc_impl.MyService1 and my_module.my_sc_impl.MyService2 respectively. The implementation name is used later when registering the implementation into the simple_component.rdb registry used for OpenOffice.org. It links a service name entry to one implementation, because there may be more than one implementation. Multiple implementations of the same service may have different characteristics, such as runtime behavior and memory footprint.
Our service instance has to support the com.sun.star.lang.XServiceInfo interface. This interface has three methods, and can be coded for one supported service as follows: (Components/simple_cpp_component/service2_impl.cxx)
// XServiceInfo implementation
OUString MyService2Impl::getImplementationName()
throw (RuntimeException)
{
// unique implementation name
return OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") );
}
sal_Bool MyService2Impl::supportsService( OUString const & serviceName )
throw (RuntimeException)
{
// this object only supports one service, so the test is simple
return serviceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("my_module.MyService2") );
}
Sequence< OUString > MyService2Impl::getSupportedServiceNames()
throw (RuntimeException)
{
return getSupportedServiceNames_MyService2Impl();
}
For the my_module.XSomething interface, add a string to be returned that informs the caller when methodOne() was called successfully . (Components/simple_cpp_component/service2_impl.cxx)
OUString MyService2Impl::methodOne( OUString const & str )
throw (RuntimeException)
{
return OUString( RTL_CONSTASCII_USTRINGPARAM(
"called methodOne() of MyService2 implementation: ") ) + str;
}
C++ component libraries must export an external "C" function called component_getFactory() that supplies a factory object for the given implementation. Use ::cppu::component_getFactoryHelper() to create this function. The declarations for it are included through cppuhelper/implementationentry.hxx.
The component_getFactory() method appears at the end of the following listing. This method assumes that the component includes a static ::cppu::ImplementationEntry array s_component_entries[], which contains a number of function pointers. The listing shows how to write the component, so that the function pointers for all services of a multi-service component are correctly initialized. (Components/simple_cpp_component/service2_impl.cxx)
#include <cppuhelper/implbase3.hxx> // "3" implementing three interfaces
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implementationentry.hxx>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <my_module/XSomething.hpp>
using namespace ::rtl; // for OUString
using namespace ::com::sun::star; // for sdk interfaces
using namespace ::com::sun::star::uno; // for basic types
namespace my_sc_impl
{
class MyService2Impl : public ::cppu::WeakImplHelper3<
::my_module::XSomething, lang::XServiceInfo, lang::XInitialization >
{
OUString m_arg;
public:
// focus on three given interfaces,
// no need to implement XInterface, XTypeProvider, XWeak
// XInitialization will be called upon createInstanceWithArguments[AndContext]()
virtual void SAL_CALL initialize( Sequence< Any > const & args )
throw (Exception);
// XSomething
virtual OUString SAL_CALL methodOne( OUString const & str )
throw (RuntimeException);
// XServiceInfo
virtual OUString SAL_CALL getImplementationName()
throw (RuntimeException);
virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
throw (RuntimeException);
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
throw (RuntimeException);
};
// Implementation of XSomething, XServiceInfo and XInitilization omitted here:
...
// component operations from service1_impl.cxx
extern Sequence< OUString > SAL_CALL getSupportedServiceNames_MyService1Impl();
extern OUString SAL_CALL getImplementationName_MyService1Impl();
extern Reference< XInterface > SAL_CALL create_MyService1Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () );
// component operations for MyService2Impl
static Sequence< OUString > getSupportedServiceNames_MyService2Impl()
{
static Sequence < OUString > *pNames = 0;
if( ! pNames )
{
if( !pNames )
{
static Sequence< OUString > seqNames(1);
seqNames.getArray()[0] = OUString(RTL_CONSTASCII_USTRINGPARAM("my_module.MyService2"));
pNames = &seqNames;
}
}
return *pNames;
}
static OUString getImplementationName_MyService2Impl()
{
static OUString *pImplName = 0;
if( ! pImplName )
{
if( ! pImplName )
{
static OUString implName(
RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_implementation.MyService2") );
pImplName = &implName;
}
}
return *pImplName;
}
Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}
/* shared lib exports implemented with helpers */
static struct ::cppu::ImplementationEntry s_component_entries [] =
{
{
create_MyService1Impl, getImplementationName_MyService1Impl,
getSupportedServiceNames_MyService1Impl, ::cppu::createSingleComponentFactory,
0, 0
},
{
create_MyService2Impl, getImplementationName_MyService2Impl,
getSupportedServiceNames_MyService2Impl, ::cppu::createSingleComponentFactory,
0, 0
},
{ 0, 0, 0, 0, 0, 0 }
};
}
extern "C"
{
void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr,
registry::XRegistryKey * xRegistry )
{
return ::cppu::component_getFactoryHelper(
implName, xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
// getImplementationEnvironment and component_writeInfo are described later, we omit them here
...
}
The static variable s_component_entries defines a null-terminated array of entries concerning the service implementations of the shared library. A service implementation entry consists of function pointers for
object creation: create_MyServiceXImpl()
implementation name: getImplementationName_MyServiceXImpl()
supported service names: getSupportedServiceNames_MyServiceXImpl()
factory helper to be used: ::cppu::createComponentFactory()
The last two values are reserved for future use and therefore can be 0.
Use ::cppu::component_writeInfoHelper() to implement component_writeInfo(): This function is called by regcomp during the registration process. [ScOURCE:Components/simple_cpp_component/service2_impl.cxx]
extern "C" sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry )
{
return ::cppu::component_writeInfoHelper(
xMgr, xRegistry, ::my_sc_impl::s_component_entries );
}
Note that component_writeInfoHelper() uses the same array of ::cppu::ImplementationEntry structs as component_getFactory(),that is, s_component_entries.
The function called component_getImplementationEnvironment() tells the shared library component loader which compiler was used to build the library. This information is required if different components have been compiled with different compilers. A specific C++-compiler is called an environment. If different compilers were used, the loader has to bridge interfaces from one compiler environment to another, building the infrastructure of communication between those objects. It is mandatory to have the appropriate C++ bridges installed into the UNO runtime. In most cases, the function mentioned above can be implemented this way: (Components/simple_cpp_component/service2_impl.cxx)
extern "C" void SAL_CALL component_getImplementationEnvironment(
sal_Char const ** ppEnvTypeName, uno_Environment ** ppEnv )
{
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}
The macro CPPU_CURRENT_LANGUAGE_BINDING_NAME is a C string defined by the compiling environment, if you use the SDK compiling environment. For example, when compiling with the Microsoft Visual C++ compiler, it defines to "msci", but when compiling with the GNU gcc 3, it defines to "gcc3".
In the following section, possible implementations without helpers are presented. This is useful if more interfaces are to be implemented than planned by the helper templates. The helper templates only allow up to ten interfaces. Also included in this section is how the core interfaces work.
Object lifetime is controlled through the common base interface com.sun.star.uno.XInterface methods acquire() and release(). These are implemented using reference-counting, that is, upon each acquire(), the counter is incremented and upon each release(), it is decreased. On last decrement, the object dies. Programming in a thread-safe manner, the modification of this counter member variable is commonly performed by a pair of sal library functions called osl_incrementInterlockedcount() and osl_decrementInterlockedcount() (include osl/interlck.h). (Components/simple_cpp_component/service1_impl.cxx)
|
Be aware of symbol conflicts when writing code. It is common practice to wrap code into a separate namespace, such as "my_sc_impl". The problem is that symbols may clash during runtime on Unix when your shared library is loaded. |
---|
namespace my_sc_impl
{
class MyService1Impl
...
{
oslInterlockedCount m_refcount;
public:
inline MyService1Impl() throw ()
: m_refcount( 0 )
{}
// XInterface
virtual Any SAL_CALL queryInterface( Type const & type )
throw (RuntimeException);
virtual void SAL_CALL acquire()
throw ();
virtual void SAL_CALL release()
throw ();
...
};
void MyService1Impl::acquire()
throw ()
{
// thread-safe incrementation of reference count
::osl_incrementInterlockedCount( &m_refcount );
}
void MyService1Impl::release()
throw ()
{
// thread-safe decrementation of reference count
if (0 == ::osl_decrementInterlockedCount( &m_refcount ))
{
delete this; // shutdown this object
}
}
In the queryInterface() method, interface pointers have to be provided to the interfaces of the object. That means, cast this to the respective pure virtual C++ class generated by the cppumaker tool for the interfaces. All supported interfaces must be returned, including inherited interfaces like XInterface. (Components/simple_cpp_component/service1_impl.cxx)
Any MyService1Impl::queryInterface( Type const & type )
throw (RuntimeException)
{
if (type.equals( ::getCppuType( (Reference< XInterface > const *)0 ) ))
{
// return XInterface interface (resolve ambiguity caused by multiple inheritance from
// XInterface subclasses by casting to lang::XTypeProvider)
Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
return makeAny( x );
}
if (type.equals( ::getCppuType( (Reference< lang::XTypeProvider > const *)0 ) ))
{
// return XInterface interface
Reference< XInterface > x( static_cast< lang::XTypeProvider * >( this ) );
return makeAny( x );
}
if (type.equals( ::getCppuType( (Reference< lang::XServiceInfo > const *)0 ) ))
{
// return XServiceInfo interface
Reference< lang::XServiceInfo > x( static_cast< lang::XServiceInfo * >( this ) );
return makeAny( x );
}
if (type.equals( ::getCppuType( (Reference< ::my_module::XSomething > const *)0 ) ))
{
// return sample interface
Reference< ::my_module::XSomething > x( static_cast< ::my_module::XSomething * >( this ) );
return makeAny( x );
}
// querying for unsupported type
return Any();
}
When implementing the com.sun.star.lang.XTypeProvider interface, two methods have to be coded. The first one, getTypes() provides all implemented types of the implementation, excluding base types, such as com.sun.star.uno.XInterface. The second one, getImplementationId() provides a unique ID for this set of interfaces. A thread-safe implementation of the above mentioned looks like the following example: (Components/simple_cpp_component/service1_impl.cxx)
Sequence< Type > MyService1Impl::getTypes()
throw (RuntimeException)
{
Sequence< Type > seq( 3 );
seq[ 0 ] = ::getCppuType( (Reference< lang::XTypeProvider > const *)0 );
seq[ 1 ] = ::getCppuType( (Reference< lang::XServiceInfo > const *)0 );
seq[ 2 ] = ::getCppuType( (Reference< ::my_module::XSomething > const *)0 );
return seq;
}
Sequence< sal_Int8 > MyService1Impl::getImplementationId()
throw (RuntimeException)
{
static Sequence< sal_Int8 > * s_pId = 0;
if (! s_pId)
{
// create unique id
Sequence< sal_Int8 > id( 16 );
::rtl_createUuid( (sal_uInt8 *)id.getArray(), 0, sal_True );
// guard initialization with some mutex
::osl::MutexGuard guard( ::osl::Mutex::getGlobalMutex() );
if (! s_pId)
{
static Sequence< sal_Int8 > s_id( id );
s_pId = &s_id;
}
}
return *s_pId;
}
|
Take a look at the thread-safe initialization of the implementation ID. A common pattern is to test a static pointer that is modified by one atom write. Using the same pattern, you can do a static initialization of the types sequence. This has been omitted for simplicity. |
---|
The function component_getFactory() provides a single object factory for the requested implementation, that is, it provides a factory that creates object instances of one of the service implementations. Using a helper from cppuhelper/factory.hxx, this is implemented quickly in the following code: (Components/simple_cpp_component/service1_impl.cxx)
#include <cppuhelper/factory.hxx>
namespace my_sc_impl
{
...
static Reference< XInterface > SAL_CALL create_MyService1Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService1Impl() );
}
static Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
return static_cast< lang::XTypeProvider * >( new MyService2Impl() );
}
}
extern "C" void * SAL_CALL component_getFactory(
sal_Char const * implName, lang::XMultiServiceFactory * xMgr, void * )
{
Reference< lang::XSingleComponentFactory > xFactory;
if (0 == ::rtl_str_compare( implName, "my_module.my_sc_impl.MyService1" ))
{
// create component factory for MyService1 implementation
OUString serviceName( RTL_CONSTASCII_USTRINGPARAM("my_module.MyService1") );
xFactory = ::cppu::createSingleComponentFactory(
::my_sc_impl::create_MyService1Impl,
OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService1") ),
Sequence< OUString >( &serviceName, 1 ) );
}
else if (0 == ::rtl_str_compare( implName, "my_module.my_sc_impl.MyService2" ))
{
// create component factory for MyService12 implementation
OUString serviceName( RTL_CONSTASCII_USTRINGPARAM("my_module.MyService2") );
xFactory = ::cppu::createSingleComponentFactory(
::my_sc_impl::create_MyService2Impl,
OUString( RTL_CONSTASCII_USTRINGPARAM("my_module.my_sc_impl.MyService2") ),
Sequence< OUString >( &serviceName, 1 ) );
}
if (xFactory.is())
xFactory->acquire();
return xFactory.get(); // return acquired interface pointer or null
}
In the example above, note the function ::my_sc_impl::create_MyService1Impl() that is called by the factory object when it needs to instantiate the class. A component context com.sun.star.uno.XComponentContext is provided to the function, which may be passed to the constructor of MyService1Impl.
The function component_writeInfo() is called by the shared library component loader upon registering the component into a registry database file (.rdb). The component writes information about objects it can instantiate into the registry when it is called by regcomp. (Components/simple_cpp_component/service1_impl.cxx)
extern "C" sal_Bool SAL_CALL component_writeInfo(
lang::XMultiServiceFactory * xMgr, registry::XRegistryKey * xRegistry )
{
if (xRegistry)
{
try
{
// implementation of MyService1A
Reference< registry::XRegistryKey > xKey(
xRegistry->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.my_sc_impl.MyService1/UNO/SERVICES") ) ) );
// subkeys denote implemented services of implementation
xKey->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.MyService1") ) );
// implementation of MyService1B
xKey = xRegistry->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.my_sc_impl.MyService2/UNO/SERVICES") ) );
// subkeys denote implemented services of implementation
xKey->createKey( OUString( RTL_CONSTASCII_USTRINGPARAM(
"my_module.MyService2") ) );
return sal_True; // success
}
catch (registry::InvalidRegistryException &)
{
// function fails if exception caught
}
}
return sal_False;
}
The single factories expect a static create_<ImplementationClass>() function. For instance, create_MyService1Impl()takes a reference to the component context and instantiates the implementation class using new ImplementationClass(). A constructor can be written for <ImplementationClass> that expects a reference to an com.sun.star.uno.XComponentContext and stores the reference in the instance for further use.
static Reference< XInterface > SAL_CALL create_MyService2Impl(
Reference< XComponentContext > const & xContext )
SAL_THROW( () )
{
// passing the component context to the constructor of MyService2Impl
return static_cast< lang::XTypeProvider * >( new MyService2Impl( xContext ) );
}
If the service should be raised passing arguments through com.sun.star.lang.XMultiComponentFactory:createInstanceWithArgumentsAndContext() and com.sun.star.lang.XMultiServiceFactory:createInstanceWithArguments(), it has to implement the interface com.sun.star.lang.XInitialization. The second service my_module.MyService2 implements it, expecting a single string as an argument. (Components/simple_cpp_component/service2_impl.cxx)
// XInitialization implementation
void MyService2Impl::initialize( Sequence< Any > const & args )
throw (Exception)
{
if (1 != args.getLength())
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("give a string instanciating this component!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
if (! (args[ 0 ] >>= m_arg))
{
throw lang::IllegalArgumentException(
OUString( RTL_CONSTASCII_USTRINGPARAM("no string given as argument!") ),
(::cppu::OWeakObject *)this, // resolve to XInterface reference
0 ); // argument pos
}
}
The construction of C++ components allows putting as many service implementations into a component file as desired. Ensure that the component operations are implemented in such a way that component_writeInfo() and component_getFactory() handle all services correctly. Refer to the sample component simple_component to see an example on how to implement two services in one link library.
For details about building component code, see the gnu makefile. It uses a number of platform dependent variables used in the SDK that are included from <SDK>/settings/settings.mk. For simplicity, details are omitted here, and the build process is just sketched in eight steps:
The UNOIDL compiler compiles the .idl file some.idl into an urd file.
The resulting binary .urd files are merged into a new simple_component.rdb.
The tool xml2cmp parses the xml component description simple_component.xml for types needed for compiling. This file describes the service implementation(s) for deployment, such as the purpose of the implementation(s) and used types. Visit http://udk.openoffice.org/common/man/module_description.html for details about the syntax of these XML files.
The types parsed in step 3 are passed to cppumaker, which generates the appropriate header pairs into the output include directory using simple_component.rdb and the OpenOffice.org type library applicat.rdb that is stored in the program directory of your OpenOffice.org installation.
|
For your own component you can simplify step 3 and 4, and pass the types used by your component to cppumaker using the -T option. |
---|
The source files service1_impl.cxx and service2_impl.cxx are compiled.
The shared library is linked out of object files, linking dynamically to the UNO base libraries sal, cppu and cppuhelper. The shared library's name is libsimple_component.so on Unix and simple_component.dll on Windows.
|
In general, the shared library component should limit its exports to only the above mentioned functions (prefixed with component_) to avoid symbol clashes on Unix. In addition, for the gnu gcc3 C++ compiler, it is necessary to export the RTTI symbols of exceptions, too. |
---|
The shared library component is registered into simple_component.rdb. This can also be done manually running
$ regcomp -register -r simple_component.rdb -c simple_component.dll
The component's registry simple_component.rdb has entries for the registered service implementations. If the library is registered successfully, run:
$ regview simple_component.rdb
The result should be similar to the following:
/
/ UCR
/ my_module
/ XSomething
... interface information ...
/ IMPLEMENTATIONS
/ my_module.my_sc_impl.MyService2
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 34
Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES
/ my_module.MyService2
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 21
Data = "simple_component.dll"
/ my_module.my_sc_impl.MyService1
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 34
Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES
/ my_module.MyService1
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 21
Data = "simple_component.dll"
/ SERVICES
/ my_module.MyService1
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 40
Len = 1
Data = 0 = "my_module.my_sc_impl.MyService1"
/ my_module.MyService2
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 40
Len = 1
Data = 0 = "my_module.my_sc_impl.MyService2"
OpenOffice.org recognizes registry files being inserted into the unorc file (on Unix, uno.ini on Windows) in the program directory of your OpenOffice.org installation. Extend the types and services in that file by simple_component.rdb. The given file has to be an absolute file URL, but if the rdb is copied to the OpenOffice.org program directory, a $SYSBINDIR macro can be used, as shown in the following unorc file:
[Bootstrap]
UNO_TYPES=$SYSBINDIR/applicat.rdb $SYSBINDIR/simple_component.rdb
UNO_SERVICES=$SYSBINDIR/applicat.rdb $SYSBINDIR/simple_component.rdb
Second, when running OpenOffice.org, extend the PATH (Windows) or LD_LIBRARY_PATH (Unix), including the output path of the build, so that the loader finds the component. If the shared library is copied to the program directory or a link is created inside the program directory (Unix only), do not extend the path.
Launching the test component inside a OpenOffice.org Basic script is simple to do, as shown in the following code:
Sub Main
REM calling service1 impl
mgr = getProcessServiceManager()
o = mgr.createInstance("my_module.MyService1")
MsgBox o.methodOne("foo")
MsgBox o.dbg_supportedInterfaces
REM calling service2 impl
dim args( 0 )
args( 0 ) = "foo"
o = mgr.createInstanceWithArguments("my_module.MyService2", args())
MsgBox o.methodOne("bar")
MsgBox o.dbg_supportedInterfaces
End Sub
This procedure instantiates the service implementations and performs calls on their interfaces. The return value of the methodOne() call is brought up in message boxes. The Basic object property dbg_supportedInterfaces retrieves its information through the com.sun.star.lang.XTypeProvider interfaces of the objects.
There are a number of opportunities to deploy components to a OpenOffice.org environment. The options available depend on how the new component is to be deployed. If OpenOffice.org is installed in a network mode, the new component could be available to an entire network or to certain users. Another option is to install the new component to individual desktop installations. Third, you may want to use UNO components without any local installation at all. This chapter introduces a simple automatic deployment tool and provides a full understanding of the underlying deployment process, so that you can troubleshoot or deploy manually, if necessary.
OpenOffice.org has a simple concept to add components to an existing installation. Bringing a UNO component into a OpenOffice.org installation involves the following steps:
Package your component.
Place the package into a specific package directory. There is a directory for shared packages in a network installation and a directory for user packages (see below).
Close all instances of OpenOffice.org, run a command line shell, change to <OfficePath>/program and run the tool pkgchk from the program directory. The pkgchk is part of the SDK.
[<OfficePath>/program] $ pkgchk
[<OfficePath>/program] $ pkgchk my_package.zip
To remove a package from the OpenOffice.org installation, the opposite steps are necessary:
Remove the package from the packages directory.
Close all instances of OpenOffice.org and run pkgchk.
The pkgchk can be run with the option '--help' or '-h' to get a comprehensive overview of all switches.
The pkgchk mechanism also works for user-defined OpenOffice.org Basic libraries. For details see 11 Basic and Dialogs.
|
Be careful not to run the pkgchk deployment tool while there are running instances of OpenOffice.org. For ordinary users, this case is recognized by the pkgchk process and leads to abortion, but for shared network installations (using option '--shared' or '-s'), this cannot be recognized. If any user of a network installation has open processes, data inconsistencies may occur, and OpenOffice.org processes may crash afterwards. |
---|
An UNO package is a zip file containing UNO components, type libraries or basic libraries. The pkgchk tool unzips all packages found in the package directory into the cache directory, preserving the file structure of the zip file. It also copies all single files recognized in the package directory to the cache directory. Subdirectories are ignored.
There is often the need for platform dependent files inside a package for the supported UNO platforms. For this purpose, create special platform directories with the extension .plt in the package, that is only processed if the platform is present. A package structure for all the platforms currently supported by UNO has to look like the following code:
my_package.zip:
windows.plt/
my_comp.dll
solaris_sparc.plt/
libmy_comp.so
linux_x86.plt/
libmy_comp.so
linux_powerpc.plt/
libmy_comp.so
macosx_powerpc.plt/
libmy_comp.so
netbsd_sparc.plt/
libmy_comp.so
The pkgchk recognizes the platform it is running on and processes only the corresponding .plt directory.
After the cache directory has been made ready, pkgchk traverses the cache directory recursively. Depending on the extension of the files it detects, it carries out the necessary registration steps. Nothing is done for unknown file types.
Shared libraries
The file extension for shared libraries is .dll for Windows and .so for Unix. Shared library files are registered and revoked in the registry database <CacheDir>/services.rdb and linked into the OpenOffice.org installation through the UNO_SERVICES entry in uno(.ini|rc) as shown in the following code. The leading '?' in uno(.ini|rc) indicates optional rdb-files:
UNO_SERVICES=?$UNO_USER_PACKAGES_CACHE/services.rdb \
?$UNO_SHARED_PACKAGES_CACHE/services.rdb \
$SYSBINDIR/applicat.rdb
Java archive files
Jar files are registered and revoked in the registry database <CacheDir>/services.rdb and added to the java classpath of the Java virtual machine used by OpenOffice.org.
Type Library Files
The file extension for type libraries is .rdb on all platforms. Type library files are merged into the <CacheDir>/types.rdb file and linked into the OpenOffice.org installation through the UNO_TYPES entry in uno(.ini|rc). The leading '?' in uno(.ini|rc) designates optional rdb-files:
UNO_TYPES=$SYSBINDIR/applicat.rdb \
?$UNO_SHARED_PACKAGES_CACHE/types.rdb \
?$UNO_USER_PACKAGES_CACHE/types.rdb
Basic libraries
Basic libraries are recognized by the extension .xlb, and they are linked to the basic library container files. Refer to 11 Basic and Dialogs for additional information.
The package directories are called uno-packages by default,. The packages can be in <OfficePath>/share for shared installations and another package can be in <OfficePath>/user for single users. The cache directories are created automatically within the respective uno-packages directory. OpenOffice.org has to be configured to look for these paths in uno(.ini|rc). When pkgchk is launched, it checks uno(.ini|rc) for package entries; if they do not exist, the following default values are added:
[Bootstrap]
UNO_SHARED_PACKAGES=${$SYSBINDIR/bootstrap.ini::BaseInstallation}/share/uno_packages
UNO_SHARED_PACKAGES_CACHE=$UNO_SHARED_PACKAGES/cache
UNO_USER_PACKAGES=${$SYSBINDIR/bootstrap.ini::UserInstallation}/user/uno_packages
UNO_USER_PACKAGES_CACHE=$UNO_USER_PACKAGES/cache
The settings reflect the default values for the shared package and cache directory, and the user package and cache directory, described above.
In a network installation, all users start the office from a common directory on a file server. The administrator puts the packages for all users of the network installation into the <OfficePath>/share/uno_packages folder of the shared installation. If a user wants to install packages locally, so that only the single installation is affected, the packages must be copied to <OfficePath>/user/uno_packages.
The pkgchk is run differently for a shared and user installation. To install shared packages, run pkgchk with the -s (-shared) option, which causes pkgchk to process the shared packages only. If pkgchk is run without command-line parameters, the user packages are registered.
By default, the tool logs all actions into the <cache-dir>/log.txt file. Switch to another log file through the -l (–log) <file name> option. Option -v (–verbose) logs to stdout, in addition to the log file.
The tool handles errors loosely. It continues after errors, even if a package cannot be inflated or a shared library cannot be registered. The tool logs these errors and proceeds silently. Switch on -strict_error handling to make the tool stop on every error.
If there is inconsistency with the cache, renew it from the ground up by repeating the installation, use the option -r (–renewal).
This section explains the necessary steps to deploy new UNO components manually into an installed OpenOffice.org. Background information is provided and the tools required to test deployment are described. The developer and deployer of the component should be familiar with this section. If the recommendations provided are accepted, interoperability of components of different vendors can be achieved easily.
UNO registries store binary data in a tree-like structure. The stored data can be accessed within a registry programmatically through the com.sun.star.registry.SimpleRegistry service, however this is generally not necessary. Note that UNO registries have nothing to do with the Windows registry, except that they follow a similar concept for data storage.
UNO-registries mainly store two types of data :
Type-library
To invoke UNO calls from BASIC or through an interprocess connection, the core UNO bridges need information about the used data types. UNO stores this information into a type library, so that the same data is reusable from any bridge. This is in contrast to the CORBA approach, where code is generated for each data type that needs to be compiled and linked into huge libraries. Every UNOIDL type description is stored as a binary large object (BLOB) that is interpreted by the com.sun.star.reflection.TypeDescriptionProvider service.
Information about registered components
One basic concept of UNO is to create an instance of a component simply by its service name through the ServiceManager. The association between the service name and the shared library or .jar-file where the necessary compiled code is found is stored into a UNO-registry.
The structure of this data is provided below. Future versions of OpenOffice.org will probably store this information in an XML file that will make it modifiable using a simple text editor.
Both types of data are necessary to run a UNO-C++ process. If the types of data are not present, it could lead to termination of the program. UNO processes in general open their registries during startup and close them when the process terminates. Both types of data are commonly stored in a file with an .rdb suffix ( rdb=registry database ), but this suffix is not mandatory.
All type descriptions must be available within the registry under the /UCR main key (UCR = Uno Core Reflection) to be usable in a UNO C++ process . Use the regview tool to view the file <officepath>/install/program/applicat.rdb. The regview tool comes with the OpenOffice.org SDK.
For instance:
$ regview applicat.rdb /UCR
prints all type descriptions used within the office to stdout. To check if a certain type is included within the registry, invoke the following command:
$ regview applicat.rdb /UCR/com/sun/star/bridge/XUnoUrlResolver
/UCR/com/sun/star/bridge/XUnoUrlResolver
Value: Type = RG_VALUETYPE_BINARY
Size = 461
Data = minor version: 0
major version: 1
type: 'interface'
name: 'com/sun/star/bridge/XUnoUrlResolver'
super name: 'com/sun/star/uno/XInterface'
Doku: ""
number of fields: 0
number of methods: 1
method #0: 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
Doku: ""
number of references: 0
The regview tool decodes the format of the BLOB containing the type description and presents it in a readable form.
The UNO component provides the data about what services are implemented. In order not to load all available UNO components into memory when starting a UNO process, the data is assembled once during setup and stored into the registry. The process of writing this information into a registry is called component registration. The tools used to perform this task are discussed below.
For an installed OpenOffice.org, the applicat.rdb contains the component registration information. The data is stored within the /IMPLEMENTATIONS and /SERVICES key. The code below shows a sample SERVICES key for the com.sun.star.io.Pipe service.
$ regview applicat.rdb /SERVICES/com.sun.star.io.Pipe
/SERVICES/com.sun.star.io.Pipe
Value: Type = RG_VALUETYPE_STRINGLIST
Size = 38
Len = 1
Data = 0 = "com.sun.star.comp.io.stm.Pipe"
The code above contains one implementation name, but it could contain more than one. In this case, only the first is used. The following entry can be found within the IMPLEMENTATIONS section:
$ regview applicat.rdb /IMPLEMENTATIONS/com.sun.star.comp.io.stm.Pipe
/IMPLEMENTATIONS/com.sun.star.comp.io.stm.Pipe
/ UNO
/ ACTIVATOR
Value: Type = RG_VALUETYPE_STRING
Size = 34
Data = "com.sun.star.loader.SharedLibrary"
/ SERVICES
/ com.sun.star.io.Pipe
/ LOCATION
Value: Type = RG_VALUETYPE_STRING
Size = 8
Data = "stm.dll"
The implementations section holds three types of data.
The loader to be used when the component is requested at runtime (here com.sun.star.loader.SharedLibrary).
The services supported by this implementation.
The URL to the file the loader uses to access the library (the url may be given relative to the OpenOffice.org library directory for native components as it is in this case).
There are various tools to create, modify and use registries. This section shows some common use cases. The regmerge tool is used to merge multiple registries into a sub-key of an existing or new registry. For instance:
$ regmerge new.rdb / test1.rdb test2.rdb
merges the contents of test1.rdb and test2.rdb under the root key / of the registry database new.rdb . The names of the keys are preserved, because both registries are merged into the root-key. In case new.rdb existed before, the previous contents remain in new.rdb unless an identical key names exist in test1.rdb and test2.rdb. In this case, the content of these keys is overwritten with the ones in test1.rdb or test2.rdb. So the above command is semantically identical to:
$ regmerge new.rdb / test1.rdb
$ regmerge new.rdb / test2.rdb
The following command merges the contents of test1.urd and test2.urd under the key /UCR into the file myapp_types.rdb.
$ regmerge myapp_types.rdb /UCR test1.urd test2.urd
The names of the keys in test1.urd and test2.urd should only be added to the /UCR key. This is a real life scenario as the files produced by the idl-compiler have a .urd-suffix. The regmerge tool needs to be run before the type library can be used in a program, because UNO expects each type description below the /UCR key.
Components can be registered using the regcomp tool. Below, the components necessary to establish an interprocess connection are registered into the myapp_services.rdb.
$ regcomp -register -r myapp_services.rdb \
-c uuresolver.dll \
-c brdgfctr.dll \
-c acceptor.dll \
-c connectr.dll \
-c remotebridge.dll
The \ means command line continuation. The option -r gives the registry file where the information is written to. If it does not exist, it is created, otherwise the new data is added. In case there are older keys, they are overwritten. The registry file (here myapp_services.rdb) must NOT be opened by any other process at the same time. The option -c is followed by a single name of a library that is registered. The -c option can be given multiple times. The shared libraries registered in the example above are needed to use the UNO interprocess bridge.
Registering a Java component is currently more complex. It works only in an installed office environment, the <OfficePath>/program must be the current working directory, the office setup must point to a valid Java installation that can be verified using jvmsetup from the <OfficePath>/program, and Java must be enabled. See Tools - Options - General - Security. The office must not run. On Unix, the LD_LIBRARY_PATH environment variable must additionally contain the directories listed by the javaldx tool (which is installed with the office).
Copy the regcomp executable into the <officepath>/program directory. The regcomp tool must then be invoked using the following parameters :
$ regcomp -register -r your_registry.rdb \
-br applicat.rdb \
-l com.sun.star.loader.Java2 \
-c file:///i:/StarOffice6.0/program/classes/JavaTestComponent.jar
The option -r (registry) tells regcomp where to write the registration data and the -br (bootstrap registry) option points regcomp to a registry to read common types from. The regcomp tool does not know the library that has the Java loader. The -l option gives the service name of the loader to use for the component that must be com.sun.star.loader.Java2. The option can be omitted for C++ components, because regcomp defaults to the com.sun.star.loader.SharedLibrary loader. The option -c gives the file url to the Java component.
File urls can be given absolute or relative. Absolute file urls must begin with 'file:///'. All other strings are interpreted as relative file urls. The '3rdpartYcomp/filterxy.dll', '../../3rdpartycomp/filterxyz.dll', and 'filterxyz.dll' are a few examples. Relative file urls are interpreted relative to all paths given in the PATH variable on Windows and LD_LIBRARY_PATH variable on Unix.
Java components require an absolute file URL for historical reasons.
|
The regcomp tool should be used only during the development and testing phase of components. For deploying final components, the pkgchk tool should be used instead. See 4.7.1 Writing UNO Components - Deployment Options for Components - UNO Package Installation. |
---|
There are several tools that currently access the type library directly. They are encountered when new UNOIDL types are introduced.
idlc, Compiles .idl files into .urd-registry-files.
cppumaker, Generates C++ header for a given UNO type list from a type registry used with the UNO C++ binding.
javamaker, Generates .java files for a given type list from a type registry.
rdbmaker, Creates a new registry by extracting given types (including dependent types) from another registry, and is used for generating minimal, but complete type libraries for components. It is useful when building minimal applications that use UNO components.
regcompare, Compares a type library to a reference type library and checks for compatibility.
regmerge, Merges multiple registries into a certain sub-key of a new or already existing registry.
Registry files used by OpenOffice.org are configured within the uno(.ini|rc) and soffice(.ini|rc) files found in the program directory. After a default OpenOffice.org installation, the files look like this:
uno.ini :
[Bootstrap]
UNO_TYPES=$SYSBINDIR/applicat.rdb
UNO_SERVICES=$SYSBINDIR/applicat.rdb
soffice.ini:
[Bootstrap]
Logo=1
UNO_WRITERDB=$SYSUSERCONFIG/user60.rdb
The three UNO variables are relevant for UNO components. The UNO_TYPES variable gives a space separated list of type library registries, and the UNO_SERVICES variable gives a space separated list of registries that contain component registration information. These registries are opened read-only. The same registry may appear in UNO_TYPES and UNO_SERVICES variables, for example, the applicat.rdb. The UNO_WRITERDB provides one registry that is opened in read-write mode. The $SYSBINDIR points to the directory where the soffice executable is located and $SYSUSERCONFIG points to the user's home directory.
OpenOffice.org uses the applicat.rdb as a type and component registration information repository. When a programmer or software vendor releases a UNO component, the following files must be provided at a minimum:
A file containing the code of the new component, for instance a shared library, a jar file, or maybe a python file in the future.
A registry file containing user defined UNOIDL types, if any.
(optional) A registry file containing registration information of a pre-registered component. The registry provider should register the component with a relative path to be beneficial in other OpenOffice.org installations.
The latter two can be integrated into a single file.
|
In fact, a vendor may release more files, such as documentation, the .idl files of the user defined types, the source code, and configuration files. While every software vendor is encouraged to do this, there are currently no recommendations how to integrate these files into OpenOffice.org. These type of files are ignored in the following paragraphs. These issues will be addressed in next releases of OpenOffice.org. |
---|
The recommended method to add a component to OpenOffice.org manually is described in the following steps:
Copy new shared library components into the <OfficePath>/program directory and new Java components into the <OfficePath>/program/classes directory.
Copy the registry containing the type library into the <OfficePath>/program directory, if needed and available.
Copy the registry containing the component registration information into the <OfficePath>/program directory, if required. Otherwise, register the component with the regcomp command line tool coming with the OpenOffice.org SDK into a new registry.
Modify the uno(.ini|rc) file, and add the type registry to the UNO_TYPES variable and the component registry to the UNO_SERVICES variable. The new uno(.ini|rc) might look like this:
[Bootstrap]
UNO_TYPES=$SYSBINDIR/applicat.rdb $SYSBINDIR/filterxyz_types.rdb
UNO_SERVICES=$SYSBINDIR/applicat.rdb $SYSBINDIR/filterxyz_services.rdb
After these changes are made, every office that is restarted can use the new component. The uno(.ini|rc) changes directly affect the whole office network installation. If adding a component only for a single user, pass the modified UNO_TYPES and UNO_SERVICES variables per command line. An example might be:
$ soffice “-env:UNO_TYPES=$SYSBINDIR/applicat.rdb $SYSBINDIR/filterxyz_types.rdb“
“-env:UNO_SERVICES=$SYSBINDIR/applicat.rdb $SYSBINDIR/filter_xyz_services.rdb” ).
There are more ways to add a component to the office with their own advantages and disadvantages. Below are some alternatives :
When adding many third-party components to your office, the startup performance suffers from having types scattered about many registries. To avoid this, merge all third-party type registries into a single type registry and all third-party service registries into a single service registry using the regmerge tool.
New types and services can be merged into the applicat.rdb directly. With this method, the uno(.ini|rc) does not have to be modified. Modifying the applicat.rdb while there are running office instances is not allowed. This is important in a network installation.
Once merged, these registries can not be 'unmerge'. To remove a certain type library, a merge with all the other source types will have to be performed, that is, repeat the installation.
When separating the OpenOffice.org installation from any third-party additions, the additional registries, shared libraries and .jar files can be stored into a directory other than the <OfficePath>/program directory. In this case, use relative filenames for component registrations, for instance ../../office3rdparty/filterxyz.dll. The only file that needs to be modified is the uno(.ini|rc).
To add a component only for the current user, register the component when the office is running by using OpenOffice.org Basic with one of two methods. Program a short script using the com.sun.star.registry.ImplementationRegistration service:
Sub Main
' create the UNO registration service
' Note: the _ is just there for line continuation
implementationRegistration = _
createUnoService( "com.sun.star.registry.ImplementationRegistration" )
'register a C++ component
implementationRegistration.registerImplementation( _
"com.sun.star.loader.SharedLibrary" , "stm.dll" , null )
'register a Java component
implementationRegistration.registerImplementation( _
"com.sun.star.loader.Java2" , "file:///x:/jbu/JavaSampleChartAddIn.jar", null )
End Sub
or use the component registration wizard coming as a basic library within the OpenOffice.org SDK. In the current version of OpenOffice.org, it is impossible to add new UNO types at runtime, therefore the component registration wizard is only usable for components that do not need any new types.
Configuring your Application with Bootstrap Parameters
A flexible approach is to use the UNO bootstrap parameters and the defaultBootstrap_InitialComponentContext() function. Arguments, such as registry names are not passed to this function, rather they are given through bootstrap parameters.
Bootstrapping a service manager means to create an instance of a service manager that is able to instantiate the UNO objects needed by a user. All UNO applications, that want to use the UnoUrlResolver for connections to the office, have to bootstrap a local service manager in order to create a UnoUrlResolver object. If developers create a new language binding, for instance for a scripting engine, they have to find a way to bootstrap a service manager in the target environment.
There are many methods to bootstrap a UNO C++ application, each requiring one or more registry files to be prepared. Once the registries are prepared, there are different options available to bootstrap your application. A flexible approach is to use UNO bootstrap parameters and the defaultBootstrap_InitialComponentContext() function.
#include <cppuhelper/bootstrap.hxx>
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace rtl;
using namespace cppu;
int main( )
{
// create the initial component context
Reference< XComponentContext > rComponentContext =
defaultBootstrap_InitialComponentContext();
// retrieve the service manager from the context
Reference< XMultiComponentFactory > rServiceManager =
rComponentContext()->getServiceManager();
// instantiate a sample service with the service manager.
Reference< XInterface > rInstance =
rServiceManger->createInstanceWithContext(
OUString::createFromAscii("com.sun.star.bridge.UnoUrlResolver" ),
rComponentContext );
// continue to connect to the office ....
}
No arguments, such as a registry name, are passed to this function. These are given using bootstrap parameters. Bootstrap parameters can be passed through a command line, an .ini file or using environment variables.
For bootstrapping the UNO component context, the following three variables are relevant:
UNO_TYPES
Gives a space separated list of type library registry files. Each registry must be given as an absolute or relative file url. Note that some special characters within the path require encoding, for example, a space must become a %20. The registries are opened in read-only.
UNO_SERVICES
Gives a space separated list of registry files with component registration information. The registries are opened in read-only. The same registry may appear in UNO_TYPES and UNO_SERVICES variables.
UNO_WRITERDB
Gives one registry file that is opened in read-write mode. Using this variable is optional, because it registers components at runtime and uses them directly.
An absolute file URL must begin with the file:/// prefix (on windows, it must look like file:///c:/mytestregistry.rdb). To make a file URL relative, the file:/// prefix must be omitted. The relative url is interpreted relative to the current working directory.
Within the paths, use special placeholders.
Bootstrap variable |
Meaning |
---|---|
$SYSUSERHOME |
Path of the user's home directory (see osl_getHomeDir()) |
$SYSBINDIR |
Path to the directory of the current executable. |
$SYSUSERCONFIG |
Path to the directory where the user's configuration data is stored (see osl_getConfigDir()) |
The advantage of this method is that the executable can be configured after it has been built. The OpenOffice.org bootstraps the service manager with this mechanism.
Consider the following example:
A tool needs to be written that converts documents between different formats. This is achieved by connecting to OpenOffice.org and doing the necessary conversions. The tool is named docconv. In the code, the defaultBootstrap_InitialComponentContext() function is used as described above to create the component context. Two registries are prepared: docconv_services.rdb with the registered components and applicat.rdb that contains the types coming with OpenOffice.org. Both files are placed beside the executable. The easiest method to configure the application is to create a docconv(.ini|rc) ascii file in the same folder as your executable, that contains the following two lines:
UNO_TYPES=$SYSBINDIR/applicat.rdb
UNO_SERVICES=$SYSBINDIR/docconv_services.rdb
No matter where the application is started form, it will always use the mentioned registries. Note that this also works on different machines when the volume is mapped to different location mount points as $SYSBINDIR is evaluated at runtime.
The second possibility is to set UNO_TYPES and UNO_SERVICES as environment variables, but this method has drawbacks. All UNO applications started with this shell use the same registries.
The third possibility is to pass the variables as command line parameters, for instance
docconv -env:UNO_TYPES=$SYSBINDIR/applicat.rdb -env:UNO_SERVICES=$SYSBINDIR/docconv_services.rdb
Note that on UNIX shells, you need to quote the $ with a backslash \.
The command line arguments do not need to be passed to the UNO runtime, because it is generally retrieved from some static variables. How this is done depends on the operating system, but it is hidden from the programmer. The docconv executable should ignore all command line parameters beginning with '-env:'. The easiest way to do this is to ignore argc and argv[] and to use the rtl_getCommandLineArg() functions defined in rtl/process.h header instead which automatically strips the additional parameters.
Combine the methods mentioned above. Command line parameters take precedence over .ini file variables and .ini file parameter take precedence over environment variables. That way, it is possible to overwrite the UNO_SERVICES variable on the command line for one invocation of the program only.
The com.sun.star.container.XSet interface allows the insertion or removal of com.sun.star.lang.XSingleServiceFactory or com.sun.star.lang.XSingleComponentFactory implementations into or from the service manager at runtime without making these changes persistent. When the office applications terminate, all the changes are lost. The inserted object must support the com.sun.star.lang.XServiceInfo interface. This interface returns the same information as the XServiceInfo interface of the component implementation which is created by the component factory.
With this feature, a running office can be connected, a new factory inserted into the service manager and the new service instantiated without registering it beforehand. This method of hard coding the registered services is not acceptable with OpenOffice.org, because it must be extended after compilation.
Java applications can use a native persistent service manager in their own process using JNI (see 3.4.1 Professional UNO - UNO Language Bindings - Java Language Binding), or in a remote process. But note, that all services will be instantiated in this remote process.
Bootstrapping in pure Java is simple, by calling the static runtime method createInitialComponentContext() from the Bootstrap class. The following small test program shows how to insert service factories into the service manager at runtime. The sample uses the Java component from the section 4.5.6 Writing UNO Components - Simple Component in Java - Storing the Service Manager for Further Use. The complete code can be found with the JavaComp sample component.
The example shows that there is the possibility to control through command line parameter, whether the service is inserted in the local Java service manager or the remote office service manager. If it is inserted into the office service manager, access the service through OpenOffice.org Basic. In both cases, the component runs in the local Java process.
If the service is inserted into the office service manager, instantiate the component through OpenOffice.org Basic calling createUnoService("JavaTestComponentB"),as long as the Java process is not terminated. Note, to add the new types to the office process by one of the above explained mechanisms, use uno.ini.
public static void insertIntoServiceManager(
XMultiComponentFactory serviceManager, Object singleFactory)
throws com.sun.star.uno.Exception {
XSet set = (XSet ) UnoRuntime.queryInterface(XSet.class, serviceManager);
set.insert(singleFactory);
}
public static void removeFromServiceManager(
XMultiComponentFactory serviceManager, Object singleFactory)
throws com.sun.star.uno.Exception {
XSet set = (XSet) UnoRuntime.queryInterface( XSet.class, serviceManager);
set.remove(singleFactory);
}
public static void main(String[] args) throws java.lang.Exception {
if (args.length != 1) {
System.out.println("usage: RunComponent local|uno-url");
System.exit(1);
}
XComponentContext xLocalComponentContext =
Bootstrap.createInitialComponentContext(null);
// initial serviceManager
XMultiComponentFactory xLocalServiceManager = xLocalComponentContext.getServiceManager();
XMultiComponentFactory xUsedServiceManager = null;
XComponentContext xUsedComponentContext = null;
if (args[0].equals("local")) {
xUsedServiceManager = xLocalServiceManager;
xUsedComponentContext = xLocalComponentContext;
System.out.println("Using local servicemanager");
// now the local servicemanager is used !
}
else {
// otherwise interpret the string as uno-url
Object xUrlResolver = xLocalServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", xLocalComponentContext);
XUnoUrlResolver urlResolver = (XUnoUrlResolver) UnoRuntime.queryInterface(
XUnoUrlResolver.class, xUrlResolver);
Object initialObject = urlResolver.resolve(args[0]);
xUsedServiceManager = (XmultiComponentFactory) UnoRuntime.queryInterface(
XMultiComponentFactory.class, initialObject);
System.out.println("Using remote servicemanager");
// now the remote servicemanager is used.
}
// retrieve the factory for the component implementation
Object factory = TestServiceProvider.__getServiceFactory(
"componentsamples.TestComponentB", null, null);
// insert the factory into the servicemanager
// from now on, the service can be instantiated !
insertIntoServiceManager( xUsedServiceManager, factory );
// Now instantiate one of the services via the servicemanager !
Object objTest= xUsedServiceManager.createInstanceWithContext(
"JavaTestComponentB",xUsedComponentContext);
// query for the service interface
XSomethingB xs= (XSomethingB) UnoRuntime.queryInterface(
XSomethingB.class, objTest);
// and call the test method.
String s= xs.methodOne("Hello World");
System.out.println(s);
// wait until return is pressed
System.out.println( "Press return to terminate" );
while (System.in.read() != 10);
// remove it again from the servicemanager, otherwise we have
// a dangling reference ( in case we use the remote service manager )
removeFromServiceManager( xUsedServiceManager, factory );
// quit, even when a remote bridge is running
System.exit(0);
}
To create a service manager from a given registry, use a single registry that contains the type library and component registration information. Hard code the name of the registry in the program and use the createRegistryServiceFactory() function located in the cppuhelper library.
#include <cppuhelper/servicefactory.hxx>
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace rtl;
using namespace cppu;
int main( )
{
// create the service manager on the registry test.rdb
Reference< XMultiServiceFactory > rServiceManager =
createRegistryServiceFactory( OUString::createFromAscii( “test.rdb” ) );
// instantiate a sample service with the service manager.
Reference< XInterface > rInstance =
rServiceManger->createInstance(
OUString::createFromAscii(“com.sun.star.bridge.UnoUrlResolver” ) );
// continue to connect to the office ....
}
|
This instantiates the old style service manager without the possibility of offering a component context. In future versions, (642) you will be able to use the new service manager here. |
---|
In chapter 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding, several methods to bootstrap a UNO application were introduced. In this section, the option UNO executable is discussed. With UNO executable, there is no need to write executables anymore, instead only components are developed. Code within executables is locked up, it can only run by starting the executable, and it can never be used in another context. Components offer the advantage that they can be used from anywhere. They can be executed from Java or from a remote process.
For these cases, the com.sun.star.lang.XMain interface was introduced. It has one method:
/* module com.sun.star.lang.XMain */
interface XMain: com::sun::star::uno::XInterface
{
long run( [in] sequence< string > aArguments );
};
Instead of writing an executable, write a component and implement this interface. The component gets the fully initialized service manager during instantiation. The run() method then should do what a main() function would have done. The UNO executable offers one possible infrastructure for using such components.
Basically, the uno tool can do two different things:
Instantiate a UNO component which supports the com.sun.star.lang.XMain interface and executes the run() method.
// module com::sun::star::lang
interface XMain: com::sun::star::uno::XInterface
{
long run( [in] sequence< string > aArguments );
};
Export a UNO component to another process by accepting on a resource, such as a tcp/ip socket or named pipe, and instantiating it on demand.
In both cases, the uno executable creates a UNO component context which is handed to the instantiated component. The registries that should be used are given by command line arguments. The goal of this tool is to minimize the need to write executables and focus on writing components. The advantage for component implementations is that they do not care how the component context is bootstrapped. In the future there may be more ways to bootstrap the component context. While executables will have to be adapted to use the new features, a component supporting XMain can be reused.
Simply typing uno gives the following usage screen :
uno (-c ComponentImplementationName -l LocationUrl | -s ServiceName)
[-ro ReadOnlyRegistry1] [-ro ReadOnlyRegistry2] ... [-rw ReadWriteRegistry]
[-u uno:(socket[,host=HostName][,port=nnn]|pipe[,name=PipeName]);urp;Name
[--singleaccept] [--singleinstance]]
[-- Argument1 Argument2 ...]
Choosing the implementation to be instantiated
Using the option -s servicename gives the name of the service which shall be instantiated. The uno executable then tries to instantiate a service by this name, using the registries as listed below.
Alternatively, the -l and -c options can be used. The -l gives an url to the location of the shared library or .jar file, and -c the name of the desired service implementation inside the component. Remember that a component may contain more than one implementation.
Choosing the registries for the component context (optional)
With the option -ro, give a file url to a registry file containing component's registration information and/or type libraries. The -ro option can be given multiple times. The -rw option can only be given once and must be the name of a registry with read/write access. It will be used when the instantiated component tries to register components at runtime. This option is rarely needed.
Note that the uno tool ignores bootstrap variables, such as UNO_TYPES and UNO_SERVICES.
The UNO URL (optional)
Giving a UNO URL causes the uno tool to start in server mode, then it accepts on the connection part of the UNO URL. In case another process connects to the resource (tcp/ip socket or named pipe), it establishes a UNO interprocess bridge on top of the connection (see also 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections). Note that urp should always be used as protocol. An instance of the component is instantiated when the client requests a named object using the name, which was given in the last part of the UNO URL.
Option --singleaccept
Only meaningful when a UNO URL is given. It tells the uno executable to accept only one connection, thus blocking any further connection attempts.
Option --singleinstance
Only meaningful when a UNO URL is given. It tells the uno executable to always return the same (first) instance of the component, thus multiple processes communicate to the same instance of the implementation. If the option is not given, every getInstance() call at the com.sun.star.bridge.XBridge interface instantiates a new object.
Option -- (double dash)
Everything following –- is interpreted as an option for the component itself. The arguments are passed to the component through the initialize() call of com.sun.star.lang.XInitialization interface.
|
The uno executable currently does not support the bootstrap variable concept as introduced by 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding. The uno registries must be given explicitly given by command line. |
---|
The following example shows how to implement a Java component suitable for the uno executable.
import com.sun.star.uno.XComponentContext;
import com.sun.star.comp.loader.FactoryHelper;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.registry.XRegistryKey;
public class UnoExeMain implements com.sun.star.lang.XMain
{
final static String __serviceName = "MyMain";
XComponentContext _ctx;
public UnoExeMain( XComponentContext ctx )
{
// in case we would need the component context !
_ctx = ctx;
}
public int run( /*IN*/String[] aArguments )
{
System.out.println( "Hello world !" );
return 0;
}
public static XSingleServiceFactory __getServiceFactory(
String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey)
{
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.equals(UnoExeMain.class.getName()))
{
xSingleServiceFactory =
FactoryHelper.getServiceFactory(
UnoExeMain.class, UnoExeMain.__serviceName, multiFactory, regKey);
}
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey)
{
boolean b = FactoryHelper.writeRegistryServiceInfo(
UnoExeMain.class.getName(),
UnoExeMain.__serviceName, regKey);
return b;
}
}
The class itself inherits from com.sun.star.lang.XMain. It implements a constructor with the com.sun.star.uno.XComponentContext interface and stores the component context for future use. Within its run() method, it prints 'Hello World'. The last two mandatory functions are responsible for instantiating the component and writing component information into a registry. Refer to 4.5.6 Writing UNO Components - Simple Component in Java - Storing the Service Manager for Further Use for further information.
The code needs to be compiled and put into a .jar file with an appropriate manifest file:
RegistrationClassName: UnoExeMain
These commands create the jar:
javac UnoExeMain
jar -cvfm UnoExeMain.jar Manifest UnoExeMain.class
To be able to use it, register it with the following command line into a separate registry file (here test.rdb). The <OfficePath>/program directory needs to be the current directory, and the regcomp and uno tools must have been copied into this directory.
regcomp -register \
-br applicat.rdb \
-r test.rdb \
-c file:///c:/devmanual/Develop/samples/unoexe/UnoExeMain.jar \
-l com.sun.star.loader.Java2
The \ means command line continuation.
The component can now be run:
uno -s MyMain -ro applicat.rdb -ro test.rdb
This command should give the output "hello world !"
This use case enables the export of any arbitrary UNO component as a remote server. As an example, the com.sun.star.io.Pipe service is used which is already implemented by a component coming with the office. It exports an com.sun.star.io.XOutputStream and a com.sun.star.io.XInputStream interface. The data is written through the output stream into the pipe and the same data from the input stream is read again. To export this component as a remote server, switch to the <OfficePath>/program directory and issue the following command line.
i:\o641l\program>uno -s com.sun.star.io.Pipe -ro applicat.rdb -u uno:socket,host=0,port=2002;urp;test
> accepting socket,host=0,port=2002...
Now a client program can connect to the server. A client may look like the following:
import com.sun.star.lang.XServiceInfo;
import com.sun.star.uno.XComponentContext;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.io.XOutputStream;
import com.sun.star.io.XInputStream;
import com.sun.star.uno.UnoRuntime;
// Note: This example does not do anything meaningful, it shall just show,
// how to import an arbitrary UNO object from a remote process.
class UnoExeClient {
public static void main(String [] args) throws java.lang.Exception {
if (args.length != 1) {
System.out.println("Usage : java UnoExeClient uno-url");
System.out.println(" The imported object must support the com.sun.star.io.Pipe service");
return;
}
XComponentContext ctx =
com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);
// get the UnoUrlResolver service
Object o = ctx.getServiceManager().createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver" , ctx);
XUnoUrlResolver resolver = (XUnoUrlResolver) UnoRuntime.queryInterface(
XUnoUrlResolver.class, o);
// connect to the remote server and retrieve the appropriate object
o = resolver.resolve(args[0]);
// Check if we got what we expected
// Note: This is not really necessary, you can also use the try and error approach
XServiceInfo serviceInfo = (XServiceInfo) UnoRuntime.queryInterface(XServiceInfo.class,o);
if (serviceInfo == null) {
throw new com.sun.star.uno.RuntimeException(
"error: The object imported with " + args[0] + " did not support XServiceInfo", null);
}
if (!serviceInfo.supportsService("com.sun.star.io.Pipe")) {
throw new com.sun.star.uno.RuntimeException(
"error: The object imported with "+args[0]+" does not support the pipe service", null);
}
XOutputStream output = (XOutputStream) UnoRuntime.queryInterface(XOutputStream.class,o);
XInputStream input = (XInputStream) UnoRuntime.queryInterface(XInputStream.class,o);
// construct an array.
byte[] array = new byte[]{1,2,3,4,5};
// send it to the remote object
output.writeBytes(array);
output.closeOutput();
// now read it again in two blocks
byte [][] read = new byte[1][0];
System.out.println("Available bytes : " + input.available());
input.readBytes( read,2 );
System.out.println("read " + read[0].length + ":" + read[0][0] + "," + read[0][1]);
System.out.println("Available bytes : " + input.available());
input.readBytes(read,3);
System.out.println("read " + read[0].length + ":" + read[0][0] +
"," + read[0][1] + "," + read[0][2]);
System.out.println("Terminating client");
System.exit(0);
}
}
After bootstrapping the component context, the UnoUrlResolver service is instantiated to access remote objects. After resolving the remote object, check whether it really supports the Pipe service. For instance, try to connect this client to a running OpenOffice.org — this check will fail. A byte array with five elements is written to the remote server and read again with two readBytes() calls. Starting the client with the following command line connects to the server started above. You should get the following output:
I:\tmp>java UnoExeClient uno:socket,host=localhost,port=2002;urp;test
Available bytes : 5
read 2:1,2
Available bytes : 3
read 3:3,4,5
Terminating client
The main benefit of using the uno tool as a replacement for writing executables is that the service manager initialization is separated from the task-solving code and the component can be reused. For example, to have multiple XMain implementations run in parallel in one process. There is more involved when writing a component compared to writing an executable. With the bootstrap variable mechanism there is a lot of freedom in bootstrapping the service manager (see chapter 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding).
The uno tool is a good starting point when exporting a certain component as a remote server. However, when using the UNO technology later, the tool does have some disadvantages, such as multiple objects can not be exported or the component can only be initialized with command line arguments. If the uno tool becomes insufficient, the listening part in an executable will have to be re-implemented.
|
To instantiate Java components in build version 641, you need a complete setup so that the uno executable can find the java.ini file. |
---|
When UNO components written in Java are to be used within the office, it has to be configured appropriately. During OpenOffice.org installation, the Java setup is run. It gives the user the opportunity to choose a Java installation. The setup only offers Java versions which are certain to work with the office. The user can also choose to have an appropriate Java Runtime Environment installed. When the office has been installed, a user can still change the used Java installation by running the jvmsetup program that is located in the program directory of the installation directory. For example:
d:\program files\<office-installation-dir>\program\jvmsetup.exe
When the office starts Java, it uses configuration data that are kept in the java(.ini|rc) file, as well as in dedicated configuration files. The java(.ini|rc) is located in the <officepath>\user\config directory. A client can use that file to pass additional properties to the Java Virtual Machine, which are then available as system properties. For example, to pass the property MyAge, invoke Java like this:
java -DMyAge=30 RunClass
If you want to have that system property accessible by your Java component you can put that property into java(.ini|rc) within the [Java] section. For example:
[Java]
Home=d:\development\jdk1.3.1
VMType=jdk
Version=1.3.1
RuntimeLib=d:\development\jdk1.3.1\jre\bin\hotspot\jvm.dll
SystemClasspath=d:\development\jdk1.3.1\jre\lib\rt.jar; ...
Java=1
JavaScript=1
Applets=1
MyAge=27
To debug a Java component, it is necessary to start the JVM with additional parameters. The parameters can be put in the java.ini the same way as they would appear on the command-line. For example , add those lines to the [Java] section:
-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,address=8000
More about debugging can be found in the JDK documentation and in the OpenOffice.org Software Development Kit.
Java components are also affected by the following configuration settings. They can be changed in the Tools - Options dialog. In the dialog, expand the OpenOffice.org node on the left-hand side and choose Security. This brings up a new pane on the right-hand side that allows Java specific settings:
Java Setting |
Description |
---|---|
Enable |
If checked, Java is used with the office. This affects Java components, as well as applets. |
Security checks |
If checked, the security manager restricts resource access of applets. |
Net access |
Determines where an applet can connect. |
ClassPath |
Additional jar files and directories where the JVM should search for classes. Also known as user classpath. |
Applets |
If checked, applets are executed. |