[ Previous document | Content Table | Next document ]

3    Professional UNO

This chapter provides in-depth information about UNO and the use of UNO in various programming languages. There are four sections:

3.1    Introduction

The goal of UNO (Universal Network Objects) is to provide an environment for network objects across programming language and platform boundaries. UNO objects run and communicate everywhere. UNO reaches this goal by providing the following fundamental framework:

3.2    API Concepts

The OpenOffice.org API is a language independent approach to specify the functionality of OpenOffice.org. Its main goals are to provide an API to access the functionality of OpenOffice.org, to enable users to extend the functionality by their own solutions and new features, and to make the internal implementation of OpenOffice.org exchangeable.

A long term target on the OpenOffice.org roadmap is to split the existing OpenOffice.org into small components which are combined to provide the complete OpenOffice.org functionality. Such components are manageable, they interact with each other to provide high level features and they are exchangeable with other implementations providing the same functionality, even if these new implementations are implemented in a different programming language. When this target will be reached, the API, the components and the fundamental concepts will provide a construction kit, which makes OpenOffice.org adaptable to a wide range of specialized solutions and not only an office suite with a predefined and static functionality.

This section provides you with a thorough understanding of the concepts behind the OpenOffice.org API. In the API reference there are UNO IDL data types which are unknown outside of the API. The reference provides abstract specifications which sometimes can make you wonder how they map to implementations you can actually use.

The data types of the API reference are explained in 3.2.1 Professional UNO - API Concepts - Data Types. The relationship between API specifications and OpenOffice.org implementations is treated in 3.2.2 Professional UNO - API Concepts - Understanding the API Reference.

3.2.1    Data Types

The data types in the API reference are UNO IDL types which have to be mapped to the types of any programming language that can be used with the OpenOffice.org API. In the chapter 2 First Steps the most important UNO types were introduced. However, there is much more to be said about simple types, interfaces, properties and services in UNO. There are special flags, conditions and relationships between these types which you will want to know if you are working with UNO on a professional level.

This section explains the types of the API reference from the perspective of a developer who wants to use the OpenOffice.org API. If you are interested in writing your own components, and you must define new interfaces and types, please refer to the chapter 4 Writing UNO Components, which describes how to write your own UNO IDL specifications and how to create UNO components.

Simple Types

UNO IDL provides a set of predefined and fundamental base types which are listed in the following table:

UNO IDL Type

Description

boolean

Can be true or false.

byte

One-byte type representing a type that is not modified by the UNO runtime during transport (marshaling) over a UNO bridge.

char

Represents a unicode character. When this type is mapped to a programming language, the representation depends on the respective hardware or software architecture.

double

Processor dependent double.

float

Processor dependent float.

hyper

64-bit integer type.

long

32-bit integer type.

short

16-bit integer type.

string

Unicode string type.

type

Meta type that describes any other UNO IDL types.

void

Empty  return value, only possible as return value.

unsigned hyper

Deprecated. Unsigned 64-bit integer value.

unsigned long

Deprecated. Unsigned 32-bit integer value.

unsigned short

Deprecated. Unsigned 16-bit integer value.

The chapters about language bindings 3.4.1 Professional UNO - UNO Language Bindings - Java Language Binding, 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding, 3.4.3 Professional UNO - UNO Language Bindings - OpenOffice.org Basic and 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge describe how these types are mapped to the types of your target language.

The Any Type

The special type any can represent all other known and defined UNO IDL types. In the target languages, the any type requires special treatment. There is an AnyConverter in Java and special operators in C++. For details, see the section 3.4 Professional UNO - UNO Language Bindings about language bindings.

Interfaces

Communication between UNO objects is based on object interfaces. Interfaces can be seen from the outside or the inside of an object.

From the outside of an object, an interface provides a functionality or special aspect of the object. Interfaces provide access to objects by publishing a set of operations that cover a certain aspect of an object without telling anything about its internals.

The concept of interfaces is quite natural and frequently used in everyday life. Interfaces allow the creation of things that fit in with each other without knowing internal details about them. A power plug that fits into a standard socket or a one-size-fits-all working glove are simple examples. They all work by standardizing the minimal conditions that must be met to make things work together.

A more advanced example would be the "remote control aspect" of a simple TV system. One possible feature of a TV system is a remote control. The remote control functions can be described by an XPower and an XChannel interface. The illustration below shows a RemoteControl object with these interfaces:

UML diagram showing the RemoteControl service
Illustration 1.1: RemoteControl service

The XPower interface has the functions turnOn() and turnOff() to control the power and the XChannel interface has the functions select(), next(),  previous() to control the current channel. The user of these interfaces does not care if he uses an original remote control that came with a TV set or a universal remote control as long as it carries out these functions. The user is only dissatisfied if some of the functions promised by the interface do not work with a remote control.

From the inside of an object, or from the perspective of someone who implements a UNO object, interfaces are abstract specifications. The abstract specification of all the interfaces in the OpenOffice.org API has the advantage that user and implementer can enter into a contract, agreeing to adhere to the interface specification. A program that strictly uses the OpenOffice.org API according to the specification will always work, while an implementer can do whatever he wants with his objects, as long as he serves the contract.

UNO uses the interface type to describe such aspects of UNO objects. All interface names start with the letter X to distinguish them from other types. All interface types must inherit the com.sun.star.uno.XInterface interface for basic object communication, either directly or in the inheritance hierarchy. XInterface is explained in 3.3.3 Professional UNO - UNO Concepts - Using UNO Interfaces. The interface types define operations to provide access to the specified UNO objects.

Interface operations allow access to the data inside an object through dedicated methods (member functions) which encapsulate the data of the object. Interfaces only consist of operations. The operations always have a parameter list and a return value, and they may define exceptions for smart error handling.

The exception concept in the OpenOffice.org API is comparable with the exception concepts known from Java or C++. All operations can raise com.sun.star.uno.RuntimeExceptions without explicit specification, but all other exceptions must be specified. UNO exceptions are explained in the section 3.3.6 Professional UNO - UNO Concepts - Exception Handling below.

Consider the following two examples for interface definitions in UNO IDL notation. UNO IDL interfaces resemble Java interfaces, and operations look similar to Java method signatures. However, note the flags in square brackets in the following example:

// base interface for all UNO interfaces

interface XInterface

{

        any queryInterface( [in] type aType );

        [oneway] void acquire();

        [oneway] void release();

 

};

// fragment of the Interface com.sun.star.io.XInputStream

interface XInputStream: com::sun::star::uno::XInterface

{

    long readBytes( [out] sequence<byte> aData,

                    [in] long nBytesToRead )

                raises( com::sun::star::io::NotConnectedException,

                        com::sun::star::io::BufferSizeExceededException,

                        com::sun::star::io::IOException);

    ...

};

The [oneway] flag indicates that an operation will be executed asynchronously. For instance, the method acquire() in the interface com.sun.star.uno.XInterface is defined to be oneway.

There are also parameter flags. Each parameter definition begins with one of the direction flags in , out, or inout to specify the use of the parameter:

These parameter flags do not appear in the API reference. The fact that a parameter is an [out] or [inout] parameter is explained in the method details.

Interfaces consisting of operations form the basis for service specifications.

Services

We have seen that an interface describes only one aspect of an object. However, it is quite common that objects have more than one aspect. UNO uses services to specify complete objects which can have many aspects.

A service comprises a set of interfaces and properties that are needed to support a certain functionality. It can include other services as well. Services are abstract specifications which have to be implemented.

From the perspective of a user of a UNO object, the object offers one or sometimes even several services described in the API reference. The services are utilized through method calls grouped in interfaces, and through properties, which are handled through special interfaces as well. Because the access to the functionality is provided by interfaces only, the implementation is irrelevant to a user who wants to use a service.

From the perspective of an implementer of a UNO object, services are used to define a functionality independently of a programming language and without giving instructions about the internal implementation of the service. Implementing a service means that the component must implement all specified interfaces and properties. It is possible that a UNO object implements more than one service. Sometimes it is useful to implement two or more services because they have related functionality or the services support different views to the component.

shows the relationship between interfaces, services and components. The language independent specification of a service with several interfaces is used to implement a UNO component  that fulfills the specification.

Service specification against service implementation
Illustration 1.2: Interfaces, services and implementation

The functionality of a TV system with a TV set and a remote control can be described in terms of service specifications. The interfaces XPower and XChannel described above would be part of a service specification RemoteControl. The new service TVSet consists of the three interfaces XPower, XChannel and XStandby to control the power, the channel selection, the additional power function standby() and a timer() function.

UML diagram showing a TVSet service
Illustration 1.3: TV System Specification

Referencing Interfaces

References to interfaces in a service definition mean that an implementation of this service must offer the specified interfaces. However, optional interfaces are possible. If a service contains an optional interface, the service may or may not export this interface. If you utilize an optional interface of a UNO object, always check if the result of queryInterface() is equal to null and react accordingly—otherwise your code will not be compatible with implementations without the optional interface and you might end up with null pointer exceptions. The following UNO IDL snippet shows a fragment of the specification for the com.sun.star.text.TextDocument service in the OpenOffice.org API. Note the flag optional in square brackets, which makes the interfaces XFootnotesSupplier and XEndnotesSupplier non-mandatory.

// com.sun.star.text.TextDocument

service TextDocument

{

    ...

    interface com::sun::star::text::XTextDocument;

    interface com::sun::star::util::XSearchable;

    interface com::sun::star::util::XRefreshable;

    [optional] interface com::sun::star::text::XFootnotesSupplier;

    [optional] interface com::sun::star::text::XEndnotesSupplier;

    ...

};

Including Properties

When the structure of the OpenOffice.org API was founded, the designers discovered that the objects in an office environment would have huge numbers of qualities that did not appear to be part of the structure of the objects, rather they seemed to be superficial changes to the underlying objects. It was also clear that not all qualities would be present in each object of a certain kind. Therefore, instead of defining a complicated pedigree of optional and non-optional interfaces for each and every quality, the concept of properties was introduced. Properties are data in an object that are provided by name over a generic interface for property access, that contains getPropertyValue() and setPropertyValue() access methods. The concept of properties has other advantages, and there is more to know about properties. Please refer to 3.3.4 Professional UNO - UNO Concepts - Properties for further information about properties.

Properties are added to a service in its UNO IDL specification. A property defines a member variable with a specific type that is accessible at the implementing component by a specific name. It is possible to add further restrictions to a property through additional flags. The following service references one interface and three optional properties. All known API types can be valid property types:

// com.sun.star.text.TextContent

service TextContent

{

    interface com::sun::star::text::XTextContent;

    [optional, property] com::sun::star::text::TextContentAnchorType AnchorType;

    [optional, readonly, property] sequence<com::sun::star::text::TextContentAnchorType> AnchorTypes;

    [optional, property] com::sun::star::text::WrapTextMode TextWrap;

};

Possible property flags are:

Referencing other Services

Services can include other services. Such references may be optional. That a service is included by another service has nothing to do with implementation inheritance, only the specifications are combined. It is up to the implementer if he inherits or delegates the necessary functionality, or if he implements it from scratch.

The service com.sun.star.text.Paragraph in the following UNO IDL example includes one mandatory service com.sun.star.text.TextContent and five optional services. Every Paragraph must be a TextContent . It can be a TextTable and it is used to support formatting properties for paragraphs and characters:

// com.sun.star.text.Paragraph

service Paragraph

{

    service com::sun::star::text::TextContent;

    [optional] service com::sun::star::text::TextTable;

    [optional] service com::sun::star::style::ParagraphProperties;

    [optional] service com::sun::star::style::CharacterProperties;

    [optional] service com::sun::star::style::CharacterPropertiesAsian;

    [optional] service com::sun::star::style::CharacterPropertiesComplex;

    ...

};

Service Implementations in Components

A component is a shared library or Java archive containing implementations of one or more services in one of the target programming languages supported by UNO. Such a component must meet basic requirements, mostly different for the different target language, and it must support the specification of the implemented services. That means all specified interfaces and properties must be implemented. Components must be registered in the UNO runtime system. After the registration all implemented services can be used by ordering an instance of the service at the appropriate service factory and accessing the functionality over interfaces.

Based on our example specifications for a TVSet and a RemoteControl service, a component RemoteTVImpl could simulate a remote TV system:

UML diagram showing  a RmoteTV component
Illustration 1.4: RemoteTVImpl Component

Such a RemoteTV component could be a jar file or a shared library. It would contain two service implementations, TVSet and RemoteControl. Once the RemoteTV component  is registered with the global service manager, users can call the factory method of the service manager and ask for a TVSet or a RemoteControl service. Then they could use their functionality over the interfaces XPower, XChannel and XStandby. When a new  implementation of these services with better performance or new features is available later on, the old component can be replaced without breaking existing code, provided that the new features are introduced by adding interfaces.

Structs

A struct type defines several elements in a record. The elements of a struct are UNO IDL types with a unique name within the struct. Structs have the disadvantage not to encapsulate data, but the absence of get () and set() methods can help to avoid the overhead of method calls over a UNO bridge. UNO IDL supports single inheritance for struct types. A derived struct recursively inherits all elements of the parent and its parents.

// com.sun.star.lang.EventObject

/** specifies the base for all event objects and identifies the

        source of the event.

 */

struct EventObject

{

        /** refers to the object that fired the event.

        */

        com::sun::star::uno::XInterface Source;

 

};

// com.sun.star.beans.PropertyChangeEvent

struct PropertyChangeEvent : com::sun::star::lang::EventObject {

    string PropertyName;

    boolean Further;

    long PropertyHandle;

    any OldValue;

    any NewValue;

};

Predefined Values

The API offers many predefined values, that are used as method parameters, or returned by methods. In UNO IDL there are two different data types for predefined values: constants and enumerations.

const

A const defines a named value of a valid UNO IDL type. The value depends on the specified type and can be a literal (integer number, floating point number or a character), an identifier of another const type or an arithmetic term using the operators: +, -, *, /, ~, &, |, %, ^, <<, >>.

Since a wide selection of types and values is possible in a const, const is occasionally used to build bit vectors which encode combined values.

const short ID = 23;

const boolean ERROR = true;

const double PI = 3.1415;

Usually const definitions are part of a constants group.

constants

The constants type defines a named group of const values. A const in a constants group is denoted by the group name and the const name. In the UNO IDL example below, ImageAlign.RIGHT refers to the value 2:

constants ImageAlign {

    const short LEFT = 0;

    const short TOP = 1;

    const short RIGHT = 2;

    const short BOTTOM = 3;

};

enum

An enum type is equivalent to an enumeration type in C++. It contains an ordered list of one or more identifiers representing long values of the enum type. By default, the values are numbered sequentially, beginning with 0 and adding 1 for each new value. If an enum value has been assigned a value, all following enum values without a predefined value get a value starting from this assigned value.

// com.sun.star.uno.TypeClass

enum TypeClass {

    VOID,

    CHAR,

    BOOLEAN,

    BYTE,

    SHORT,

    ...

};

enum Error {

    SYSTEM = 10, // value 10

    RUNTIME,     // value 11

    FATAL,       // value 12

    USER = 30,   // value 30

    SOFT         // value 31

};

If enums are used during debugging, you should be able to derive the numeric value of an enum by counting its position in the API reference. However, never use literal numeric values instead of enums in your programs.

Pay attention to the following important text section

Once an enum type has been specified and published, you can trust that it is not extended later on, for that would break existing code. However, new const vaues may be added to a constant group.

Sequences

A sequence type is a set of elements of the same type, that has a variable number of elements. In UNO IDL, the used element always references an existing and known type or another sequence type. A sequence can occur as a normal type in all other type definitions.

sequence< com::sun::star::uno::XInterface >

sequence< string > getNamesOfIndex( sequence< long > indexes );

Modules

Modules are namespaces, similar to namespaces in C++ or packages in Java. They group services, interfaces, structs, exceptions, enums, typedefs, constant groups and submodules with related functional content or behavior. They are utilized to specify coherent blocks in the API, this allows for a well-structured API. For example, the module com.sun.star.text contains interfaces and other types for text handling. Some other typical modules are com.sun.star.uno, com.sun.star.drawing, com.sun.star.sheet and com.sun.star.table. Identifiers inside a module do not clash with identifiers in other modules, therefore it is possible for the same name to occur more than once. The global index of the API reference shows that this does happen.

Although it may seem that the modules correspond with the various parts of OpenOffice.org, there is no direct relationship between the API modules and the OpenOffice.org applications Writer, Calc and Draw. Interfaces from the module com.sun.star.text are used in Calc and Draw. Modules like com.sun.star.style or com.sun.star.document provide generic services and interfaces that are not specific to any one part of OpenOffice.org.

The modules you see in the API reference were defined by nesting UNO IDL types in module instructions. For example, the module com.sun.star.uno contains the interface XInterface:

module com {

    module sun {

        module star {

            module uno {

                interface XInterface {

                    ...

                };

            };

        };

    };

};

Exceptions

An exception type indicates an error to the caller of a function. The type of an exception gives a basic description of the kind of error that occurred. In addition, the UNO IDL exception types contain elements which allow for an exact specification and a detailed description of the error. The exception type supports inheritance, this is freqzuently used to define a hierarchy of errors. Exceptions are only used to raise errors, not  as method parameters or return types.

UNO IDL requires that all exceptions must inherit from com.sun.star.uno.Exception. This is a precondition for the UNO runtime.

// com.sun.star.uno.Exception is the base exception for all exceptions

exception Exception {

    string Message;

    Xinterface Context;

};

// com.sun.star.uno.RuntimeException is the base exception for serious problems

// occuring at runtime, usually programming errors or problems in 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 {

};

Exceptions may only be thrown by operations which were specified to do so. In contrast, com.sun.star.uno.RuntimeExceptions can always occur.

Pay attention to the following important text section

The methods acquire() and release of the UNO base interface com.sun.star.uno.XInterface are an exception to the above rule. They are the only operations that may not even throw runtime exceptions. But in Java and C++ programs, you do not use these methods directly, they are handled by the respective language binding.

Singletons

Singletons are used to specify named objects where exactly one instance can exist in the life of a UNO component context. A singleton references one service and specifies that the only existing instance of this service can be reached over the component context using the name of the singleton. If no instance of the service exists, the component context will instantiate a new one.

singleton theServiceManager {

    service com::sun::star::lang::ServiceManager

};

3.2.2    Understanding the API Reference

Specification, Implementation and Instances

The API specifications you find in the API reference are abstract. The service descriptions of the API reference are not about classes that previously exist somewhere. The specifications are first, then the UNO implementation is created according to the specification. That holds true even for legacy implementations that had to be adapted to UNO.

Moreover, since a component developer is free to implement services and interfaces as required, there is not necessarily a one-to-one relationship between a certain service specification and a real object. The real object can be capable of more things than specified in a service definition. For example, if you order a service at the factory or receive an object from a getter or getPropertyValue() method, the specified features will be present, but there may be additional features. For instance, the text document model has a few interfaces which are not included in the specification for the com.sun.star.text.TextDocument.

Because of the optional interfaces and properties, it is impossible to comprehend fully from the API reference what a given instance of an object in OpenOffice.org is capable of. The optional interfaces and properties are correct for an abstract specification, but it means that when you leave the scope of mandatory interfaces and properties, the reference only defines how things are allowed to work, not how they actually work.

Another important point is the fact that there are several entry points where service implementations are actually available. You cannot instantiate every service that can be found in the API reference by means of the global service manager. The reasons are:

Consequently, it is sometimes confusing to look up a needed functionality in the API reference, for you need a basic understanding how a functionality works, which services are involved, where they are available etc., before you can really utilize the reference. This manual aims at giving you this understanding about the OpenOffice.org document models, the database integration and the application itself.

Object Composition

Interfaces only support single inheritance and they are all based on com.sun.star.uno.XInterface. In the API reference, this is mirrored in the Base Hierarchy section of any interface specification. If you look up an interface, always check the base hierarchy section to understand the full range of supported methods. For instance, if you look up com.sun.star.text.XText, you see two methods, insertTextContent() and removeTextContent(), but  there are nine more methods provided by the inherited interfaces. The same applies to exceptions and sometimes also to structs, which support single inheritance as well.

The service specifications in the API reference can contain a section  Included Services , which is similar to the above in that a single included service might encompass a whole world of services. However, the fact that a service is included has nothing to do with class inheritance. In which manner a service implementation technically includes other services, by inheriting from base implementations, by aggregation, some other kind of delegation or simply by reimplementing everything is by no means defined. And it is uninteresting for an API user – he can absolutely rely on the availability of the described functionality, but he must never rely on inner details of the implementation, which classes provide the functionality, where they inherit from and what they delegate to other classes.

3.3    UNO Concepts

Now that you have an advanced understanding of OpenOffice.org API concepts and you understand the specification of UNO objects , we are ready to explore UNO, i.e. to see how UNO objects connect and communicate with each other.

3.3.1    UNO Interprocess Connections

UNO objects in different environments connect via the interprocess bridge. You can execute calls on UNO object instances, that are located in a different process. This is done by converting the method name and the arguments into a byte stream representation, and sending this package to the remote process, for example, through a socket connection. Most of the examples in this manual use the interprocess bridge to communicate with the OpenOffice.org.      

This section deals with the creation of UNO interprocess connections using the UNO API.

Starting OpenOffice.org in Listening Mode

Most examples in this developers guide connect to a running OpenOffice.org and perform API calls, which are then executed in OpenOffice.org. By default, the office does not listen on a resource for security reasons. This makes it necessary to make OpenOffice.org listen on an interprocess connection resource, for example, a socket. Currently this can be done in two ways:

The various parts of the connection URL will be discussed in the next section.

Importing a UNO Object

The most common use case of interprocess connections is to import a reference to a UNO object from an exporting server. For instance, most of the Java examples described in this manual retrieve a reference to the OpenOffice.org ComponentContext. The correct way to do this is using the com.sun.star.bridge.UnoUrlResolver service. Its main interface com.sun.star.bridge.XUnoUrlResolver is defined in the following way:

interface XUnoUrlResolver: com::sun::star::uno::XInterface

{
   /** 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);
};

The string passed to the resolve() method is called a UNO URL. It must have the following format:

Graphic showing an UNO Url scheme

An example URL could be uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager . The parts of this URL are:

  1. The URL schema uno:. This identifies the URL as UNO URL and distinguishes it from others, such as http: or ftp: URLs.

  2. A string which characterizes the type of connection to be used to access the other process. Optionally, directly after this string, a comma separated list of name-value pairs can follow, where name and value are separated by a '='. The currently supported connection types are described in 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections - Opening a Connection. The connection type specifies the transport mechanism used to transfer a byte stream, for example, TCP/IP sockets or named pipes.

  3. A string which characterizes the type of protocol used to communicate over the established byte stream connection. The string can be followed by a comma separated list of name-value pairs, which can be used to customize the protocol to specific needs. The suggested protocol is urp (UNO Remote Protocol). Some useful parameters are explained below. Refer to the document named UNO-URL at udk.openoffice.org. for the complete specification.

  4. A process must explicitly export a certain object by a distinct name. It is not possible to access an arbitrary UNO object (which would be possible with IOR in CORBA, for instance).

The following example demonstrates how to import an object using the UnoUrlResolver: (ProfUNO/InterprocessConn/UrlResolver.java):

    XComponentContext xLocalContext =

        com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);

       

    // initial serviceManager

    XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();

               

    // create a URL resolver

    Object urlResolver = xLocalServiceManager.createInstanceWithContext(

        "com.sun.star.bridge.UnoUrlResolver", xLocalContext);

    // query for the XUnoUrlResolver interface

    XUnoUrlResolver xUrlResolver =

        (XUnoUrlResolver) UnoRuntime.queryInterface(XUnoUrlResolver.class, urlResolver);

    // Import the object

    Object rInitialObject = xUrlResolver.resolve(

        “uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager”);

           

    // XComponentContext

    if (null != rInitialObject) {

        System.out.println("initial object successfully retrieved");

    } else {

        System.out.println("given initial-object name unknown at server side");

    }

The usage of the UnoUrlResolver has certain disadvantages. You cannot:

These issues are addressed by the underlying API, which is explained below. in 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections - Opening a Connection.

Characteristics of the Interprocess Bridge

The whole bridge is threadsafe and allows multiple threads to execute remote calls. The dispatcher thread inside the bridge cannot block because it never executes calls. It instead passes the requests to worker threads.

For synchronous requests, thread identity is guaranteed. When process A calls process B, and process B calls process A, the same thread waiting in process A will take over the new request. This avoids deadlocks when the same mutex is locked again. For asynchronous requests, this is not possible because there is no thread waiting in process A. Such requests are executed in a new thread. The series of calls between two processes is guaranteed. If two asynchronous requests from process A are sent to process B, the second request waits until the first request is finished.

The remote bridge can be started in a mode that disables the oneway feature and thus executes every call as a synchronous call. To do this, the protocol part of the UNO URL on the server and client must be extended by ',Negotiate=0,forceSynchronous=1' . For example:

soffice -accept=socket,host=0,port=2002;urp ,Negotiate=0,forceSynchronous=1 ;

for starting the office and

"uno:socket,host=localhost,port=2002;urp ,Negotiate=0,forceSynchronous=1 ;StarOffice.ServiceManager"

as UNO URL for connecting to it. This can be useful to avoid deadlocks within OpenOffice.org.

Pay attention to the following important text section

Do not activate this mode unless you experience such problems.

Opening a Connection

The method to import a UNO object using the UnoUrlResolver has drawbacks as described in the previous chapter. The layer below the UnoUrlResolver offers full flexibility in interprocess connection handling.

UNO interprocess bridges are established on the com.sun.star.connection.XConnection interface, which encapsulates a reliable bidirectional byte stream connection (such as a TCP/IP connection).

interface XConnection: com::sun::star::uno::XInterface

{

    long read( [out] sequence < byte > aReadBytes , [in] long nBytesToRead )

                raises( com::sun::star::io::IOException );

    void write( [in] sequence < byte > aData )

                raises( com::sun::star::io::IOException );

    void flush( ) raises( com::sun::star::io::IOException );

    void close( ) raises( com::sun::star::io::IOException );

    string getDescription();

};

There are different mechanisms to establish an interprocess connection. Most of these mechanisms follow a similar pattern. One process listens on a resource and waits for one or more processes to connect to this resource.

This pattern has been abstracted by the services com.sun.star.connection.Acceptor that exports the com.sun.star.connection.XAcceptor interface and com.sun.star.connection.Connector that exports the com.sun.star.connection.XConnector interface.

interface XAcceptor: com::sun::star::uno::XInterface

{

    XConnection accept( [in] string sConnectionDescription )

                raises( AlreadyAcceptingException,

                ConnectionSetupException,

                com::sun::star::lang::IllegalArgumentException);

 

    void stopAccepting();

};

interface XConnector: com::sun::star::uno::XInterface

{

    XConnection connect( [in] string sConnectionDescription )

                raises( NoConnectException,ConnectionSetupException );

};

The acceptor service is used in the listening process while the connector service is used in the actively connecting service. The methods accept() and connect() get the connection string as a parameter. This is the connection part of the UNO URL (between uno: and ;urp).

The connection string consists of a connection type followed by a comma separated list of name-value pairs. The following table shows the connection types that are supported by default.

Connection type

socket

Reliable TCP/IP socket connection

Parameter

Description

host

Hostname or IP number of the resource to listen on/connect. May be localhost. In an acceptor string, this may be 0 ('host=0'), which means, that it accepts on all available network interfaces.

port

TCP/IP port number to listen on/connect to.

tcpNoDelay

Corresponds to the socket option tcpNoDelay. For a UNO connection, this parameter should be set to 1 (this is NOT the default it must be added explicitly). If the default is used (0), it may come to 200 ms delays at certain call combinations.

pipe

A named pipe (uses shared memory). This type of interprocess connection is marginally faster than socket connections and works only if both processes are located on the same machine. It does not work on Java by default, because Java does not support named pipes directly

Parameter

Description

name

Name of the named pipe. Can only accept one process on name on one machine at a time.

Tip graphics marks a hint section in the text

You can add more kinds of interprocess connections by implementing connector and acceptor services, and choosing the service name by the scheme com.sun.star.connection.Connector.<connection-type>, where <connection-type> is the name of the new connection type.

If you implemented the service com.sun.star.connection.Connector.mytype, use the UnoUrlResolver with the URL 'uno:mytype,param1=foo;urp;StarOffice.ServiceManager' to establish the interprocess connection to the office.

Creating the Bridge

UML diagram showing the initiaton of a brdige
Illustration 1.5: The interaction of services that are needed to initiate a UNO interprocess bridge. The interfaces have been simplified.

The XConnection instance can now be used to establish a UNO interprocess bridge on top of the connection, regardless if the connection was established with a Connector or Acceptor service (or another method). To do this, you must instantiate the service com.sun.star.bridge.BridgeFactory. It supports the com.sun.star.bridge.XBridgeFactory interface.

interface XBridgeFactory: com::sun::star::uno::XInterface

{

    XBridge createBridge(

            [in] string sName,

            [in] string sProtocol ,

            [in] com::sun::star::connection::XConnection aConnection ,

            [in] XInstanceProvider anInstanceProvider )

        raises ( BridgeExistsException , com::sun::star::lang::IllegalArgumentException );

    XBridge getBridge( [in] string  sName );

    sequence < XBridge > getExistingBridges( );

};

The BridgeFactory service administrates all UNO interprocess connections. The createBridge() method creates a new bridge:

interface XInstanceProvider: com::sun::star::uno::XInterface

{

    com::sun::star::uno::XInterface getInstance( [in] string sInstanceName )

                raises ( com::sun::star::container::NoSuchElementException );

};

The BridgeFactory returns a com.sun.star.bridge.XBridge interface.

interface XBridge: com::sun::star::uno::XInterface

{

    XInterface getInstance( [in] string sInstanceName );

    string getName();

    string getDescription();

};

The XBridge.getInstance() method retrieves an initial object from the remote counterpart. The local XBridge.getInstance() call arrives in the remote process as an XInstanceProvider.getInstance() call. The object returned can be controlled by the string sInstanceName. It completely depends on the implementation of XInstanceProvider, which object it returns.

The XBridge interface can be queried for a com.sun.star.lang.XComponent interface, that adds a com.sun.star.lang.XEventListener to the bridge. This listener will be terminated when the underlying connection closes (see above). You can also call dispose() on the XComponent interface explicitly, which closes the underlying connection and initiates the bridge shutdown procedure.

Closing a Connection

The closure of an interprocess connection can occur for the following reasons:

Except for the first reason, all other connection closures initiate an interprocess bridge shutdown procedure. All pending synchronous requests abort with a com.sun.star.lang.DisposedException, which is derived from the com.sun.star.uno.RuntimeException. Every call that is initiated on a disposed proxy throws a DisposedException. After all threads have left the bridge (there may be a synchronous call from the former remote counterpart in the process), the bridge explicitly releases all stubs to the original objects in the local process, which were previously held by the former remote counterpart. The bridge then notifies all registered listeners about the disposed state using com.sun.star.lang.XEventListener. The example code for a connection-aware client below shows how to use this mechanism. The bridge itself is destroyed, after the last proxy has been released.

Unfortunately, the various listed error conditions are not distinguishable.

Example: A Connection Aware Client

The following example shows an advanced client which can be informed about the status of the remote bridge. A complete example for a simple client is given in the chapter 2 First Steps.

The following Java example opens a small awt window containing the buttons new writer and new calc that opens a new document and a status label. It connects to a running office when a button is clicked for the first time. Therefore it uses the connector/bridge factory combination, and registers itself as an event listener at the interprocess bridge.

When the office is terminated, the disposing event is terminated, and the Java program sets the text in the status label to 'disconnected' and clears the office desktop reference. The next time a button is pressed, the program knows that it has to re-establish the connection.

The method getComponentLoader() retrieves the XComponentLoader reference on demand:

(ProfUNO/InterprocessConn/ConnectionAwareClient.java)

    XComponentLoader _officeComponentLoader = null;

    // local component context

    XComponentContext _ctx;    

    protected com.sun.star.frame.XComponentLoader getComponentLoader()

            throws com.sun.star.uno.Exception {

        XComponentLoader officeComponentLoader = _officeComponentLoader;

        if (officeComponentLoader == null) {

            // instantiate connector service

            Object x = _ctx.getServiceManager().createInstanceWithContext(

                "com.sun.star.connection.Connector", _ctx);

           

            XConnector xConnector = (XConnector) UnoRuntime.queryInterface(XConnector.class, x);

            // helper function to parse the UNO URL into a string array

            String a[] = parseUnoUrl(_url);

            if (null == a) {

                throw new com.sun.star.uno.Exception("Couldn't parse UNO URL "+ _url);

            }

            // connect using the connection string part of the UNO URL only.

            XConnection connection = xConnector.connect(a[0]);

       

            x = _ctx.getServiceManager().createInstanceWithContext(

                "com.sun.star.bridge.BridgeFactory", _ctx);

            XBridgeFactory xBridgeFactory = (XBridgeFactory) UnoRuntime.queryInterface(

                XBridgeFactory.class , x);

            // create a nameless bridge with no instance provider

            // using the middle part of the UNO URL

            XBridge bridge = xBridgeFactory.createBridge("" , a[1] , connection , null);

            // query for the XComponent interface and add this as event listener

            XComponent xComponent = (XComponent) UnoRuntime.queryInterface(

                XComponent.class, bridge);

            xComponent.addEventListener(this);

            // get the remote instance

            x = bridge.getInstance(a[2]);

            // Did the remote server export this object ?

            if (null == x) {

                throw new com.sun.star.uno.Exception(

                    "Server didn't provide an instance for" + a[2], null);

            }

     

            // Query the initial object for its main factory interface

            XMultiComponentFactory xOfficeMultiComponentFactory = (XMultiComponentFactory)

                UnoRuntime.queryInterface(XMultiComponentFactory.class, x);

            // retrieve the component context (it's not yet exported from the office)

            // Query for the XPropertySet interface.

            XPropertySet xProperySet = (XPropertySet)

                UnoRuntime.queryInterface(XPropertySet.class, xOfficeMultiComponentFactory);

           

            // Get the default context from the office server.

            Object oDefaultContext =

                xProperySet.getPropertyValue("DefaultContext");

           

            // Query for the interface XComponentContext.

            XComponentContext xOfficeComponentContext =

                (XComponentContext) UnoRuntime.queryInterface(

                    XComponentContext.class, oDefaultContext);

           

            // now create the desktop service

            // NOTE: use the office component context here !

            Object oDesktop = xOfficeMultiComponentFactory.createInstanceWithContext(

                "com.sun.star.frame.Desktop", xOfficeComponentContext);

            officeComponentLoader = (XComponentLoader)

                UnoRuntime.queryInterface( XComponentLoader.class, oDesktop);

            if (officeComponentLoader == null) {

                throw new com.sun.star.uno.Exception(

                    "Couldn't instantiate com.sun.star.frame.Desktop" , null);

            }

            _officeComponentLoader = officeComponentLoader;

        }

        return officeComponentLoader;

    }

This is the button event handler:

    public void actionPerformed(ActionEvent event) {

        try {

            String sUrl;

            if (event.getSource() == _btnWriter) {

                sUrl = "private:factory/swriter";

            } else {

                sUrl = "private:factory/scalc";

            }

            getComponentLoader().loadComponentFromURL(

                sUrl, "_blank", 0,new com.sun.star.beans.PropertyValue[0]);

            _txtLabel.setText("connected");

        } catch (com.sun.star.connection.NoConnectException exc) {

            _txtLabel.setText(exc.getMessage());

        } catch (com.sun.star.uno.Exception exc) {

            _txtLabel.setText(exc.getMessage());

            exc.printStackTrace();

            throw new java.lang.RuntimeException(exc.getMessage());

        }

    }

And the disposing handler clears the _officeComponentLoader reference:

    public void disposing(com.sun.star.lang.EventObject event) {

        // remote bridge has gone down, because the office crashed or was terminated.

        _officeComponentLoader = null;

        _txtLabel.setText("disconnected");

    }

3.3.2    Service Manager and Component Context

This chapter discusses the root object for connections to OpenOffice.org (and to any UNO application) – the service manager. The root object serves as the entry point for every UNO application and is passed to every UNO component during instantiation.

Two different concepts to get the root object currently exist. StarOffice6.0 and OpenOffice.org1.0 use the previous concept. Newer versions or product patches use the the newer concept and provide the previous concept for compatibility issues only. First we will look at the previous concept, the service manager as it is used in the main parts of the underlying OpenOffice.org implementation of this guide. Second, we will introduce the component context—which is the newer concept and explain the migration path.

Service Manager

The com.sun.star.lang.ServiceManager is the main factory in every UNO application. It instantiates services by their service name, to enumerate all implementations of a certain service, and to add or remove factories for a certain service at runtime. The service manager is passed to every UNO component during instantiation.

XMultiServiceFactory Interface

The main interface of the service manager is the com.sun.star.lang.XMultiServiceFactory interface. It offers three methods: createInstance(), createInstanceWithArguments() and getAvailableServiceNames().

interface XMultiServiceFactory: com::sun::star::uno::XInterface

{

    com::sun::star::uno::XInterface createInstance( [in] string aServiceSpecifier )

        raises( com::sun::star::uno::Exception );

    com::sun::star::uno::XInterface createInstanceWithArguments(

            [in] string ServiceSpecifier,

            [in] sequence<any> Arguments )

        raises( com::sun::star::uno::Exception );

    sequence<string> getAvailableServiceNames();

};

When using the service name, the caller does not have any influence on which concrete implementation is instantiated. If multiple implementations for a service exist, the service manager is free to decide which one to employ. This in general does not make a difference to the caller because every implementation does fulfill the service contract. Performance or other details may make a difference. So it is also possible to pass the implementation name instead of the service name, but it is not advised to do so as the implementation name may change.

In case the service manager does not provide an implementation for a request, a null reference is returned, so it is mandatory to check. Every UNO exception may be thrown during instantiation. Some may be described in the specification of the service that is to be instantiated, for instance, because of a misconfiguration of the concrete implementation. Another reason may be the lack of a certain bridge, for instance the Java-C++ bridge, in case a Java component shall be instantiated from C++ code.

XContentEnumerationAccess Interface

The com.sun.star.container.XContentEnumerationAccess interface allows the creation of an enumeration of all implementations of a concrete servicename.

interface XContentEnumerationAccess: com::sun::star::uno::XInterface

{

    com::sun::star::container::XEnumeration createContentEnumeration( [in] string aServiceName );

 

    sequence<string> getAvailableServiceNames();

 

};

The createContentEnumeration() method returns a com.sun.star.container.XEnumeration interface. Note that it may return an empty reference in case the enumeration is empty.

interface XEnumeration: com::sun::star::uno::XInterface

{

    boolean hasMoreElements();

 

    any nextElement()

        raises( com::sun::star::container::NoSuchElementException,

                com::sun::star::lang::WrappedTargetException );

 

};

In the above case, the returned any of the method Xenumeration.nextElement() contains a com.sun.star.lang.XSingleServiceFactory interface for each implementation of this specific service. You can, for instance, iterate over all implementations of a certain service and check each one for additional implemented services. The XSingleServiceFactory interface provides such a method. With this method, you can instantiate a feature rich implementation of a service.

XSet Interface

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 to the service manager at runtime without making the changes permanent. When the office application terminates, all the changes are lost. The object must also support the com.sun.star.lang.XServiceInfo interface that provides information about the implementation name and supported services of the component implementation.

This feature may be of particular interest during the development phase. For instance, you can connect to a running office, insert a new factory into the service manager and directly instantiate the new service without having it registered before.

The chapter 4.9.6 Writing UNO Components - Deployment Options for Components - Special Service Manager Configurations shows an example that demonstrates how a factory is inserted into the service manager.

Component Context

The service manager was described above as the main factory that is passed to every new instantiated component. Often a component needs more functionality or information that must be exchangeable after deployment of an application. In this context, the service manager approach is limited.

Therefore, the concept of the component context was created. In future, it will be the central object in every UNO application. It is basically a read-only container offering named values. One of the named values is the service manager. The component context is passed to a component during its instantiation. This can be understood as an environment where components live (the relationship is similar to shell environment variables and an executable program).

UML diagram showing an CompoenntContext
Illustration 1.6: ComponentContext and the ServiceManager

ComponentContext API

The component context only supports the com.sun.star.uno.XComponentContext interface.

// module com::sun::star::uno

interface XComponentContext : XInterface

{

    any getValueByName( [in] string Name );

    com::sun::star::lang::XMultiComponentFactory getServiceManager();

};

The getValueByName() method returns a named value. The getServiceManager() is a convenient way to retrieve the value named /singleton/com.sun.star.lang.theServiceManager. It returns the ServiceManager singleton, because most components need to access the service manager. The component context offers at least three kinds of named values:

Singletons (/singletons/...)

The singleton concept was introduced in 3.2.1 Professional UNO - API Concepts - Data Types. In OpenOffice.org 1.0.2 there is only the ServiceManager singleton. From OpenOffice.org 1.1, a singleton  /singletons/com.sun.star.util.theMacroExpander has been added, which can be used to expand macros in configuration files. Other possible singletons can be found in the IDL reference.

Implementation properties (not yet defined)

These properties customize a certain implementation and are specified in the module description of each component. A module description is an xml-based description of a module (DLL or jar file) which contains the formal description of one or more components.

Service properties (not yet defined)

These properties can customize a certain service independent from the implementation and are specified in the IDL specification of a service.
Note that service context properties are different from service properties. Service context properties are not subject to change and are the same for every instance of the service that shares the same component context. Service properties are different for each instance and can be changed at runtime through the XPropertySet interface.

Note, that in the scheme above, the ComponentContext has a reference to the service manager, but not conversely.

Besides the interfaces discussed above, the ServiceManager supports the com.sun.star.lang.XMultiComponentFactory interface.

interface XMultiComponentFactory : com::sun::star::uno::XInterface

{

        com::sun::star::uno::XInterface createInstanceWithContext(

                        [in] string aServiceSpecifier,

                        [in] com::sun::star::uno::XComponentContext Context )

                        raises (com::sun::star::uno::Exception);

   

        com::sun::star::uno::XInterface createInstanceWithArgumentsAndContext(

                        [in] string ServiceSpecifier,

                        [in] sequence<any> Arguments,

                        [in] com::sun::star::uno::XComponentContext Context )

                        raises (com::sun::star::uno::Exception);

   

        sequence< string > getAvailableServiceNames();

};

It replaces the XMultiServiceFactory interface. It has an additional XComponentContext parameter for the two object creation methods. This parameter enables the caller to define the component context that the new instance of the component receives. Most components use their initial component context to instantiate new components. This allows for context propagation.

Context propagation
Illustration 1.7 : Context propagation.

The illustration above shows the context propagation. A user might want a special component to get a customized context. Therefore, the user creates a new context by simply wrapping an existing one. The user overrides the desired values and delegates the properties that he is not interested into the original C1 context.The user defines which context Instance A and B receive. Instance A and B  propagate their context to every new object that they create. Thus, the user has established two instance trees, the first tree completely uses the context Ctx C1, while the second tree uses Ctx C2.

Availability

The final API for the component context is available in StarOffice 6.0 and OpenOffice 1.0. Use this API instead of the API explained in the service manager section. Currently the component context does not have a persistent storage, so named values can not be added to the context of a deployed OpenOffice.org. Presently, there is no additional benefit from the new API until there is a future release.

Compatibility Issues and Migration Path

Relation between service manager and component context
Illustration 1.8Compromise between service-manger-only und component context concept

As discussed previously, both concepts are currently used within the office. The ServiceManager supports the interfaces com.sun.star.lang.XMultiServiceFactory and com.sun.star.lang.XMultiComponentFactory. Calls to the XMultiServiceFactory interface are delegated to the XMultiComponentFactory interface. The service manager uses its own XComponentContext reference to fill the missing parameter. The component context of the ServiceManager can be retrieved through the XPropertySet interface as 'DefaultContext'.

// Query for the XPropertySet interface.

// Note xOfficeServiceManager is the object retrieved by the

// UNO URL resolver

XPropertySet xPropertySet = (XPropertySet)

        UnoRuntime.queryInterface(XPropertySet.class, xOfficeServiceManager);

               

// Get the default context from the office server.

Object oDefaultContext = xpropertysetMultiComponentFactory.getPropertyValue("DefaultContext");

               

// Query for the interface XComponentContext.

xComponentContext = (XComponentContext) UnoRuntime.queryInterface(

        XComponentContext.class, objectDefaultContext);

This solution allows the use of the same service manager instance, regardless if it uses the old or  new style API. In future, the whole OpenOffice.org code will only use the new API. However, the old API will still remain to ensure compatibility.

Note graphics marks a special text section

The described compromise has a drawback. The service manager now knows the component context, that was not necessary in the original design. Thus, every component that uses the old API (plain createInstance()) breaks the context propagation (see Illustration ). Therefore, it is recommended to use the new API in every new piece of code that is written.

3.3.3    Using UNO Interfaces

Every UNO object must inherit from the interface com.sun.star.uno.XInterface. Before using an object, know how to use it and how long it will function. By prescribing XInterface to be the base interface for each and every UNO interface, UNO lays the groundwork for object communication.

// module com::sun::star::uno

interface XInterface

{

    any queryInterface( [in] type aType );

    [oneway] void acquire();

    [oneway] void release();

};

The methods acquire() and release() handle the lifetime of the UNO object by reference counting. Detailed information about Reference counting is discussed in chapter 3.3.7 Professional UNO - UNO Concepts - Lifetime of UNO Objects. All current language bindings take care of acquire() and release() internally whenever there is a reference to a UNO object.

The queryInterface() method obtains other interfaces exported by the object. The caller asks the implementation of the object if it supports the interface specified by the type argument. The type parameter is an UNO IDL base type, and generally stores the name of a type and its com.sun.star.uno.TypeClass. The call may return with an interface reference of the requested type or with a void any. In C++ or Java simply test if the result is not equal null.

Unknowingly, we encountered XInterface when the service manager was asked to create a service instance:

    XComponentContext xLocalContext =

        com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);

       

    // initial serviceManager

    XMultiComponentFactory xLocalServiceManager = xLocalContext.getServiceManager();

               

    // create a urlresolver

    Object urlResolver  = xLocalServiceManager.createInstanceWithContext(

        "com.sun.star.bridge.UnoUrlResolver", xLocalContext);

The IDL specification of XmultiComponentFactory shows:

// module com::sun::star::lang

interface XMultiComponentFactory : com::sun::star::uno::XInterface

{

    com::sun::star::uno::XInterface createInstanceWithContext(

            [in] string aServiceSpecifier,

            [in] com::sun::star::uno::XComponentContext Context )

        raises (com::sun::star::uno::Exception);

    ...

}

The above code shows that createInstanceWithContext() provides an instance of the given service, but it only returns a com.sun.star.uno.XInterface. This is mapped to java.lang.Object by the Java UNO binding afterwards.

In order to access a service, you need to know which interfaces the service exports. This information is available in the IDL reference. For instance, for the com.sun.star.bridge.UnoUrlResolver service, you learn:

// module com::sun::star::bridge

service UnoUrlResolver

{

    interface com::sun::star::bridge::XUnoUrlResolver;

};

This means the service you ordered at the service manager must support com.sun.star.bridge.XUnoUrlResolver. Next query the returned object for this interface:

// query urlResolver for its com.sun.star.bridge.XUnoUrlResolver interface

XUnoUrlResolver xUrlResolver = (XUnoUrlResolver)

    UnoRuntime.queryInterface(UnoUrlResolver.class, urlResolver);

// test if the interface was available

if (null == xUrlResolver) {

    throw new java.lang.Exception(

        “Error: UrlResolver service does not export XUnoUrlResolver interface”);

}

// use the interface

Object remoteObject = xUrlResolver.resolve(

    “uno:socket,host=0,port=2002;urp;StarOffice.ServiceManager”);       

The object decides whether or not it returns the interface. You have encountered a bug if the object does not return an interface that is specified to be mandatory in a service. When the interface reference is retrieved, invoke a call on the reference according to the interface specification. You can follow this strategy with every service you instantiate at a service manager, leading to success.

With this method, you may not only get UNO objects through the service manager, but also by normal interface calls:

// Module com::sun::star::text

interface XTextRange: com::sun::star::uno::XInterface

{

    XText getText();

    XTextRange getStart();

    ....

};

The returned interface types are specified in the operations, so that calls can be invoked directly on the returned interface. Often, an object implementing multiple interfaces are returned, instead of an object implementing one certain interface.

You can then query the returned object for the other interfaces specified in the given service, here com.sun.star.drawing.Text.

UNO has a number of generic interfaces. For example, the interface com.sun.star.frame.XComponentLoader:

// module com::sun::star::frame

interface XComponentLoader: com::sun::star::uno::XInterface

{

    com::sun::star::lang::XComponent loadComponentFromURL( [in] string aURL,

            [in] string aTargetFrameName,

            [in] long nSearchFlags,

            [in] sequence<com::sun::star::beans::PropertyValue> aArgs )

        raises( com::sun::star::io::IOException,

                com::sun::star::lang::IllegalArgumentException );

};

It becomes difficult to find which interfaces are supported beside XComponent, because the kind of returned document (text, calc, draw, etc.) depends on the incoming URL.

These dependencies are described in the appropriate chapters of this manual.

Tools such as the InstanceInspector component is a quick method to find out which interfaces a certain object supports. The InstanceInspector component comes with the OpenOffice.org SDK that allows the inspection of a certain object at runtime. Do not rely on implementation details of certain objects. If an object supports more interfaces than specified in the service description, query the interface and perform calls. The code may only work for this distinct office version and not work with an update of the office!

Note graphics marks a special text section

Unfortunately, there may still be bugs in the service specifications. Please provide feedback about missing interfaces to openoffice.org to ensure that the specification is fixed and that you can rely on the support of this interface.

There are certain specifications a queryInterface() implementation must not violate:

These specifications must not be violated because a UNO runtime environment may choose to cache queryInterface() calls. The rules are basically identical to the rules of QueryInterface in MS COM.

3.3.4    Properties

Properties are name-value pairs belonging to a service and determine the characteristics of an object in a service instance. Usually, properties are used for non-structural attributes, such as font, size or color of objects, whereas get and set methods are used for structural attributes like a parent or sub-object.

In almost all cases, com.sun.star.beans.XPropertySet is used to access properties by name. Other interfaces, for example, are com.sun.star.beans.XPropertyAccess which is used to set and retrieve all properties at once or com.sun.star.beans.XMultiPropertySet which is used to access several specified properties at once. This is useful on remote connections. Additionally, there are interfaces to access properties by numeric ID, such as com.sun.star.beans.XFastPropertySet.

The following example demonstrates how to query and change the properties of a given text document cursor using its XPropertySet interface:

    // get an XPropertySet, here the one of a text cursor

    XPropertySet xCursorProps = (XPropertySet)

            UnoRuntime.queryInterface(XPropertySet.class, mxDocCursor);

    // get the character weight property

    Object aCharWeight = xCursorProps.getPropertyValue("CharWeight");

    float fCharWeight = AnyConverter.toFloat(aCharWeight);

    System.out.println("before: CharWeight=" + fCharWeight);

    // set the character weight property to BOLD

    xCursorProps.setPropertyValue("CharWeight", new Float(com.sun.star.awt.FontWeight.BOLD));

    // get the character weight property again

    aCharWeight = xCursorProps.getPropertyValue("CharWeight");

    fCharWeight = AnyConverter.toFloat(aCharWeight);

    System.out.println("after: CharWeight=" + fCharWeight);

A possible output of this code could be:

before: CharWeight=100.0

after: CharWeight=150.0

Pay attention to the following important text section

The sequence of property names must be sorted.

The following example deals with multiple properties at once:

// get an XMultiPropertySet, here the one of the first paragraph

XEnumerationAccess xEnumAcc = (XEnumerationAccess) UnoRuntime.queryInterface(

    XEnumerationAccess.class, mxDocText);

XEnumeration xEnum = xEnumAcc.createEnumeration();

Object aPara = xEnum.nextElement();

XMultiPropertySet xParaProps = (XMultiPropertySet) UnoRuntime.queryInterface(

    XMultiPropertySet.class, aPara);

// get three property values with a single UNO call

String[] aNames = new String[3];

aNames[0] = "CharColor";

aNames[1] = "CharFontName";

aNames[2] = "CharWeight";

Object[] aValues = xParaProps.getPropertyValues(aNames);

// print the three values

System.out.println("CharColor=" + AnyConverter.toLong(aValues[0]));

System.out.println("CharFontName=" + AnyConverter.toString(aValues[1]));

System.out.println("CharWeight=" + AnyConverter.toFloat(aValues[2]));

Properties can be assigned flags to determine a specific behavior of the property, such as read-only, bound, constrained or void. Possible flags are specified in com.sun.star.beans.PropertyAttribute. Read-only properties cannot be set. Bound properties broadcast changes of their value to registered listeners and constrained properties veto changes to these listeners.

Properties might have a status specifying where the value comes from. See com.sun.star.beans.XPropertyState. The value determines if the value comes from the object, a style sheet or if it cannot be determined at all. For example, in a multi-selection with multiple values within this selection.

The following example shows how to find out status information about property values:

    // get an XPropertySet, here the one of a text cursor

    XPropertySet xCursorProps = (XPropertySet) UnoRuntime.queryInterface(

        XPropertySet.class, mxDocCursor);

    // insert “first” in NORMAL character weight

    mxDocText.insertString(mxDocCursor, "first ", true);

    xCursorProps.setPropertyValue("CharWeight", new Float(com.sun.star.awt.FontWeight.NORMAL));

    // append “second” in BODL characer weight

    mxDocCursor.collapseToEnd();

    mxDocText.insertString(mxDocCursor, "second", true);

    xCursorProps.setPropertyValue("CharWeight", new Float(com.sun.star.awt.FontWeight.BOLD));

    // try to get the character weight property of BOTH words

    mxDocCursor.gotoStart(true);

    try {

        Object aCharWeight = xCursorProps.getPropertyValue("CharWeight");

        float fCharWeight = AnyConverter.toFloat(aCharWeight );

        System.out.println("CharWeight=" + fCharWeight);

    } catch (NullPointerException e) {

        System.out.println("CharWeight property is NULL");

    }

    // query the XPropertState interface of the cursor properties

    XPropertyState xCursorPropsState = (XPropertyState) UnoRuntime.queryInterface(

        XPropertyState.class, xCursorProps);

    // get the status of the character weight property

    PropertyState eCharWeightState = xCursorPropsState.getPropertyState("CharWeight");

    System.out.print("CharWeight property state has ");

    if (eCharWeightState == PropertyState.AMBIGUOUS_VALUE)

        System.out.println("an ambiguous value");

    else

        System.out.println("a clear value");

The property state of character weight is queried for a string like this:

        first second

And the output is:

CharWeight property is NULL

CharWeight property state has an ambiguous value

The description of properties available for a certain object is given by com.sun.star.beans.XPropertySetInfo. Multiple objects can share the same property information for their description. This makes it easier for introspective caches that are used in scripting languages where the properties are accessed directly, without directly calling the methods of the interfaces mentioned above.

This example shows how to find out which properties an object provides using com.sun.star.beans.XPropertySetInfo:

try {

    // get an XPropertySet, here the one of a text cursor

    XPropertySet xCursorProps = (XPropertySet)UnoRuntime.queryInterface(

        XPropertySet.class, mxDocCursor);

    // get the property info interface of this XPropertySet

    XPropertySetInfo xCursorPropsInfo = xCursorProps.getPropertySetInfo();

    // get all properties (NOT the values) from XPropertySetInfo

    Property[] aProps = xCursorPropsInfo.getProperties();

    int i;

    for (i = 0; i < aProps.length; ++i) {

        // number of property within this info object

        System.out.print("Property #"  + i);

        // name of property

        System.out.print(": Name<" + aProps[i].Name);

        // handle of property (only for XFastPropertySet)

        System.out.print("> Handle<" + aProps[i].Handle);

        // type of property

        System.out.print("> " + aProps[i].Type.toString());

        // attributes (flags)

        System.out.print(" Attributes<");

        short nAttribs = aProps[i].Attributes;

        if ((nAttribs & PropertyAttribute.MAYBEVOID) != 0)

            System.out.print("MAYBEVOID|");

        if ((nAttribs & PropertyAttribute.BOUND) != 0)

        System.out.print("BOUND|");

        if ((nAttribs & PropertyAttribute.CONSTRAINED) != 0)

            System.out.print("CONSTRAINED|");

        if ((nAttribs & PropertyAttribute.READONLY) != 0)

            System.out.print("READONLY|");

        if ((nAttribs & PropertyAttribute.TRANSIENT) != 0)

            System.out.print("TRANSIENT|");

        if ((nAttribs & PropertyAttribute.MAYBEAMBIGUOUS ) != 0)

            System.out.print("MAYBEAMBIGUOUS|");

        if ((nAttribs & PropertyAttribute.MAYBEDEFAULT) != 0)

            System.out.print("MAYBEDEFAULT|");

        if ((nAttribs & PropertyAttribute.REMOVEABLE) != 0)

            System.out.print("REMOVEABLE|");

        System.out.println("0>");

    }

} catch (Exception e) {

    // If anything goes wrong, give the user a stack trace

    e.printStackTrace(System.out);

}

The following is an example output for the code above. The output shows the names of the text cursor properties, and their handle, type and property attributes. The handle is not unique, since the specific object does not implement com.sun.star.beans.XFastPropertySet, so proper handles are not needed here.

Using default connect string: socket,host=localhost,port=8100

Opening an empty Writer document

Property #0: Name<BorderDistance> Handle<93> Type<long> Attributes<MAYBEVOID|0>

Property #1: Name<BottomBorder> Handle<93> Type<com.sun.star.table.BorderLine> Attributes<MAYBEVOID|0>

Property #2: Name<BottomBorderDistance> Handle<93> Type<long> Attributes<MAYBEVOID|0>

Property #3: Name<BreakType> Handle<81> Type<com.sun.star.style.BreakType> Attributes<MAYBEVOID|0>

...

Property #133: Name<TopBorderDistance> Handle<93> Type<long> Attributes<MAYBEVOID|0>

Property #134: Name<UnvisitedCharStyleName> Handle<38> =Type<string> Attributes<MAYBEVOID|0>

Property #135: Name<VisitedCharStyleName> Handle<38> Type<string> Attributes<MAYBEVOID|0>

In some cases properties are used to specify the options in a sequence of com.sun.star.beans.PropertyValue. See com.sun.star.view.PrintOptions or com.sun.star.document.MediaDescriptor for examples properties in sequences. These are not accessed by the methods mentioned above, but by accessing the sequence specified in the language binding.

This example illustrates how to deal with sequences of property values:

// create a sequence of PropertyValue

PropertyValue[] aArgs = new PropertyValue[2];

// set name/value pairs (other fields are irrelevant here)

aArgs[0] = new PropertyValue();

aArgs[0].Name = "FilterName";

aArgs[0].Value = "HTML (StarWriter)";

aArgs[1] = new PropertyValue();

aArgs[1].Name = "Overwrite";

aArgs[1].Value = Boolean.TRUE;

// use this sequence of PropertyValue as an argument

// where a service with properties but witouth any interfaces is specified

com.sun.star.frame.XStorable xStorable = (com.sun.star.frame.XStorable) UnoRuntime.queryInterface(

    com.sun.star.frame.XStorable.class, mxDoc);

xStorable.storeAsURL("file:///tmp/devmanual-test.html", aArgs);

Usually the properties supported by an object, as well as their type and flags are fixed over the lifetime of the object. There may be exceptions. If the properties can be added and removed externally, the interface com.sun.star.beans.XPropertyContainer has to be used. In this case, the fixed com.sun.star.beans.XPropertySetInfo changes its supplied information over the lifetime of the object. Listeners for such changes can register at com.sun.star.beans.XPropertyChangeListener.

Tip graphics marks a hint section in the text

If you use a component from other processes or remotely, try to adhere to the rule to use com.sun.star.beans.XPropertyAccess and com.sun.star.beans.XMultiPropertySet instead of having a separate call for each single property.

The following diagram shows the relationship between the property-related interfaces.

UML diagram showing the relationship of property-related interfaces
Illustration 1.9: Properties

3.3.5    Collections and Containers

Collections and containers are concepts for objects that contain multiple sub-objects where the number of sub-objects is usually not predetermined. While the term collection is used when the sub-objects are implicitly determined by the collection itself, the term container is used when it is possible to add new sub-objects and remove existing sub-objects explicitly. Thus, containers add methods like insert() and remove() to the collection interfaces.

UML diagram showing the interfaces of  the com.sun.star.conatiner module
Illustration 1.10: Interfaces in com.sun.star.container

In general, the OpenOffice.org API collection and container interfaces contain any type that can be represented by the UNO type any. However, many container instances can be bound to a specific type or subtypes of this type. This is a runtime and specification agreement, and cannot be checked at runtime.

The base interface for collections is com.sun.star.container.XElementAccess that determines the types of the sub-object, if they are determined by the collection, and the number of contained sub-objects. Based on XElementAccess, there are three main types of collection interfaces:

com.sun.star.container.XIndexAccess is extended by com.sun.star.container.XIndexReplace to replace existing sub-objects by index, and com.sun.star.container.XIndexContainer to insert and remove sub-objects. You can find the same similarity for com.sun.star.container.XNameAccess and other specific collection types.

All containers support com.sun.star.container.XContainer that has interfaces to register com.sun.star.container.XContainerListener interfaces. This way it is possible for an application to learn about insertion and removal of sub-objects in and from the container.

Note graphics marks a special text section

The com.sun.star.container.XIndexAccess is appealing to programmers because in most cases, it is easy to implement. But this interface should only be implemented if the collection really is indexed.

Refer to the module com.sun.star.container in the API reference for details about collection and container interfaces.

The following examples demonstrate the usage of the three main collection interfaces. First, we iterate through an indexed collection. The index always starts with 0 and is continuous:

// get an XIndexAccess interface from the collection

XIndexAccess xIndexAccess = (XIndexAccess) UnoRuntime.queryInterface(

    XIndexAccess.class, mxCollection);

// iterate through the collection by index

int i;

for (i = 0; i < xIndexAccess.getCount(); ++i) {

    Object aSheet = xIndexAccess.getByIndex(i);

    Named xSheetNamed = (XNamed) oRuntime.queryInterface(XNamed.class, aSheet);

    System.out.println("sheet #" + i + " is named '" + xSheetNamed.getName() + "'");

}

Our next example iterates through a collection with named objects. The element names are unique within the collection and case sensitive.

// get an XNameAccess interface from the collection

XNameAccess xNameAccess = (XNameAccess) UnoRuntime.queryInterface(XNameAccess.class, mxCollection);

// get the list of names

String[] aNames = xNameAccess.getElementNames();

// iterate through the collection by name

int i;

for (i = 0; i < aNames.length; ++i) {

    // get the i-th object as a UNO Any

    Object aSheet = xNameAccess.getByName( aNames[i] );

    // get the name of the sheet from its XNamed interface

    XNamed xSheetNamed = (XNamed) UnoRuntime.queryInterface(XNamed.class, aSheet);

    System.out.println("sheet '" + aNames[i] + "' is #" + i);

}

The next example shows how we iterate through a collection using an enumerator. The order of the enumeration is undefined. It is only defined that all elements are enumerated. The behavior is undefined, if the collection is modified after creation of the enumerator.

// get an XEnumerationAccess interface from the collection

XEnumerationAccess xEnumerationAccess = (XEnumerationAccess) UnoRuntime.queryInterface(

    XEnumerationAccess.class, mxCollection );

// create an enumerator

XEnumeration xEnum = xEnumerationAccess.createEnumeration();

// iterate through the collection by name

while (xEnum.hasMoreElements()) {

    // get the next element as a UNO Any

    Object aSheet = xEnum.nextElement();

    // get the name of the sheet from its XNamed interface

    XNamed xSheetNamed = (XNamed) UnoRuntime.queryInterface(XNamed.class, aSheet);

    System.out.println("sheet '" + xSheetNamed.getName() + "'");

}

For an example showing the use of containers, see 7.4.1 Text Documents - Overall Document Features - Styles where a new style is added into the style family ParagraphStyles.

3.3.6    Event Model

Events are a well known concept in graphical user interface (GUI) models, although they can be used in many contexts. The purpose of events is to notify an application about changes in the components used by the application. In a GUI environment, for example, an event might be the click on a button. Your application might be registered to this button and thus be able to execute certain code when this button is clicked.

The OpenOffice.org event model is similar to the JavaBeans event model. Events in OpenOffice.org are, for example, the creation or activation of a document, as well as the change of the current selection within a view. Applications interested in these events can register handlers (listener interfaces) that are called when the event occurs. Usually these listeners are registered at the object container where the event occurs or to the object itself. These listener interfaces are named X...Listener.

UML diagram showing the relation between listener interfaces and events
Illustration 1.11

Event listeners are subclasses of com.sun.star.lang.XEventListener that receives one event by itself, the deletion of the object to which the listener is registered. On this event, the listener has to unregister from the object, otherwise it would keep it alive with its interface reference counter.

Pay attention to the following important text section

Important! Implement the method disposing() to unregister at the object you are listening to and release all other references to this object.

Many event listeners can handle several events. If the events are generic, usually a single callback method is used. Otherwise, multiple callback methods are used. These methods are called with at least one argument: com.sun.star.lang.EventObject. This argument specifies the source of the event, therefore, making it possible to register a single event listener to multiple objects and still know where an event is coming from. Advanced listeners might get an extended version of this event descriptor struct.

3.3.7    Exception Handling

UNO uses exceptions as a mechanism to propagate errors from the called method to the caller. This error mechanism is preferred instead of error codes (as in MS COM) to allow a better separation of the error handling code from the code logic. Furthermore, Java, C++ and other high-level programming languages provide an exception handling mechanism, so that this can be mapped easily into these languages.

In IDL, an exception is a structured container for data, comparable to IDL structs. Exceptions cannot be passed as a return value or method argument, because the IDL compiler does not allow this. They can be specified in raise clauses and transported in an any. There are two kinds of exceptions, user-defined exceptions and runtime exceptions.

User-Defined Exceptions

The designer of an interface should declare exceptions for every possible error condition that might occur. Different exceptions can be declared for different conditions to distinguish between different error conditions.

The implementation may throw the specified exceptions and exceptions derived from the specified exceptions. The implementation must not throw unspecified exceptions, that is, the implementation must not throw an exception if no exception is specified. This applies to all exceptions except for RuntimeExceptions, described later.

When a user-defined exception is thrown, the object should be left in the state it was in before the call. If this cannot be guaranteed, then the exception specification must describe the state of the object. Note that this is not recommended.

Every UNO IDL exception must be derived from com.sun.star.uno.Exception, whether directly or indirectly. Its UNO IDL specification looks like this:

module com {  module sun {  module star {  module uno {  

exception Exception

{

    string Message;

    com::sun::star::uno::XInterface Context;

};

}; }; }; };

The exception has two members:

The following .IDL file snippet shows a method with a proper exception specification and proper documentation.

module com {  module sun {  module star {  module beans {  

interface XPropertySet: com::sun::star::uno::XInterface

{

    ...

    /** @returns

        the value of the property with the specified name.

                                         

        @param PropertyName      

            This parameter specifies the name of the property.

                         

        @throws UnknownPropertyException        

            if the property does not exist.

                                 

        @throws com::sun::star::uno::lang::WrappedTargetException  

            if the implementation has an internal reason for the

            exception. In this case the original exception

            is wrapped into that WrappedTargetException.

    */

    any getPropertyValue( [in] string PropertyName )

        raises( com::sun::star::beans::UnknownPropertyException,

                com::sun::star::lang::WrappedTargetException );

    ...

};

}; }; }; };

Runtime Exceptions

Throwing a runtime exception signals an exceptional state. Runtime exceptions and exceptions derived from runtime exceptions cannot be specified in the raise clause of interface methods in IDL.

These are a few reasons for throwing a runtime exception are:

Every UNO call may throw a com.sun.star.uno.RuntimeException, except acquire and release. This is independent of how many calls have been completed successfully. Every caller should ensure that its own object is kept in a consistent state even if a call to another object replied with a runtime exception. The caller should also ensure that no resource leaks occur in these cases. For example, allocated memory, file descriptors, etc.

If a runtime exception occurs, the caller does not know if the call has been completed successfully or not. The com.sun.star.uno.RuntimeException is derived from com.sun.star.uno.Exception. Note, that in the Java UNO binding, the com.sun.star.uno.Exception is derived from java.lang.Exception, while the com.sun.star.uno.RuntimeException is directly derived from java.lang.RuntimeException.

A common misuse of the runtime exception is to reuse it for an exception that was forgotten during interface specification. This should be avoided under all circumstances. Consider, defining a new interface.

An exception should not be misused as a new kind of programming flow mechanism. It should always be possible that during a session of a program, no exception is thrown. If this is not the case, the interface design should be reviewed.

Good Exception Handling

This section provides tips on exception handling strategies. Under certain circumstances, the code snippets we call bad below might make sense, but often they do not.

Often, especially in C++ code where you generally do not have a stack trace, the message within the exception is the only method that informs the caller about the reason and origin of the exception. The message is important, especially when the exception comes from a generic interface where all kinds of UNO exceptions can be thrown.

When writing exceptions, put descriptive text into them. To transfer the text to another exception, make sure to copy the text.

Many people write helper functions to simplify recurring coding tasks. However, often code will be written like the following:

// Bad example for exception handling

public static void insertIntoCell( XPropertySet xPropertySet ) {

    [...]

    try {

        xPropertySet.setPropertyValue("CharColor",new Integer(0));

    } catch (Exception e) {

    }

}

This code is ineffective, because the error is hidden. The caller will never know that an error has occurred. This is fine as long as test programs are written or to try out certain aspects of the API (although even test programs should be written correctly). Exceptions must be addressed because the compiler can not perform correctly. In real applications, handle the exception.

The appropriate solution depends on the appropriate handling of exceptions. The following is the minimum each programmer should do:

// During early development phase, this should be at least used instead

public static void insertIntoCell(XPropertySet xPropertySet) {

    [...]

    try {

        xPropertySet.setPropertyValue("CharColor",new Integer(0));

    } catch (Exception e) {

        e.dumpStackTrace();

    }

}

The code above dumps the exception and its stack trace, so that a message about the occurrence of the exception is received on stderr. This is acceptable during development phase, but it is insufficient for deployed code. Your customer does not watch the stderr window.

The level where the error can be handled must be determined. Sometimes, it would be better not to catch the exception locally, but further up the exception chain. The user can then be informed of the error through dialog boxes. Note that you can even specify exceptions on the main() function:

// this is how the final solution could look like

public static void insertIntoCell(XPropertySet xPropertySet) throws UnknownPropertyException,

        PropertyVetoException, IllegalArgumentException, WrappedTargetException {

    [...]

    xPropertySet.setPropertyValue("CharColor",new Integer(0));

}

As a general rule, if you cannot recover from an exception in a helper function, let the caller determine the outcome. Note that you can even throw exceptions at the main() method.

3.3.8    Lifetime of UNO Objects

The UNO component model has a strong impact on the lifetime of UNO objects, in contrast to CORBA, where object lifetime is completely unspecified. UNO uses the same mechanism as Microsoft COM by handling the lifetime of objects by reference counting.

Each UNO runtime environment defines its own specification on lifetime management. While in C++ UNO, each object maintains its own reference count. Java UNO uses the normal Java garbage collector mechanism. The UNO core of each runtime environment needs to ensure that it upholds the semantics of reference counting towards other UNO environments.

The last paragraph of this chapter explains the differences between the lifetime of Java and C++ objects in detail.

acquire() and release()

Every UNO interface is derived from com.sun.star.uno.XInterface:

// module com::sun::star::uno

interface XInterface

{

    any queryInterface( [in] type aType );

    [oneway] void acquire();

    [oneway] void release();

};

UNO objects must maintain an internal reference counter. Calling acquire() on a UNO interface increases the reference count by one. Calling release() on UNO interfaces decreases the reference count by one. If the reference count drops to zero, the UNO object may be destroyed. Destruction of an object is sometimes called death of an object or that the object dies. The reference count of an object must always be non-negative.

Once acquire() is called on the UNO object, there is a reference or a hard reference to the object, as opposed to a weak reference. Calling release() on the object is often called releasing or clearing the reference.

The UNO object does not export the state of the reference count, that is, acquire() and release() do not have return values. Generally, the UNO object should not make any assumptions on the concrete value of the reference count, except for the transition from one to zero.

The invocation of a method is allowed first when acquire () has been called before. For every call to acquire() , there must be a corresponding release call, otherwise the object leaks.

Note graphics marks a special text section

Note: The UNO Java binding encapsulates acquire() and release() in the UnoRuntime.queryInterface() call. The same applies to the Reference<> template in C++. As long as the interface references are obtained through these mechanisms, acquire() and release() do not have to be called in your programs.

The XComponent Interface

A central problem of reference counting systems is cyclic references. Assume Object A keeps a reference on object B and B keeps a direct or indirect reference on object A. Even if all the external references to A and B are released, the objects are not destroyed, which results in a resource leak.

Cyclic reference
Illustration 1.12: Cyclic Reference

Note graphics marks a special text section

In general, a Java developer does not have to be concerned about this kind of issue, as the garbage collector algorithm detects ring references. However, in the UNO world one never knows, whether object A and object B really live in the same Java virtual machine. If they do, the ring reference is really garbage collected. If they do not, the object leaks, because the Java VM is not able to inspect the object outside of the VM for its references.

In UNO, the developer must explicitly decide when to the break cyclic references. To support this concept, the interface com.sun.star.lang.XComponent exists. When an XComponent is disposed of, it can inform other objects that have expressed interest to be notified.

// within the module com::sun::star::lang

// when dispose() is called, previously added XEventListeners are notified

interface XComponent: com::sun::star::uno::XInterface

{

     void dispose();

     void addEventListener( [in] XEventListener xListener );

     void removeEventListener( [in] XEventListener aListener );

};

// An XEventListener is notified by calling its disposing() method

interface XEventListener: com::sun::star::uno::XInterface

{

   void disposing( [in] com::sun::star::lang::EventObject Source );

};

Other objects can add themselves as com.sun.star.lang.XEventListener to an XComponent. When the dispose() method is called, the object notifies all XEventListeners through the disposing() method and releases all interface references, thus breaking the cyclic reference.

Dispose Call
Illustration 1.13: Object C calls dispose() on XComponent of Object B

A disposed object is unable to comply with its specification, so it is necessary to ensure that an object is not disposed of before calling it. UNO uses an owner/user concept for this purpose. Only the owner of an object is allowed to call dispose and there can only be one owner per object. The owner is always free to dispose of the object. The user of an object knows that the object may be disposed of at anytime. The user adds an event listener to discover when an object is being disposed. When the user is notified, the user releases the interface reference to the object. In this case, the user should not call removeEventListener(), because the disposed object releases the reference to the user.

Pay attention to the following important text section

One major problem of the owner/user concept is that there always must be someone who calls dispose(). This must be considered at the design time of the services and interfaces, and be specified explicitly.

This solves the problem described above. However, there are a few conditions which still have to be met.

Break of a cyclic reference
Illustration 1.14: B releases all interface references, which leads to destruction of Object A, which then releases its reference to B, thus the cyclic reference is broken.

If an object is called while it is disposed of, it should behave passively. For instance, if removeListener() is called, the call should be ignored. If methods are called while the object is no longer able to comply with its interface specification, it should throw a com.sun.star.lang.DisposedException, derived from com.sun.star.uno.RuntimeException. This is one of the rare situations in which an implementation should throw a RuntimeException. The situation described above can always occur in a multithreaded environment, even if the caller has added an event listener to avoid calling objects which were disposed of by the owner.

The owner/user concept may not always be appropriate, especially when there is more than one possible owner. In these cases, there should be no owner but only users. In a multithreaded scenario, dispose() might be called several times. The implementation of an object should be able to cope with such a situation.

The XComponent implementation should always notify the disposing() listeners that the object is being destroyed, not only when dispose() is called, but when the object is deleted. When the object is deleted, the reference count of the object drops to zero. This may happen when the listeners do not hold a reference on the broadcaster object.

The XComponent does not have to be implemented when there is only one owner and no further users.

Children of the XEventListener Interface

The com.sun.star.lang.XEventListener interface is the base for all listener interfaces . This means that not only XEventListeners, but every listener must implement disposing(), and every broadcaster object that allows any kind of listener to register, must call disposing() on the listeners as soon as it dies. However, not every broadcaster is forced to implement the XComponent interface with the dispose() method, because it may define its own condition when it is disposed.

In a chain of broadcaster objects where every element is a listener of its predecessor and only the root object is an XComponent that is being disposed, all the other chain links must handle the disposing() call coming from their predecessor and call disposing() on their registered listeners.

Weak Objects and References

A strategy to avoid cyclic references is to use weak references. Having a weak reference to an object means that you can reestablish a hard reference to the object again if the object still exists, and there is another hard reference to it.

In the cyclic reference shown in illustration : , object B could be specified to hold a hard reference on object A, but object A only keeps a weak reference to B. If object A needs to invoke a method on B, it temporarily tries to make the reference hard. If this succeeds, it invokes the method and releases the hard reference afterwards.

To be able to create a weak reference on an object, the object needs to support it explicitly by exporting the com.sun.star.uno.XWeak interface. The illustration : depicts the UNO mechanism for weak references.

When an object is assigned to a weak reference, the weak reference calls queryAdapter() at the original object and adds itself (with the com.sun.star.uno.XReference interface) as reference to the adapter.

UML diagram showing the weak reference mechanism
Illustration 1.15: The UNO weak reference mechanism

When a hard reference is established from the weak reference, it calls the queryAdapted() method at the com.sun.star.uno.XAdapter interface of the adapter object. When the original object is still alive, it gets a reference for it, otherwise a null reference is returned.

The adapter notifies the destruction of the original object to all weak references which breaks the cyclic reference between the adapter and weak reference.

4 Writing UNO Components describes the helper classes in C++ and Java that implement a Xweak interface and a weak reference..

Differences Between the Lifetime of C++ and Java Objects

Note: It is recommended that you read 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding and 3.4.1 Professional UNO - UNO Language Bindings - Java Language Binding for information on language bindings, and 4.6 Writing UNO Components - C++ Component and 4.5.6 Writing UNO Components - Simple Component in Java - Storing the Service Manager for Further Use about component implementation before beginning this section.

The implementation of the reference count specification is different in Java UNO and C++ UNO. In C++ UNO, every object maintains its own reference counter. When you implement a C++ UNO object, instantiate it, acquire it and afterwards release it, the destructor of the object is called immediately. The following example uses the standard helper class ::cppu::OWeakObject and prints a message when the destructor is called. [SOURCE:ProfUNO/Lifetime/object_lifetime.cxx]

class MyOWeakObject : public ::cppu::OWeakObject

{

public:

    MyOWeakObject() { fprintf( stdout, "constructed\n" ); }

    ~MyOWeakObject() { fprintf( stdout, "destroyed\n" ); }

};

The following method creates a new MyOWeakObject, acquires it and releases it for demonstration purposes. The call to release() immediately leads to the destruction of MyOWeakObject. If the Reference<> template is used, you do not need to care about acquire() and release().

void simple_object_creation_and_destruction()

{

    // create the UNO object

    com::sun::star::uno::XInterface * p = new MyOWeakObject();

    // acquire it

    p->acquire();

    // releast it

    fprintf( stdout, "before release\n" );

    p->release();

    fprintf( stdout, "after release\n" );

}

This piece of code produces the following output:

constructed

before release

destroyed

after release

Java UNO objects behave differently, because they are finalized by the garbage collector at a time of its choosing. com.sun.star.uno.XInterface has no methods in the Java UNO language binding, therefore no methods need to be implemented, although MyUnoObject implements XInterface: [SOURCE:ProfUNO/Lifetime/MyUnoObject.java]

class MyUnoObject implements com.sun.star.uno.XInterface {

    public MyUnoObject() {

    }

    void finalize() {

        System.out.println("finalizer called");

    }

    static void main(String args[]) throws java.lang.InterruptedException {

        com.sun.star.uno.XInterface a = new MyUnoObject();

        a = null;

        // ask the garbage collector politely

        System.gc();

        System.runFinalization();

        System.out.println("leaving");

        // It is java VM dependent, whether or not the finalizer was called

    }

}

The output of this code depends on the Java VM implementation. The output “finalizer called” is not a usual result. Be aware of the side effects when UNO brings Java and C++ together.

When a UNO C++ object is mapped to Java, a Java proxy object is created that keeps a hard UNO reference to the C++ object. The UNO core takes care of this. The Java proxy only clears the reference when it enters the finalize() method, thus the destruction of the C++ object is delayed until the Java VM collects some garbage.

When a UNO Java object is mapped to C++, a C++ proxy object is created that keeps a hard UNO reference to the Java object. Internally, the Java UNO bridge keeps a Java reference to the original Java object. When the C++ proxy is no longer used, it is destroyed immediately. The Java UNO bridge is notified and immediately frees the Java reference to the original Java object. When the Java object is finalized is dependent on the garbage collector.

When a Java program is connected to a running OpenOffice.org, the UNO objects in the office process are not destroyed until the garbage collector finalizes the Java proxies or until the interprocess connection is closed (see 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections).

3.3.9    Object Identity

UNO guarantees if two object references are identical, that a check is performed and it always leads to a correct result, whether it be true or false. This is different from CORBA, where a return of false does not necessarily mean that the objects are different.

Every UNO runtime environment defines how this check should be performed. In Java UNO, there is a static areSame() function at the com.sun.star.uno.UnoRuntime class. In C++, the check is performed with the Reference<>::operator == () function that queries both references for XInterface and compares the resulting XInterface pointers.

This has a direct effect in the API design. For instance, look at com.sun.star.lang.XComponent:

interface XComponent: com::sun::star::uno::XInterface

{

    void dispose();

    void addEventListener( [in] XEventListener xListener );

    void removeEventListener( [in] XEventListener aListener );

};

The method removeEventListener() that takes a listener reference, is logical if the implementation can check for object identity, otherwise it could not identify the listener that has to be removed. CORBA interfaces are not designed in this manner. They need an object ID, because object identity is not guaranteed.

3.4    UNO Language Bindings

This chapter documents the mapping of UNO to various programming languages or component models. These language bindings are sometimes called UNO Runtime Environment (URE). Each URE needs to fulfill the specifications given in the earlier chapters. The use of UNO services and interfaces are also explained in this chapter. Refer to 4 Writing UNO Components for information about the implementation of UNO objects.

Each chapter provides detail information for the following topics:

Other programming language specific material (like core libraries in C++ UNO).

C++, Java, OpenOffice.org Basic and all languages supporting MS OLE automation on the win32 platform are currently supported. In future, the UNO component model may extend the number of supported language bindings.

3.4.1    Java Language Binding

The Java language binding gives developers the choice of using Java or UNO components for client programs. A Java program can access components written in other languages and built with a different compiler, as well as remote objects, because of the seamless interaction of UNO bridges.

Java delivers a rich set of classes that can be used within client programs or component implementations. However, when it comes to interaction with other UNO objects, use UNO interfaces, because only those are known to the bridge and can be mapped into other environments.

To control the office from a client program, the client needs a Java 1.3 installation, a free socket port, and the following jar files jurt.jar, jut.jar, javaunohelper.jar, ridl.jar, classes.jar and sandbox.jar . A Java installation on the server-side is not necessary. A step-by-step description is given in the chapter 2 First Steps

When using Java components, the office is installed with Java support. Also make sure that Java is enabled: there is a switch that can be set to achieve this in the Tools - Options - OpenOffice.org - Security dialog. All necessary jar files should have been installed during the OpenOffice.org setup. A detailed explanation can be found in the chapter 4.5.6 Writing UNO Components - Simple Component in Java - Storing the Service Manager for Further Use.

The Java UNO Runtime is documented in the Java UNO Reference which can be found in the OpenOffice.org Software development Kit (SDK) or on udk.openoffice.org.

Getting a Service Manager

Office objects that provide the desired functionality are required when writing a client application that accesses the office. The root of all these objects is the service manager component, therefore clients need to instantiate it. Service manager runs in the office process, therefore office must be running first when you use Java components that are instantiated by the office. In a client-server scenario, the office has to be launched directly. The interprocess communication uses a remote protocol that can be provided as a command-line argument to the office:

soffice -accept=socket,host=localhost,port=8100;urp;

The client obtains a reference to the global service manager of the office (the server) using a local com.sun.star.bridge.UnoUrlResolver. The global service manager of the office is used to get objects from the other side of the bridge. In this case, an instance of the com.sun.star.frame.Desktop is acquired:

import com.sun.star.uno.XComponentContext;

import com.sun.star.comp.helper.Bootstrap;

import com.sun.star.lang.XMultiComponentFactory;

import com.sun.star.bridge.XUnoUrlResolver;

import com.sun.star.beans.XPropertySet

import com.sun.star.uno.UnoRuntime;

XComponentContext xcomponentcontext = Bootstrap.createInitialComponentContext(null);

// initial serviceManager

XMultiComponentFactory xLocalServiceManager = xcomponentcontext.getServiceManager();

// create a connector, so that it can contact the office

Object xUrlResolver = xLocalServiceManager.createInstanceWithContext(

    "com.sun.star.bridge.UnoUrlResolver", xcomponentcontext);

       

XUnoUrlResolver urlResolver = (XUnoUrlResolver) UnoRuntime.queryInterface(

    XUnoUrlResolver.class, xUrlResolver);

Object initialObject = urlResolver.resolve(

    "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager");

       

XMultiComponentFactory xOfficeFactory = (XMultiComponentFactory) UnoRuntime.queryInterface(

    XMultiComponentFactory.class, initialObject);

// retrieve the component context as property (it is not yet exported from the office)

// Query for the XPropertySet interface.

XPropertySet xProperySet = (XPropertySet) UnoRuntime.queryInterface(

    XPropertySet.class, xOfficeFactory);

// Get the default context from the office server.

Object oDefaultContext = xProperySet.getPropertyValue("DefaultContext");

// Query for the interface XComponentContext.

XComponentContext xOfficeComponentContext = (XComponentContext) UnoRuntime.queryInterface(

    XComponentContext.class, oDefaultContext);

// now create the desktop service

// NOTE: use the office component context here!

Object oDesktop = xOfficeFactory.createInstanceWithContext(

                “com.sun.star.frame.Desktop", xOfficeComponentContext);

As the example shows, a local service manager is created through the com.sun.star.comp.helper.Bootstrap class (a Java UNO runtime class). Its implementation provides a service manager that is limited in the number of services it can create. The names of these services are:

com.sun.star.lang.ServiceManager
com.sun.star.lang.MultiServiceFactory
com.sun.star.loader.Java
com.sun.star.loader.Java2
com.sun.star.bridge.UnoUrlResolver
com.sun.star.bridge.BridgeFactory
com.sun.star.connection.Connector
com.sun.star.connection.Acceptor

They are sufficient to establish a remote connection and obtain the fully featured service manager provided by the office.

Tip graphics marks a hint section in the text

The local service manager could create other components, but this is only possible if the service manager is provided with the respective factories during runtime. An example that shows how this works can be found in the implementation of the Bootstrap class in the project javaunohelper.
There is also a service manager that uses a registry database to locate services. It is implemented by the class com.sun.star.comp.helper.RegistryServiceFactory in the project javaunohelper. However, the implementation uses a native registry service manager instead of providing a pure Java implementation.

Handling Interfaces

The service manager is created in the server process and the Java UNO remote bridge ensures that its XInterface is transported back to the client. A Java proxy object is constructed that can be used by the client code. This object is called the initial object , because it is the first object created by the bridge. When another object is obtained through this object, then the bridge creates a new proxy. For instance, if a function is called that returns an interface. That is, the original object is actually running in the server process (the office) and calls to the proxy are forwarded by the bridge. Not only interfaces are converted, but function arguments, return values and exceptions.

The Java bridge maps objects on a per-interface basis, that is, in the first step only the interface is converted that is returned by a function described in the API reference. For example, if you have  the service manager and use it to create another component, you initially get a com.sun.star.uno.XInterface:

XInterface xint= (XInterface) serviceManager.createInstance(“com.sun.star.bridge.OleObjectFactory”);

You know from the service description that the OleObjectFactory implements a com.sun.star.lang.XMultiServiceFactory interface. However, you cannot cast the object or call the interface function on the object, since the object is only a proxy for just one interface, XInterface. Therefore, you have to use a mechanism that is provided with the Java bridge that generates proxy objects on demand. For example:

XMultiServiceFactory xfac = (XMultiServiceFactory) UnoRuntime.queryInterface(

    XMultiServiceFactory.class, xint);

If xint is a proxy, then queryInterface() hands out another proxy for XMultiServiceFactory provided that the original object implements it. Interface proxies can be used as arguments in function calls on other proxy objects. For example:

// client side

// obj is a proxy interface and returns another interface through its func() method

XSomething ret = obj.func();

// anotherObject is a proxy interface, too. Its method func(XSomething arg)

// takes the interface ret obtained from obj

anotherObject.func(ret);

In the server process, the obj object would receive the original ret object as a function argument.

It is also possible to have Java components on the client side. As well, they can be used as function arguments, then the bridge would set up proxies for them in the server process.

Not all language elements of UNO IDL have a corresponding language element in Java. For example, there are no structs and all-purpose out parameters. Refer to 3.4.1 Professional UNO - UNO Language Bindings - Java Language Binding - Type Mappings for how those elements are mapped.

Interface handling normally involves the ability of com.sun.star.uno.XInterface to acquire and release objects by reference counting. In Java, the programmer does not bother with acquire() and release(), since the Java UNO runtime automatically acquires objects on the server side when com.sun.star.uno.UnoRuntime.queryInterface() is used. Conversely, when the Java garbage collector deletes your references, the Java UNO runtime releases the corresponding office objects. If a UNO object is written in Java, no reference counting is used to control its lifetime. The garbage collector takes that responsibility.

Sometimes it is necessary to find out if two interfaces belong to the same object. In Java, you would compare the references with the equality operator '=='. This works as long as the interfaces refer to a local Java object. Often the interfaces are proxies and the real objects reside in a remote process. There can be several proxies that belong to the same object, because objects are bridged on a per-interface basis. Those proxies are Java objects and comparing their references would not establish them as parts of the same object. To determine if interfaces are part of the same object, use the method areSame() at the com.sun.star.uno.UnoRuntime class:

static public boolean areSame(Object object1, Object object2)

Type Mappings

Mapping of Simple Types

The following table shows the mapping of IDL basic types to the corresponding Java types.

Users should be careful when using unsigned types in Java, since there is no support for unsigned types in the Java language. A user is responsible for the conversion of large unsigned IDL type values as signed values in Java.

IDL

Java

boolean

boolean

short

short

unsigned short

short

long

int

usigned long

int

hyper

long

unsigned hyper

long

float

float

double

double

char

char

byte

byte

string

java.lang.String

any

java.lang.Object/com.sun.star.uno.Any

type

com.sun.star.uno.Type

void

void

Mapping of Any

There is a dedicated com.sun.star.uno.Any type, but it is not always used. An any in the API reference is represented by a java.lang.Object in Java UNO. An Object reference can be used to refer to all possible Java objects. This does not work with primitive types, but if you need to use them as an any, there are Java wrapper classes available that allow primitive types to be used as objects. Also, a Java Object always brings along its type information by means of an instance of java.lang.Class. Therefore a variable declared as :

Object ref;

can be used with all objects and its type information is available by calling:

ref.getClass();

Those qualities of Object are sufficient to replace the Any in most cases. Even Java interfaces generated from IDL interfaces do not contain Anys, instead Object references are used in place of Anys. Cases where an explicit Any is needed to not loose information contain unsigned integral types, all interface types except the basic XInterface, and the void type.

Pay attention to the following important text section

However, implementations of those interfaces must be able to deal with real Anys that can also be passed by means of Object references.

To facilitate the handling of the Any type, use the com.sun.star.uno.AnyConverter class. It is documented in the Java UNO reference. The following list sums up its methods:

static boolean isArray(java.lang.Object object)

static boolean isBoolean(java.lang.Object object)

static boolean isByte(java.lang.Object object)

static boolean isChar(java.lang.Object object)

static boolean isDouble(java.lang.Object object)

static boolean isFloat(java.lang.Object object)

static boolean isInt(java.lang.Object object)

static boolean isLong(java.lang.Object object)

static boolean isObject(java.lang.Object object)

static boolean isShort(java.lang.Object object)

static boolean isString(java.lang.Object object)

static boolean isType(java.lang.Object object)

static boolean isVoid(java.lang.Object object)

static java.lang.Object toArray(java.lang.Object object)

static boolean toBoolean(java.lang.Object object)

static byte toByte(java.lang.Object object)

static char toChar(java.lang.Object object)

static double toDouble(java.lang.Object object)

static float toFloat(java.lang.Object object)

static int toInt(java.lang.Object object)

static long toLong(java.lang.Object object)

static java.lang.Object toObject(Type type, java.lang.Object object)

static short toShort(java.lang.Object object)

static java.lang.String toString(java.lang.Object object)

static Type toType(java.lang.Object object)

The Java com.sun.star.uno.Any is needed in situations when the type needs to be specified explicitly. Assume there is a C++ component with an interface function which is declared in UNO IDL as:

//UNO IDL

void foo(any arg);

The corresponding C++ implementation could be:

void foo(const Any& arg)

{

    const Type& t = any.getValueType();

    if (t == getCppuType((const Reference<XReference>*) 0))

    {

        Reference<XReference> myref = *reinterpret_cast<const Reference<XReference>*>(any.getValue());

        ...

    }

}

In the example, the any is checked if it contains the expected interface. If is does, it is assigned  accordingly. If the any contained a different interface, a query would be performed for the expected interface. If the function is called from Java, then an interface has to be supplied that is an object. That object could implement several interfaces and the bridge would use the basic XInterface. If this is not the interface that is expected, then the C++ implementation has to call queryInterface to obtain the desired interface. In a remote scenario, those queryInterface() calls could lead to a noticeable performance loss. If you use a Java Any as a parameter for foo(), the intended interface is sent across the bridge.

Preserving UNO Type Information for Complex Types

In C++ UNO, all necessary type information is described by the type Type. In Java, type information is mapped to the Java type Class, but some information described in IDL is lost. The Java mapping for the complex types (interface, struct, exception) creates an additional public static final member array of type com.sun.star.lib.uno.typelib.TypeInfo named UNOTYPEINFO to describe this information. This array can be filled with objects of the following types:

Note Only these definitions are maintained in UNOTYPEINFO. This additional type information and the information from Class is used by the Java UNO runtime to handle the type during transport over a remote connection or conversion to another object model.

All generated types (interface, struct, enum, exception) have another public static member of type Object UNORUNTIMEDATA. This member is reserved for internal use by the UNO runtime.

Mapping of Sequence

Sequence types are mapped to a Java array of the Java type that corresponds to the element types of the original IDL sequence.

Mapping of Module

An IDL module is mapped to a Java package with the same name. All IDL type declarations within the module are mapped to corresponding Java class or interface declarations within the generated package. IDL declarations not enclosed in any modules are mapped into the Java global scope.

Example:

An IDL module org {...} is mapped to package org; ...

Mapping of Interface

An IDL interface is mapped to a Java interface with the same name as the IDL interface type. If an IDL interface inherits another interface, the Java interface extends the appropriate Java interface.

Mapping of Method Parameters

In Java there are special conditions concerning the value null for parameters and return values, and concerning out and inout parameters. It is common for Java that arguments or return values which are objects can be null. Since UNO interfaces, sequences, structs and strings are mapped to Java objects (sequence is mapped to an array which is a special kind of object), the respective method arguments or return values could be null. But UNO allows only interface values to be passed as null values. If a UNO interface function has parameters, in, inout or out parameters, or a return value of type sequence, struct or string, then the respective values of the Java method must not be null. The example below uses a struct FooStruct in an interface XFoo to show how to use empty parameters and return values, and how to use out and inout parameters.

//UNO IDL

struct FooStruct

{

    long nval;

    string strval;

};

interface XFoo: com.sun.star.uno.XInterface

{

    string funcOne( [in] string value);

    FooStruct funcTwo( [inout] FooStruct value);

    sequence<byte> funcThree([out] sequence <byte> value);

};

IDL in parameters that call-by-value semantics are mapped to normal Java actual parameters. The result of IDL operations is returned as the result of the corresponding Java method. IDL out and inout parameters that implement call-by-reference semantics are mapped to arrays of the appropriate types. The type is determined according to the mappings defined in this document. The arrays contain one element, that is, the length of the array is 1. Therefore, the Java interface for the IDL interface XFoo would look:

// Java

public interface XFoo extends com.sun.star.uno.XInterface {

    public String funcOne(String value);

    public FooStruct funcTwo(FooStruct[] value);

    public byte[] funcThree(byte[][] value);

    ...

}

This is how FooStruct would be mapped to Java:

// Java

public class FooStruct {

    public int nval;

    public String strval;

    // default constructor

    public FooStruct() {

      strval=””;

    }

    public FooStruct(int _nval, String _strval) {

        nval = _nval;

        strval = _strval;

    }

    // extra type information

    ...

}

When providing a value as an inout parameter, the caller has to write the input value into the element at index 0 of the array. When the function returns, the value at index 0 reflects the output value, which may be a new value, modified input value, or unmodified input value. The object obj implements XFoo:

// calling the interface in Java

obj.funcOne(null);    // error

obj.funcOne(“”);      // OK

FooStruct[] inoutstruct= new FooStruct[1];

obj.funcTwo(inoutstruct);          //error, inoutstruct[0] = null

inoutstruct[0]= new FooStruct();   // now we initialise inoutstruct[0]

obj.funcTwo(inoutstruct);          // inoutstruct[0] is valid now

When a method receives an argument that is an out parameter, it has to provide a value that has to be put at index null of the array.

// method implementations of interface XFoo

public String funcOne(String value) {

  // param value always != null otherwise it is a bug of the caller!

  return null; //error

  // instead

  // return “”;

}

public FooStruct funcTwo(FooStruct[] value) {

  value[0] = null; //error

  // instead

  // value[0] = new FooStruct();

  return null; // error

  // instead

  // return new FooStruct();

}

public byte[] funcThree(byte[][] value) {

  value[0]= null; //error

  // instead

  // value[0]= new byte[0];

  return null; //error

  // instead

  // return new byte[0];

}

Exceptions specified in UNO IDL are mapped to normal Java throws statements. Any method may throw a com.sun.star.uno.RuntimeException, therefore a RuntimeException never has to be specified explicitly in UNO IDL.

module com {  module sun {  module star {  module registry {  

interface XImplementationRegistration: com::sun::star::uno::XInterface

{

    void registerImplementation(

            [in] string aImplementationLoader,

                [in] string aLocation,

                [in] com::sun::star::registry::XSimpleRegistry xReg )

            raises( com::sun::star::registry::CannotRegisterImplementationException );

    boolean revokeImplementation(

        [in] string aLocation,

        [in] com::sun::star::registry::XSimpleRegistry xReg );

    sequence getImplementations(

        [in] string aImplementationLoader,

        [in] string aLocation );

    sequence checkInstantiation( [in] string implementationName );

};

is mapped to:

package com.sun.star.registry;

public interface XImplementationRegistration extends com.sun.star.uno.XInterface {

    // Methods

    public void registerImplementation(/*IN*/String aImplementationLoader,

            /*IN*/String aLocation, /*IN*/XSimpleRegistry xReg)

        throws CannotRegisterImplementationException, com.sun.star.uno.RuntimeException;

    public boolean revokeImplementation(/*IN*/String aLocation, /*IN*/XSimpleRegistry xReg)

        throws com.sun.star.uno.RuntimeException;

    public String[] getImplementations(/*IN*/String aImplementationLoader, /*IN*/String aLocation)

        throws com.sun.star.uno.RuntimeException;

    public String[] checkInstantiation(/*IN*/String implementationName)

        throws com.sun.star.uno.RuntimeException;

    // static Member

    public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = {

        new com.sun.star.lib.uno.typeinfo.MethodTypeInfo("registerImplementation", 0, 0),

        new com.sun.star.lib.uno.typeinfo.ParameterTypeInfo("xReg", "registerImplementation", 2,

            com.sun.star.lib.uno.typeinfo.TypeInfo.INTERFACE),

        new com.sun.star.lib.uno.typeinfo.MethodTypeInfo("revokeImplementation", 1, 0),

        new com.sun.star.lib.uno.typeinfo.ParameterTypeInfo("xReg", "revokeImplementation", 1,

            com.sun.star.lib.uno.typeinfo.TypeInfo.INTERFACE),

        new com.sun.star.lib.uno.typeinfo.MethodTypeInfo("getImplementations", 2, 0),

        new com.sun.star.lib.uno.typeinfo.MethodTypeInfo("checkInstantiation", 3, 0)

     };

    public static Object UNORUNTIMEDATA = null;

}

Mapping of Structs

An IDL struct is mapped to a Java class with the same name as the struct type. Each member of the IDL struct is mapped to a public instance variable with the same type and name. The class also provides a default constructor which initializes all members with default values, and a constructor which takes values for all struct members. If a struct inherits from another struct, the generated class extends the class of the inherited struct. The default constructor only initializes the complex type members. The member constructor has all fields of the extended class and its own fields as parameters.

module com {  module sun {  module star {  module chart {  

 

struct ChartDataChangeEvent: com::sun::star::lang::EventObject

{

    com::sun::star::chart::ChartDataChangeType Type;

    short StartColumn;

    short EndColumn;

    short StartRow;

    short EndRow;

};

 

 

}; }; }; };  

is mapped to:

package com.sun.star.chart;

public class ChartDataChangeEvent extends com.sun.star.lang.EventObject {

    //instance variables

    public ChartDataChangeType Type;

    public short StartColumn;

    public short EndColumn;

    public short StartRow;

    public short EndRow;

    //constructors

    public ChartDataChangeEvent() {

        Type = com.sun.star.chart.ChartDataChangeType.getDefault();

    }

    public ChartDataChangeEvent(java.lang.Object _Source, ChartDataChangeType _Type,

            short _StartColumn, short _EndColumn, short _StartRow, short _EndRow ) {

        super( _Source );

        Type = _Type;

        StartColumn = _StartColumn;

        EndColumn = _EndColumn;

        StartRow = _StartRow;

        EndRow = _EndRow;

    }

    public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = {

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("Type", 0, 0),

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("StartColumn", 1, 0),

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("EndColumn", 2, 0),

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("StartRow", 3, 0),

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("EndRow", 4, 0)

        };

}

Mapping of Exceptions

An IDL exception is mapped to a Java class with the same name as the exception type.

There are two UNO exceptions that are the base for all other exceptions. These are the com.sun.star.uno.Exception and com.sun.star.uno.RuntimeException that are inherited by all other exceptions. The corresponding exceptions in Java inherit from Java exceptions:

//UNO IDL

module com {  module sun {  module star {  module uno {  

exception Exception

{

    string Message;

    com::sun::star::uno::XInterface Context;

};

}; }; }; };  

module com { module sun { module star { module uno {

exception RuntimeException

{

    string Message;

    com::sun::star::uno::XInterface Context;

};

}; }; }; };

The com.sun.star.uno.Exception in Java:

package com.sun.star.uno;

public class Exception extends java.lang.Exception {

    // instance variables

    public java.lang.Object Context;

    // constructors

    public Exception() {

    }

    public Exception(String _Message) {

        super (_Message);

    }

    public Exception(String _Message, java.lang.Object _Context) {

        super (_Message);

        Context = _Context;

    }

    public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = {

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("Context", 0,

            com.sun.star.lib.uno.typeinfo.TypeInfo.INTERFACE)

        };

}

The com.sun.star.uno.RuntimeException in Java:

package com.sun.star.uno;

public class RuntimeException extends java.lang.RuntimeException {

    // instance variables

    public java.lang.Object Context;

    // constructors

    public RuntimeException() {

    }

    public RuntimeException(String _Message) {

        super (_Message);

    }

    public RuntimeException(String _Message, java.lang.Object _Context) {

        super( _Message );

        Context = _Context;

    }

    public static final com.sun.star.lib.uno.typeinfo.TypeInfo UNOTYPEINFO[] = {

        new com.sun.star.lib.uno.typeinfo.MemberTypeInfo("Context", 0,

            com.sun.star.lib.uno.typeinfo.TypeInfo.INTERFACE)

        };

}

As shown, the Message member has no direct counterpart in the respective Java class. Instead, the constructor argument _Message is used to initialize the base class which is a Java exception. The message is accessible through the inherited getMessage() method. All other members of the IDL exceptions are mapped to public instance variables with the same type and name. A generated Java exception class always has a default constructor that initializes all members with default values, and a constructor which takes values for all instance variables.

If an exception inherits from another exception, the generated class extends the class of the inherited exception, and the constructor takes the arguments for all fields of the class and the base classes.

Mapping of Enums and Constants

An IDL enum is mapped to a Java final class with the same name as the enum type, derived from the class com.sun.star.uno.Enum. This base class declares a protected member to store the actual value, a protected constructor to initialize the value and a public getValue() method to get the actual value. The generated final class has a protected constructor and a public method getDefault() that returns an enum with the value of the first enum label as a default. For each IDL enum label, the class declares a public static member of the same type as the enum and is initialized with the defined value in IDL. The Java class for the enum has an additional public method fromInt() that which returns the enum with the specified value. The following IDL definition for com.sun.star.uno.TypeClass:

module com { module sun { star { module uno {

    enum TypeClass

    {

        INTERFACE,

        SERVICE,

        IMPLEMENTATION,

        STRUCT,

        TYPEDEF,

        ...

    };

}; }; }; };

is mapped to:

package com.sun.star.uno;

final public class TypeClass extends com.sun.star.uno.Enum {

    private TypeClass(int value) {

        super (value);

    }

    public static TypeClass getDefault() {

        return INTERFACE;

    }

    public static final TypeClass INTERFACE = new TypeClass(0);

    public static final TypeClass SERVICE = new TypeClass(1);

    public static final TypeClass IMPLEMENTATION = new TypeClass(2);

    public static final TypeClass STRUCT = new TypeClass(3);

    public static final TypeClass TYPEDEF = new TypeClass(4);

    ...

    public static TypeClass fromInt(int value) {

        switch (value) {

          case 0:

              return INTERFACE;

          case 1:

              return SERVICE;

          case 2:

              return IMPLEMENTATION;

          case 3:

              return STRUCT;

          case 4:

              return TYPEDEF;

          ...

        }

    }

    public static Object UNORUNTIMEDATA = null;

}

An IDL const named USERFLAG:

module example {

    const long USERFLAG = 1;

};

is mapped to:

package example;

public interface USERFLAG {

    public static final int value = (int)1L;

}

IDL constants groups are mapped to a public interface with the same name as the constants group. All const defined in this constant group are mapped to public static members of the interface with type and name of the const that holds the value.

An IDL constants group User containing three const values FLAG1, FLAG2 and FLAG3:

module example {

    constants User

    {

        const long FLAG1 = 1;

        const long FLAG2 = 2;

        const long FLAG3 = 3;

    };

};

is mapped to:

package example;

public interface User {

    public static final int FLAG1 = (int)1L;

    public static final int FLAG2 = (int)2L;

    public static final int FLAG3 = (int)3L;

}

3.4.2    UNO C++ Binding

This chapter describes the UNO C++ language binding. It provides an experienced C++ programmer the first steps in UNO to establish UNO interprocess connections to a remote OpenOffice.org and to use its UNO objects.

Library Overview

gives an overview about the core libraries of the UNO component model.

Overview about the base shared libraries
Illustration 1.16: Shared Libraries for C++ UNO

These shared libraries can be found in the <officedir>/program folder of your OpenOffice.org installation. The label (c) in the illustration above means C-linkage and (C++) means C++ linkage. For all libraries, a C++ compiler to build is required.

The basis for all UNO libraries is the sal library. It contains the system abstraction laye r (sal) and additional runtime library functionality, but does not contain any UNO-specific information. The commonly used C-functions of the sal library can be accessed through C++ inline wrapper classes. This allows functions to be called from any other programming language, because most programming languages have some mechanism to call a C function.

The salhelper library is a small C++ library which offers additional runtime library functionality, that could not be implemented inline.

The cppu (C++ UNO) library is the core UNO library. It offers methods to access the UNO type library, and allows the creation, copying and comparing values of UNO data types in a generic manner. Moreover, all UNO bridges (= mappings + environments) are administered in this library.

The examples msci_uno.dll, libsunpro5_uno.so and libgcc2_uno.so are only examples for language binding libraries for certain C++ compilers.

The cppuhelper library is a C++ library that contains important base classes for UNO objects and functions to bootstrap the UNO core. C++ Components and UNO programs have to link the cppuhelper library.

All the libraries shown above will be kept compatible in all future releases of UNO. You will be able to build and link your application and component once, and run it with the current and  later versions of OpenOffice.org.

System Abstraction Layer

C++ UNO client programs and C++ UNO components use the system abstraction layer (sal) for types, files, threads, interprocess communication, and string handling. The sal library offers operating system dependent functionality as C-functions. The aim is to minimize or to eliminate operating system dependent #ifdef in libraries above sal. Sal offers high performance access because sal is a thin layer above the API offered by each operating system.

Note graphics marks a special text section

In OpenOffice.org GUI APIs are encapsulated in the vcl library.

Sal exports only C-symbols. The inline C++ wrapper exists for convenience. Refer to the UNO C++ reference that is part of the OpenOffice.org SDK or in the References section of udk.openoffice.org to gain a full overview of the features provided by the sal library. In the following sections, the C++ wrapper classes will be discussed. The sal types used for UNO IDL types are discussed in section 3.4.2 Professional UNO - UNO Language Bindings - UNO C++ Binding - Type Mappings. If you want to use them, look up the names of the appropriate include files in the C++ reference.

File Access

The classes listed below manage platform independent file access. They are C++ classes that call corresponding C functions internally.

An unfamiliar concept is the use of absolute filenames throughout the whole API. In a multithreaded program, the current working directory cannot be relied on, thus relative paths must be explicitly made absolute by the caller.

Threadsafe Reference Counting

The functions osl_incrementInterlockedCount() and osl_decrementInterlockedCount() in the global C++ namespace increase and decrease a 4-byte counter in a threadsafe manner. This is needed for reference counted objects. Many UNO APIs control object lifetime through refcounting. Since concurrent incrementing the same counter does not increase the reference count reliably, these functions should be used. This is faster than using a mutex on most platforms.

Threads and Thread Synchronization

The class osl::Thread is meant to be used as a base class for your own threads. Overwrite the run() method.

The following classes are commonly used synchronization primitives:

 osl::Mutex

Socket and Pipe

The following classes allow you to use interprocess communication in a platform independent manner:

Strings

The classes rtl::OString (8-bit, encoded) and rtl::OUString (16-bit, UTF16) are the base-string classes for UNO programs. The strings store their data in a heap memory block. The string is refcounted and incapable of changing, thus it makes copying faster and creation is an expensive operation. An OUString can be created using the static function OUString::createFromASCII() or it can be constructed from an 8-bit string with encoding using this constructor:

OUString( const sal_Char * value,

        sal_Int32 length,

        rtl_TextEncoding encoding,

        sal_uInt32 convertFlags = OSTRING_TO_OUSTRING_CVTFLAGS );

It can be converted into an 8-bit string, for example, for printf() using the rtl::OUStringToOString() function that takes an encoding, such as RTL_TEXTENCODING_ASCII_US).

For fast string concatenation, the classes rtl::OStringBuffer and rtl::OUStringBuffer should be used, because they offer methods to concatenate strings and numbers. After preparing a new string buffer, use the makeStringAndClear() method to create the new OUString or OString. The following example illustrates this :

    sal_Int32 n = 42;

    double pi = 3.14159;

    // create a buffer with a suitable size, rough guess is sufficient

    // stringbuffer extends if necessary

    OUStringBuffer buf( 128 );

    // append an ascii string

    buf.appendAscii( "pi ( here " );

    // numbers can be simply appended

    buf.append( pi );

    // RTL_CONSTASCII_STRINGPARAM()

    // lets the compiler count the stringlength, so this is more efficient than

    // the above appendAscii call, where the length of the string must be calculated at

    // runtime

    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" ) multiplied with " ) );

    buf.append( n );

    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" gives ") );

    buf.append( (double)( n * pi ) );

    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM( "." ) );

    // now transfer the buffer into the string.

    // afterwards buffer is empty and may be reused again !

    OUString string = buf.makeStringAndClear();

    // You could of course use the OStringBuffer directly to get an OString

    OString oString = rtl::OUStringToOString( string , RTL_TEXTENCODING_ASCII_US );

    // just to print something

    printf( "%s\n" ,oString.getStr() );

Establishing Interprocess Connections

Any language binding supported by UNO establishes interprocess connections using a local service manager to create the services necessary to connect to the office. Refer to chapter 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections for additional information. The following client program connects to a running office and retrieves the com.sun.star.lang.XMultiServiceFactory in C++: (ProfUNO/CppBinding/office_connect.cxx)

#include <stdio.h>

#include <cppuhelper/bootstrap.hxx>

#include <com/sun/star/bridge/XUnoUrlResolver.hpp>

#include <com/sun/star/lang/XMultiServiceFactory.hpp>

using namespace com::sun::star::uno;

using namespace com::sun::star::lang;

using namespace com::sun::star::bridge;

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 =

                rServiceManager->createInstanceWithContext(

            OUString::createFromAscii("com.sun.star.bridge.UnoUrlResolver" ),

            rComponentContext );

       

    // Query for the XUnoUrlResolver interface

    Reference< XUnoUrlResolver > rResolver( rInstance, UNO_QUERY );

    if( ! rResolver.is() )

    {

                printf( "Error: Couldn't instantiate com.sun.star.bridge.UnoUrlResolver service\n" );

                return 1;

    }

    try

    {

                // resolve the uno-URL

                rInstance = rResolver->resolve( OUString::createFromAscii(

            "uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" ) );

       

        if( ! rInstance.is() )

        {

            printf( "StarOffice.ServiceManager is not exported from remote process\n" );

            return 1;

        }

                // query for the simpler XMultiServiceFactory interface, sufficient for scripting

                Reference< XMultiServiceFactory > rOfficeServiceManager (rInstance, UNO_QUERY);

                if( ! rOfficeServiceManager.is() )

        {

            printf( "XMultiServiceFactory interface is not exported\n" );

            return 1;

        }

        printf( "Connected sucessfully to the office\n" );

    }       

    catch( Exception &e )

    {

        OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

        printf( "Error: %s\n", o.pData->buffer );

        return 1;

    }

    return 0;

}

Type Mappings

Mapping of Simple Types

The following table provides a summary of the mappings from IDL types to C++ UNO types.

IDL type

Size [byte]

C++ type

Description

void

-

void

void

byte

1

sal_Int8

Signed 8-bit integer

short

2

sal_Int16

Signed 16-bit integer

unsigned short

2

sal_uInt16

Unsigned 16-bit integer

signed long

4

sal_Int32

Signed 32-bit integer

unsigned long

4

sal_uInt32

Unsigned 32-bit integer

hyper

8

sal_Int64

Signed 64-bit integer

unsigned hyper

8

sal_uInt64

Unsigned 64-bit integer

float

sizeof (float)

float

processor dependent: Intel, Sparc = IEEE float

double

sizeof (double)

double

processor dependent: Intel, Sparc = IEEE double

boolean

1

sal_Bool { 0, 1 }

8-bit unsigned char

char

2

sal_Unicode

16-bit unicode char

string

4

rtl::OUString

Unicode string

type

4

com::sun::star::uno::Type

Type descriptor

The basic integer types are all mapped to sal_x types, where x describes the bit length and sign of the simple type. The sal prefix is used to avoid name clashes with other libraries or applications.

A string is mapped to an rtl::OUString that is a reference counted, non-changing UTF-16 string. There are no 8-bit strings in UNO.

Mapping of Any

IDL type

Size [byte]

C++ type

Description

any

sizeof (uno_Any)

com::sun::star::uno::Any

universal type

The IDL any is mapped to com::sun::star::uno::Any. It holds an instance of an arbitrary UNO type. Only UNO types can be stored within the any, because the data from the type library are required for any handling.

A default constructed Any contains the void type and no value. You can assign a value to the Any using the operator <<= and retrieve a value using the operator >>=.

// default construct an any

Any any;

sal_Int32 n = 3;

// Store the value into the any

any <<= n;

// extract the value again

sal_Int32 n2;

any >>= n2;

assert( n2 == n );

assert( 3 == n2 );

The extraction operator >>= carries out widening conversions when no loss of data can occur, but data cannot be directed downward. If the extraction was successful, the operator returns sal_True, otherwise sal_False.

Any any;

sal_Int16 n = 3;

any <<= n;

sal_Int8 aByte = 0;

sal_Int16 aShort = 0;
sal_Int32 aLong = 0;

// this will succeed, conversion from int16 to int32 is OK.

assert( any >>= aLong );

assert( 3 == aLong );

// this will succeed, conversion from int16 to int16 is OK

assert( any >>= aShort );

assert( 3 == aShort

// the following two assertions will FAIL, because conversion

// from int16 to int8 may involve loss of data..

// Even if a downcast is possible for a certain value, the operator refuses to work

assert( any >>= aByte );

assert( 3 == aByte );

Instead of using the operator for extracting, you can also get a pointer to the data within the Any. This may be faster, but it is more complicated to use. With the pointer, care has to be used during  casting and proper type handling, and the lifetime of the Any must exceed the pointer usage.

Any a = ...;

if( a.getTypeClass() == TypeClass_LONG && 3 ==  *(sal_Int32 *)a.getValue() )

{

}

You can also construct an Any from a pointer to a C++ UNO type that can be useful. For instance:

Any foo()

{

    sal_Int32 i = 3;

    if( ... )

        i = ..;

    return Any( &i, getCppuType( &i) );

}

Mapping of Interface

IDL type

Size [byte]

C++ type

Description

Interface

4

com::sun::star::uno::Reference< interfacetype >

Pointer to a refcounted interface

An IDL interface reference is mapped to the template class:

template< class t >

com::sun::star::uno::Reference< t >

The template is used to get a type safe interface reference, because only a correctly typed interface pointer can be assigned to the reference. The example below assigns an instance of the desktop service to the rDesktop reference:

// the xSMgr reference gets constructed somehow

{

    ...

    // construct a deskop object and acquire it

    Reference< XInterface > rDesktop = xSMgr->createInstance(

    OUString::createFromAscii("com.sun.star.frame.Desktop"”));

    ...

    // reference goes out of scope now, release is called on the interface

}

The constructor of Reference calls acquire() on the interface and the destructor calls release() on the interface. These references are often called smart pointers. Always use the Reference template consistently to avoid reference counting bugs.

The Reference class makes it simple to invoke queryInterface() for a certain type:

// construct a deskop object and acquire it

Reference< XInterface > rDesktop = xSMgr->createInstance(

    OUString::createFromAscii("com.sun.star.frame.Desktop"));

// query it for the XFrameLoader interface

Reference< XFrameLoader > rLoader( rDesktop , UNO_QUERY );

// check, if the frameloader interface is supported

if( rLoader.is() )

{

    // now do something with the frame loader

    ...

}

The UNO_QUERY is a dummy parameter that tells the constructor to query the first constructor argument for the XFrameLoader interface. If the queryInterface() returns successfully, it is assigned to the rLoader reference. You can check if querying was successful by calling is() on the new reference.

Methods on interfaces can be invoked using the operator ->:

xSMgr->createInstance(...);

The operator()->() returns the interface pointer without acquiring it, that is, without incrementing the refcount.

Tip graphics marks a hint section in the text

If you need the direct pointer to an interface for some purpose, you can also call get() at the reference class.

You can explicitly release the interface reference by calling clear()at the reference or by assigning a default constructed reference.

You can check if two interface references belong to the same object using the operator ==.

Mapping of Sequence

An IDL sequence is mapped to:

template< class t >

com::sun::star::uno::Sequence< t >

The sequence class is a reference to a reference counted handle that is allocated on the heap.

The sequence follows a copy-on-modify strategy. If a sequence is about to be modified, it is checked if the reference count of the sequence is 1. If this is the case, it gets modified directly, otherwise a copy of the sequence is created that has a reference count of 1.

A sequence can be created with an arbitrary UNO type as element type, but do not use a non-UNO type. The full reflection data provided by the type library are needed for construction, destruction and comparison.

You can construct a sequence with an initial number of elements. Each element is default constructed.

{

    // create an integer sequence with 3 elements,

    // elements default to zero.

    Sequence< sal_Int32 > seqInt( 3 );

    // get a read/write array pointer (this method checks for

    // the refcount and does a copy on demand).

    sal_Int32 *pArray = seqInt.getArray();

    // if you know, that the refocunt is one

    // as in this case, where the sequence has just been

    // constructed, you could avoid the check,

    // which is a C-call overhead,

    // by writing sal_Int32 *pArray = (sal_Int32*) seqInt.getConstArray();

    // modify the members

    pArray[0] = 4;

    pArray[1] = 5;

    pArray[2] = 3;

}

You can also initialize a sequence from an array of the same type by using a different constructor. The new sequence is allocated on the heap and all elements are copied from the source.

{

    sal_Int32 sourceArray[3] = {3,5,3};

    // result is the same as above, but we initialize from a buffer.

    Sequence< sal_Int32 > seqInt( sourceArray , 3 );

}

Complex UNO types like structs can be stored within sequences, too:

{

    // construct a sequence of Property structs,

    // the structs are default constructed

    Sequence< Property > seqProperty(2);

    seqProperty[0].Name = OUString::createFromAscii( "A" );

    seqProperty[0].Handle = 0;

    seqProperty[1].Name = OUString::createFromAscii( "B" );

    seqProperty[1].Handle = 1;

    // copy construct the sequence (The refcount is raised)

    Sequence< Property > seqProperty2 = seqProperty;

                               

    // access a sequence

    for( sal_Int32 i = 0 ; i < seqProperty.getLength() ; i ++ )

    {

        // Please NOTE : seqProperty.getArray() would also work, but  

        //               it is more expensive, because a

        //               unnessecary copy construction

        //               of the sequence takes place.

        printf( "%d\n" , seqProperty.getConstArray()[i].Handle );

    }

}

The size of sequences can be changed using the realloc() method, which takes the new number of elements as a parameter. For instance:

// construct an empty sequence

Sequence < Any > anySequence;

// get your enumeration from somewhere

Reference< XEnumeration > rEnum = ...;

// iterate over the enumeration

while( rEnum->hasMoreElements() )

{       

    anySequence.realloc( anySequence.getLength() + 1 );

    anySequence[anySequence.getLength()-1] = rEnum->nextElement();

}

The above code shows an enumeration is transformed into a sequence,using an inefficient method. The realloc() default constructs a new element at the end of the sequence. If the sequence is shrunk by realloc, the elements at the end are destroyed.

The sequence is meant as a transportation container only, therefore it lacks methods for efficient insertion and removal of elements. Use a C++ Standard Template Library vector as an intermediate container to manipulate a list of elements and finally copy the elements into the sequence.

Sequences of a specific type are a fully supported UNO type. There can also be a sequence of sequences. This is similar to a multidimensional array with the exception that each row may vary in length. For instance:

{

    sal_Int32 a[ ] = { 1,2,3 }, b[] = {4,5,6}, c[] = {7,8,9,10};

    Sequence< Sequence< sal_Int32 > > aaSeq ( 3 );

    aaSeq[0] = Sequence< sal_Int32 >( a , 3 );

    aaSeq[1] = Sequence< sal_Int32 >( b , 3 );

    aaSeq[2] = Sequence< sal_Int32 >( c , 4 );

}

is a valid sequence of sequence< sal_Int32>.

Mapping of Type

A type is mapped to com::sun::star::uno::Type. It holds the name of a type and the com.sun.star.uno.TypeClass. The type allows you to obtain a com::sun::star::uno:: TypeDescription that contains all the information defined in the IDL. A UNO type object for a specific type using the overloaded cppu:: getCppuType () function can be constructed:

// get the type of sal_Int32

Type intType = getCppuType( (sal_Int32 *) 0 );

// get the type of a string

Type stringType = getCppuType( (OUString * ) 0 );

// get the type of the XEnumeration interface

Type xenumerationType = getCppuType( (Reference<XEnumeration>*) 0 );

The above code is useful to write template functions. Some getCppuType() functions would be ambiguous. There are specialized functions: getVoidCppuType(), getBooleanCppuType(), getCharCppuType()to handle the ambiguous functions.

The functions are implemented inline and introduced by headers that have been generated from the type library.

Using Weak References

The C++ binding offers a method to hold UNO objects weakly, that is, not holding a hard reference to it. A hard reference prevents an object from being destroyed, whereas an object that is held weakly can be deleted anytime. The advantage of weak references is used to avoid cyclic references between objects.

An object must actively support weak references by supporting the com.sun.star.uno.XWeak interface. The concept is explained in detail in chapter 3.3.7 Professional UNO - UNO Concepts - Lifetime of UNO Objects.

Weak references are often used for caching. For instance, if you want to reuse an existing object, but do not want to hold it forever to avoid cyclic references.

Weak references are implemented as a template class:

template< class t >

class com::sun::star::uno::WeakReference<t>

You can simply assign weak references to hard references and conversely. The following examples stress this:

// forward declaration of a function that

Reference< XFoo > getFoo();

int main()

{

    // default construct a weak reference.

    // this reference is empty

    WeakReference < XFoo > weakFoo;

    {

        // obtain a hard reference to an XFoo object

        Reference< XFoo > hardFoo = getFoo();

        assert( hardFoo.is() );

        // assign the hard reference to weak referencecount

        weakFoo = hardFoo;

        // the hardFoo reference goes out of scope. The object itself

        // is now destroyed, if no one else keeps a reference to it.

        // Nothing happens, if someone else still keeps a reference to it

    }

    // now make the reference hard again

    Reference< XFoo > hardFoo2 = weakFoo;

       

    // check, if this was successful

    if( hardFoo2.is() )

    {

        // the object is still alive, you can invoke calls on it again

        hardFoo2->foo();

    }

    else

    {

        // the objects has died, you can't do anything with it anymore.

    }

}

A call on a weak reference can not be invoked directly. Make the weak reference hard and check whether it succeeded or not. You never know if you will get the reference, therefore always handle both cases properly.

It is more expensive to use weak references instead of hard references. When assigning a weak reference to a hard reference, a mutex gets locked and some heap allocation may occur. When the object is located in a different process, at least one remote call takes place, meaning an overhead of approximately a millisecond.

The XWeak mechanism does not support notification at object destruction. For this purpose, objects must export XComponent and add com.sun.star.lang.XEventListener.

Exception Handling in C++

For throwing and catching of UNO exceptions, use the normal C++ exception handling mechanisms. Calls to UNO interfaces may only throw the com::sun::star::uno::Exception or derived exceptions. The following example catches every possible exception:

try

{

    Reference< XInterface > rInitialObject =

        xUnoUrlResolver->resolve( OUString::createFromAsci(

            “uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager” ) );

}

catch( com::sun::star::uno::Exception &e )

{

    OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

    printf( "An error occurred: %s\n", o.pData->buffer );

}

If you want to react differently for each possible exception type, look up the exceptions that may be thrown by a certain method. For instance the resolve() method in com.sun.star.bridge.XUnoUrlResolver is allowed to throw three kinds of exceptions. Catch each exception type separately:

try

{

    Reference< XInterface > rInitialObject =

    xUnoUrlResolver->resolve( OUString::createFromAsci(

        “uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager” ) );

}

catch( ConnectionSetupException &e )

{

    OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

    printf( "%s\n", o.pData->buffer );

    printf( "couldn't access local resource ( possible security resons )\n" );

}

catch( NoConnectException &e )

{

    OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

    printf( "%s\n", o.pData->buffer );

    printf( "no server listening on the resource\n" );

}

catch( IllegalArgumentException &e )

{

    OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

    printf( "%s\n", o.pData->buffer );

    printf( "uno URL invalid\n" );

}

catch( RuntimeException & e )

{

    OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );

    printf( "%s\n", o.pData->buffer );

    printf( "an unknown error has occurred\n" );

}

When implementing your own UNO objects (see 4.6 Writing UNO Components - C++ Component), throw exceptions using the normal C++ throw statement:

void MyUnoObject::initialize( const Sequence< Any > & args.getLength() ) throw( Exception )
{

    // we expect 2 elements in this sequence

    if( 2 != args.getLength() )

    {

        // create an error message

        OUStringBuffer buf;

        buf.appendAscii( “MyUnoObject::initialize, expected 2 args, got ” );

        buf.append( args.getLength() );

        buf.append( “.” );

        // throw the exception

        throw Exception( buf.makeStringAndClear() , *this );

    }

    ...

}

Note that only exceptions derived from com::sun::star::uno::Exception may be thrown at UNO interface methods. Other exceptions (for instance the C++ std::exception) cannot be bridged by the UNO runtime if the caller and called object are not within the same UNO Runtime Environment. Moreover, most current Unix C++ compilers, for instance gcc 3.0.x, do not compile code. During compilation, exception specifications are loosen in derived classes by throwing exceptions other than the exceptions specified in the interface that it is derived. Throwing unspecified exceptions leads to a std::unexpected exception and causes the program to abort on Unix systems.

3.4.3    OpenOffice.org Basic

OpenOffice.org Basic provides access to the OpenOffice.org API from within the office application. It hides the complexity of interfaces and simplifies the use of properties by making UNO objects look like Basic objects. It offers convenient Runtime Library (RTL) functions and special Basic properties for UNO. Furthermore, Basic procedures can be easily hooked up to GUI elements, such as menus, toolbar icons and GUI event handlers.

This chapter describes how to access UNO using the OpenOffice.org Basic scripting language. In the following sections, OpenOffice.org Basic is referred to as Basic.

Handling UNO Objects

Accessing UNO Services

UNO objects are used through their interface methods and properties. Basic simplifies this by mapping UNO interfaces and properties to Basic object methods and properties.

First, in Basic it is not necessary to distinguish between the different interfaces an object supports when calling a method. The following illustration shows an example of an UNO service that supports three interfaces:

UMl diagram showing an example service with three interfaces
Illustration 1.17: Basic Hides Interfaces

In Java and C++, it is necessary to obtain a reference to each interface before calling one of its methods. In Basic, every method of every supported interface can be called directly at the object without querying for the appropriate interface in advance. The '.' operator is used:

    ' Basic

    oExample = getExampleObjectFromSomewhere()

    oExample.doNothing()                ' Calls method doNothing of XFoo1

    oExample.doSomething()                ' Calls method doSomething of XFoo2

    oExample.doSomethingElse(42)        ' Calls method doSomethingElse of XFoo2

Additionally, OpenOffice.org Basic interprets pairs of get and set methods at UNO objects as Basic object properties if they follow this pattern:

SomeType getSomeProperty()

void setSomeProperty(SomeType aValue)

In this pattern, OpenOffice.org Basic offers a property of type SomeType named SomeProperty. This functionality is based on the com.sun.star.beans.Introspection service. For additional details, see 5.2.3 Advanced UNO - Language Bindings - UNO Reflection API.

The get and set methods can always be used directly. In our example service above, the methods getIt() and setIt(), or read and write a Basic property It are used:

    Dim x as Integer

    x = oExample.getIt()    ' Calls getIt method of XFoo3

    ' is the same as

    x = oExample.It        ' Read property It represented by XFoo3

    oExample.setIt( x )    ' Calls setIt method of XFoo3

    ' is the same as

    oExample.It = x        ' Modify property It represented by XFoo3

If there is only a get method, but no associated set method, the property is considered to be read only.

    Dim x as Integer, y as Integer

    x = oExample.getMore()    ' Calls getMore method of XFoo1

    y = oExample.getLess()    ' Calls getLess method of XFoo1

    ' is the same as

    x = oExample.More    ' Read property More represented by XFoo1

    y = oExample.Less    ' Read property Less represented by XFoo1

    ' but

    oExample.More = x    ' Runtime error “Property is read only”

    oExample.Less = y    ' Runtime error “Property is read only”

Properties an object provides through com.sun.star.beans.XPropertySet are available through the . operator. The methods of com.sun.star.beans.XPropertySet can be used also. The object oExample2 in the following example has three integer properties Value1, Value2 and Value3 :

    Dim x as Integer, y as Integer, z as Integer

    x = oExample2.Value1

    y = oExample2.Value2

    z = oExample2.Value3

    ' is the same as

    x = oExample2.getPropertyValue( “Value1” )

    y = oExample2.getPropertyValue( “Value2” )

    z = oExample2.getPropertyValue( “Value3” )

    ' and

    oExample2.Value1 = x

    oExample2.Value2 = y

    oExample2.Value3 = z

    ' is the same as

    oExample2.setPropertyValue( “Value1”, x )

    oExample2.setPropertyValue( “Value2”, y )

    oExample2.setPropertyValue( “Value3”, z )

Basic uses com.sun.star.container.XNameAccess to provide named elements in a collection through the . operator. However, XNameAccess only provides read access. If a collection offers write access through com.sun.star.container.XNameReplace or com.sun.star.container.XNameContainer, use the appropriate methods explicitly:

    ' oNameAccessible is an object that supports XNameAccess

    ' including the names “Value1”, “Value2”

    x = oNameAccessible.Value1

    y = oNameAccessible.Value2

    ' is the same as

    x = oNameAccessible.getByName( “Value1” )

    y = oNameAccessible.getByName( “Value2” )

    ' but

    oNameAccessible.Value1 = x        ' Runtime Error, Value1 cannot be changed

    oNameAccessible.Value2 = y        ' Runtime Error, Value2 cannot be changed

    ' oNameReplace is an object that supports XNameReplace

    ' replaceByName() sets the element Value1 to 42

    oNameReplace.replaceByName( "Value1", 42 )

Instantiating UNO Services

In Basic, instantiate services using the Basic Runtime Library (RTL) function createUnoService(). This function expects a fully qualified service name and returns an object supporting this service, if it is available:

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

This call instantiates the com.sun.star.ucb.SimpleFileAccess service. To ensure that the function was successful, the returned object can be checked with the IsNull function:

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

    bError = IsNull( oSimpleFileAccess )        ' bError is set to False

    oNoService = CreateUnoService( "com.sun.star.nowhere.ThisServiceDoesNotExist" )

    bError = IsNull( oNoService )                ' bError is set to True

Instead of using CreateUnoService() to instantiate a service, it is also possible to get the global UNO com.sun.star.lang.ServiceManager of the OpenOffice.org process by calling GetProcessServiceManager(). Once obtained, use createInstance() directly:

    oServiceMgr = GetProcessServiceManager()

    oSimpleFileAccess = oServiceMgr.createInstance( "com.sun.star.ucb.SimpleFileAccess" )

    ' is the same as

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

The advantage of GetProcessServiceManager() is that additional information and pass in arguments is received when services are instantiated using the service manager. For instance, to initialize a service with arguments, the createInstanceWithArguments() method of com.sun.star.lang.XMultiServiceFactory has to be used at the service manager, because there is no appropriate Basic RTL function to do that. Example:

    Dim args(1)

    args(0) = "Important information"

    args(1) = "Even more important information"

    oService = oServiceMgr.createInstanceWithArguments _

        ( "com.sun.star.nowhere.ServiceThatNeedsInitialization", args() )

The object returned by GetProcessServiceManager() is a normal Basic UNO object supporting com.sun.star.lang.ServiceManager. Its properties and methods are accessed as described above.

In addition, the Basic RTL provides special properties as API entry points. They are described in more detail in 11.3 OpenOffice.org Basic and Dialogs - Features of OpenOffice.org Basic:

OpenOffice.org Basic RTL Property

Description

ThisComponent

Only exists in Basic code which is embedded in a Writer, Calc, Draw or Impress document. It contains the document model the Basic code is embedded in.

StarDesktop

The com.sun.star.frame.Desktop singleton of the office application. It loads document components and handles the document windows. For instance, the document in the top window can be retrieved using
oDoc = StarDesktop.CurrentComponent

Getting Information about UNO Objects

The Basic RTL retrieves information about UNO objects. There are functions to evaluate objects during runtime and object properties used to inspect objects during debugging.

Checking for interfaces during runtime

Although Basic does not support the queryInterface concept like C++ and Java, it can be useful to know if a certain interface is supported by a UNO Basic object or not. The function HasUnoInterfaces() detects this.

The first parameter HasUnoInterfaces() expects the object that should be tested. Parameter(s) of one or more fully qualified interface names can be passed to the function next. The function returns True if all these interfaces are supported by the object, otherwise False.

Sub Main

    Dim oSimpleFileAccess

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

    Dim bSuccess

    Dim IfaceName1$, IfaceName2$, IfaceName3$

    IfaceName1$ = "com.sun.star.uno.XInterface"

    IfaceName2$ = "com.sun.star.ucb.XSimpleFileAccess2"

    IfaceName3$ = "com.sun.star.container.XPropertySet"

    bSuccess = HasUnoInterfaces( oSimpleFileAccess, IfaceName1$ )

    MsgBox bSuccess    ' Displays True because XInterface is supported

    bSuccess = HasUnoInterfaces( oSimpleFileAccess, IfaceName1$, IfaceName2$ )

    MsgBox bSuccess    ' Displays True because XInterface

                       ' and XSimpleFileAccess2 are supported

    bSuccess = HasUnoInterfaces( oSimpleFileAccess, IfaceName3$ )

    MsgBox bSuccess    ' Displays False because XPropertySet is NOT supported

    bSuccess = HasUnoInterfaces( oSimpleFileAccess, IfaceName1$, IfaceName2$, IfaceName3$ )

    MsgBox bSuccess    ' Displays False because XPropertySet is NOT supported

End Sub

Testing if an object is a struct during runtime

As described in the section 3.4.3 Professional UNO - UNO Language Bindings - OpenOffice.org Basic - Type Mappings - Structs above, structs are handled differently from objects, because they are treated as values. Use the IsUnoStruct () function to check it the UNO Basic object represents an object or a struct. This function expects one parameter and returns True if this parameter is a UNO struct, otherwise False. Example:

Sub Main

    Dim bIsStruct

        ' Instantiate a service

    Dim oSimpleFileAccess

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

    bIsStruct = IsUnoStruct( oSimpleFileAccess )

    MsgBox bIsStruct    ' Displays False because oSimpleFileAccess is NO struct

                        ' Instantiate a Property struct

    Dim aProperty As New com.sun.star.beans.Property

    bIsStruct = IsUnoStruct( aProperty )

    MsgBox bIsStruct    ' Displays True because aProperty is a struct

    bIsStruct = IsUnoStruct( 42 )

    MsgBox bIsStruct    ' Displays False because 42 is NO struct

End Sub

Testing objects for identity during runtime

To find out if two UNO OpenOffice.org Basic objects refer to the same UNO object instance, use the function EqualUnoObjects(). Basic is not able to apply the comparison operator = to arguments of type object, for example, If Obj1 = Obj2 Then which leads to a runtime error.

Sub Main

    Dim bIdentical

    Dim oSimpleFileAccess, oSimpleFileAccess2, oSimpleFileAccess3

        ' Instantiate a service

    oSimpleFileAccess = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

    oSimpleFileAccess2 = oSimpleFileAccess    ' Copy the object reference

    bIdentical = EqualUnoObjects( oSimpleFileAccess, oSimpleFileAccess2 )

    MsgBox bIdentical    ' Displays True because the objects are identical

       ' Instantiate the service a second time

    oSimpleFileAccess3 = CreateUnoService( "com.sun.star.ucb.SimpleFileAccess" )

    bIdentical = EqualUnoObjects( oSimpleFileAccess, oSimpleFileAccess3 )

    MsgBox bIdentical    ' Displays False, oSimpleFileAccess3 is another instance

    bIdentical = EqualUnoObjects( oSimpleFileAccess, 42 )

    MsgBox bIdentical    ' Displays False, 42 is not even an object

       ' Instantiate a Property struct

    Dim aProperty As New com.sun.star.beans.Property

    Dim aProperty2

    aProperty2 = aProperty    ' Copy the struct

    bIdentical = EqualUnoObjects( aProperty, aProperty2 )

    MsgBox bIdentical    ' Displays False because structs are values

                         ' and so aProperty2 is a copy of aProperty

End Sub

Basic hides interfaces behind OpenOffice.org Basic objects that could lead to problems when developers are using API structures. It can be difficult to understand the API reference and find the correct method of accessing an object to reach a certain goal.

To assist during development and debugging, every UNO object in OpenOffice.org Basic has special properties that provide information about the object structure. These properties are all prefixed with Dbg_ to emphasize their use for development and debugging purposes. The type of these properties is String. To display the properties use the MsgBox function.

Inspecting interfaces during debugging

The Dbg_SupportedInterfaces lists all interfaces supported by the object. In the following example, the object returned by the function GetProcessServiceManager() described in the previous section is taken as an example object.

    oServiceManager = GetProcessServiceManager()

    MsgBox oServiceManager.Dbg_SupportedInterfaces

This call displays a message box:

Screenshot shwoing the output of dbg_supportedInterfaces
Illustration 1.18: Dbg_SupportedInterfaces Property

The list contains all interfaces supported by the object. For interfaces that are derived from other interfaces, the super interfaces are indented as shown above for com.sun.star.container.XSet, which is derived from com.sun.star.container.XEnumerationAccess based upon com.sun.star.container.XElementAccess.

Note graphics marks a special text section

If the text “(ERROR: Not really supported!)” is printed behind an interface name, the implementation of the object usually has a bug, because the object pretends to support this interface (per com.sun.star.lang.XTypeProvider, but a query for it fails. For details, see 5.2.3 Advanced UNO - Language Bindings - UNO Reflection API).

Inspecting properties during debugging

The Dbg_Properties lists all properties supported by the object through com.sun.star.beans.XPropertySet and through get and set methods that could be mapped to Basic object properties:

    oServiceManager = GetProcessServiceManager()

    MsgBox oServiceManager.Dbg_Properties

This code produces a message box like this:

Screenshot showing the output of dbg_properties
Illustration 1.19: Dbg_Properties

Inspecting Methods During Debugging

The Dbg_Methods lists all methods supported by an object. Example:

    oServiceManager = GetProcessServiceManager()

    MsgBox oServiceManager.Dbg_Methods

This code displays:

Screenshot showing the output of dbg_methods
Illustration 1.20: Dbg_Methods

The notations used in Dbg_Properties and Dbg_Methods refer to internal implementation type names in Basic. The Sbx prefix can be ignored. The remaining names correspond with the normal Basic type notation. The SbxEMPTY is the same type as Variant. Additional information about Basic types is available in the next chapter.

Note graphics marks a special text section

Basic uses the com.sun.star.lang.XTypeProvider interface to detect which interfaces an object supports. Therefore, it is important to support this interface when implementing a component that should be accessible from Basic. For details, see 4 Writing UNO Components.

Mapping of UNO and Basic Types

Basic and UNO use different type systems. While OpenOffice.orgBasic is compatible to Visual Basic and its type system, UNO types correspond to the IDL specification (see 3.2.1 Professional UNO - API Concepts - Data Types), therefore it is necessary to map these two type systems. This chapter describes which Basic types have to be used for the different UNO types.

Mapping of Simple Types

In general, the OpenOffice.orgBasic type system is not rigid. Unlike C++ and Java, OpenOffice.orgBasic does not require the declaration of variables, unless the Option Explicit command is used that forces the declaration. To declare variables, the Dim command is used. Also, a OpenOffice.orgBasic type can be optionally specified through the Dim command. The general syntax is:

    Dim VarName [As Type][, VarName [As Type]]...

All variables declared without a specific type have the type Variant. Variables of type Variant can be assigned values of arbitrary Basic types. Undeclared variables are Variant unless type postfixes are used with their names. Postfixes can be used in Dim commands as well. The following table contains a complete list of types supported by Basic and their corresponding postfixes:

Type

Postfix

Range

Boolean

True or False

Integer

%

-32768 to 32767

Long

&

-2147483648 to 2147483647

Single

!

Floating point number
negative: -3.402823E38 to -1.401298E-45
positive: 1.401298E-45 to 3.402823E38

Double

#

Double precision floating point number
negative: -1.79769313486232E308 to -4.94065645841247E-324
positive: 4.94065645841247E-324 to 1.79769313486232E308

Currency

@

Fixed point number with four decimal places
-922,337,203,685,477.5808 to 922,337,203,685,477.5807

Date

01/01/100 to 12/31/9999

Object

Basic Object

String

$

Character string

Any

arbitrary Basic type

Consider the following Dim examples.

    Dim a, b            ' Type of a and b is Variant

    Dim c as Variant    ' Type of c is Variant

    Dim d as Integer    ' Type of d is Integer (16 bit!)

    ' The type only refers to the preceding variable

    Dim e, f as Double  ' ATTENTION! Type of e is Variant!

                        '   Only the type of f is Double

    Dim g as String     ' Type of g is String

    Dim i as Date       ' Type of g is Date

    ' Usage of Postfixes

    Dim i%              ' is the same as

    Dim i as Integer

    Dim d#              ' is the same as

    Dim d as Double

    Dim s$              ' is the same as

    Dim s as String

The correlation below is used to map types from UNO to Basic and vice versa.

Uno type

Basic type

long

Long

hyper

Not yet supported

short

Integer

float

Single

double

Double

char

Char (only used internally)

byte

Integer

any

Variant

string

String

boolean

Boolean

void

Void (only used internally)

type

com.sun.star.reflection.XIdlClass

The simple UNO type type is mapped to the com.sun.star.reflection.XIdlClass interface to retrieve type specific information. For further details, refer to 5.2.3 Advanced UNO - Language Bindings - UNO Reflection API.

When UNO methods or properties are accessed, and the target UNO type is known, Basic automatically chooses the appropriate types:

    ' The UNO object oExample1 has a property “Count” of type short

    a% = 42

    oExample1.Count = a%    ' a% has the right type (Integer)

    pi = 3,141593

    oExample1.Count = pi    ' pi will be converted to short, so Count will become 3

    s$ = “111”

    oExample1.Count = s$    ' s$ will be converted to short, so Count will become 111

Occasionally, OpenOffice.orgBasic does not know the required target type, especially if a parameter of an interface method or a property has the type any. In this situation, OpenOffice.orgBasic mechanically converts the OpenOffice.orgBasic type into the UNO type shown in the table above, although a different type may be expected. The only mechanism provided by OpenOffice.orgBasic is an automatic downcast of numeric values:

Long and Integer values are always converted to the shortest possible integer type:

The Single/Double values are converted to integers in the same manner if they have no decimal places.

This mechanism is used, because some internal C++ tools used to implement UNO functionality in OpenOffice.org provide an automatic upcast but no downcast. Therefore, it can be successful to pass a byte value to an interface expecting a long value, but not vice versa.

In the following example, oNameCont is an object that supports com.sun.star.container.XNameContainer and contains elements of type short. Assume FirstValue is a valid entry.

    a% = 42

    oNameCount.replaceByName( “FirstValue”, a% )    ' Ok, a% is downcasted to type byte

    b% = 123456

    oNameCount.replaceByName( “FirstValue”, b% )    ' Fails, b% is outside the short range

The method call fails, therefore the implementation should throw the appropriate exception that is converted to a OpenOffice.orgBasic error by the OpenOffice.orgBasic RTL. It may happen that an implementation also accepts unsuitable types and does not throw an exception. Ensure that the values used are suitable for their UNO target by using numeric values that do not exceed the target range or converting them to the correct Basic type before applying them to UNO.

Always use the type Variant to declare variables for UNO Basic objects, not the type Object. The OpenOffice.orgBasic type Object is tailored for pure OpenOffice.orgBasic objects and not for UNO OpenOffice.orgBasic objects. The Variant variables are best for UNO Basic objects to avoid problems that can result from the OpenOffice.orgBasic specific behavior of the type Object:

    Dim oService1    ' Ok

    oService1 = CreateUnoService( "com.sun.star.anywhere.Something" )

    Dim oService2 as Object    ' NOT recommended

    oService2 = CreateUnoService( "com.sun.star.anywhere.SomethingElse" )

Mapping of Sequences and Arrays

Many UNO interfaces use sequences, as well as simple types. The OpenOffice.orgBasic counterpart for sequences are arrays. Arrays are standard elements of the Basic language. The example below shows how they are declared:

    Dim a1( 100 )    ' Variant array, index range: 0-100 -> 101 elements

    Dim a2%( 5 )     ' Integer array, index range: 0-5 -> 6 elements

    Dim a3$( 0 )     ' String array, index range: 0-0 -> 1 element

    Dim a4&( 9, 19 ) ' Long array, index range: (0-9) x (0-19) -> 200 elements

Basic does not have a special index operator like [] in C++ and Java. Array elements are accessed using normal parentheses ():

    Dim i%, a%( 10 )

    for i% = 0 to 10            ' this loop initializes the array

       a%(i%) = i%

    next i%

    dim s$

    for i% = 0 to 10            ' this loop adds all array elements to a string

       s$ = s$ + " " + a%(i%)

    next i%

    msgbox s$                   ' Displays the string containing all array elements

    Dim b( 2, 3 )

    b( 2, 3 ) = 23

    b( 0, 0 ) = 0

    b( 2, 4 ) = 24              ' Error ”Subscript out of range”

As the examples show, the indices in Dim commands differ from C++ and Java array declarations. They do not describe the number of elements, but the largest allowed index. There is one more array element than the given index. This is important for the mapping of OpenOffice.orgBasic arrays to UNO sequences, because UNO sequences follow the C++/Java array semantic.

When the UNO API requires a sequence, the Basic programmer uses an appropriate array. In the following example, oSequenceContainer is an object that has a property TheSequence of type sequence<short>. To assign a sequence of length 10 with the values 0, 1, 2, ... 9 to this property, the following code can be used:

    Dim i%, a%( 9 )    ' Maximum index 9 -> 10 elements

    for i% = 0 to 9    ' this loop initializes the array

       a%(i%) = i%

    next i%

    oSequenceContainer.TheSequence = a%()

    ' If “TheSequence” is based on XPropertySet alternatively

    oSequenceContainer.setPropertyValue( “TheSequence”, a%() )

The Basic programmer must be aware of the different index semantics during programming. In the following example, the programmer passed a sequence with one element, but actually passed two elements:

    ' Pass a sequence of length 1 to the TheSequence property:

    Dim a%( 1 )     ' WRONG: The array has 2 elements, not only 1!

    a%( 0 ) = 3     ' Only Element 0 is initialized,

                    ' Element 1 remains 0 as initialized by Dim

    ' Now a sequence with two values (3,0) is passed what

    ' may result in an error or an unexpected behavior!

    oSequenceContainer.setPropertyValue( “TheSequence”, a%() )

Note graphics marks a special text section

When using Basic arrays as a whole for parameters or for property access, they should always be followed by '()' in the Basic code, otherwise errors may occur in some situations.

It can be useful to use a OpenOffice.orgBasic RTL function called Array() to create, initialize and assign it to a Variant variable in a single step, especially for small sequences:

    Dim a        ' should be declared as Variant

    a = Array( 1, 2, 3 )

    ' is the same as

    Dim a(2)

    a( 0 ) = 1

    a( 1 ) = 2

    a( 2 ) = 3

Sometimes it is necessary to pass an empty sequence to a UNO interface. In Basic, empty sequences can be declared by omitting the index from the Dim command:

    Dim a%()    ' empty array/sequence of type Integer

    Dim b$()    ' empty array/sequence of String

Sequences returned by UNO are also represented in Basic as arrays, but these arrays do not have to be declared as arrays beforehand. Variables used to accept a sequence should be declared as Variant. To access an array returned by UNO, it is necessary to get information about the number of elements it contains with the Basic RTL functions LBound() and UBound().

The function LBound() returns the lower index and UBound() returns the upper index. The following code shows a loop going through all elements of a returned sequence:

    Dim aResultArray    ' should be declared as Variant

    aResultArray = oSequenceContainer.TheSequence

    dim i%, iFrom%, iTo%

    iFrom% = LBound( aResultArray() )

    iTo%   = UBound( aResultArray() )

    for i% = iFrom% to iTo%    ' this loop displays all array elements

       msgbox aResultArray(i%)

    next i%

The function LBound() is a standard Basic function and is not specific in a UNO context. Basic arrays do not necessarily start with index 0, because it is possible to write something similar to:

Dim a (3 to 5 )

This causes the array to have a lower index of 3. However, sequences returned by UNO always have the start index 0. Usually only UBound() is used and the example above can be simplified to:

    Dim aResultArray    ' should be declared as Variant

    aResultArray = oSequenceContainer.TheSequence

    Dim i%, iTo%

    iTo%   = UBound( aResultArray() )

    For i% = 0 To iTo%    ' this loop displays all array elements

       MsgBox aResultArray(i%)

    Next i%

The element count of a sequence/array can be calculated easily:

    u% = UBound( aResultArray() )

    ElementCount% = u% + 1

For empty arrays/sequences UBound returns -1. This way the semantic of UBound stays consistent as the element count is then calculated correctly as:

    ElementCount% = u% + 1        ' = -1 + 1 = 0

Note graphics marks a special text section

The mapping between UNO sequences and Basic arrays depends on the fact that both use a zero-based index system. To avoid problems, the syntax
Dim a ( IndexMin to IndexMin )
or the Basic command Option Base 1 should not be used. Both cause all Basic arrays to start with an index other than 0.

UNO also supports sequences of sequences. In Basic, this corresponds with arrays of arrays. Do not mix up sequences of sequences with multidimensional arrays. In multidimensional arrays, all sub arrays always have the same number of elements, whereas in sequences of sequences every element sequence can have a different size. Example:

    Dim aArrayOfArrays    ' should be declared as Variant

    aArrayOfArrays = oExample.ShortSequences    ' returns a sequence of sequences of short

    Dim i%, NumberOfSequences%

    Dim j%, NumberOfElements%

    Dim aElementArray

    NumberOfSequences% = UBound( aArrayOfArrays() ) + 1

    For i% = 0 to NumberOfSequences% - 1        ' loop over all sequences

       aElementArray = aArrayOfArrays( i% )

       NumberOfElements% = UBound( aElementArray() ) + 1

       For j% = 0 to NumberOfElements% - 1    ' loop over all elements

          MsgBox aElementArray( j% )

       Next j%

    Next i%

To create an array of arrays in Basic, sub arrays are used as elements of a master array:

    ' Declare master array

    Dim aArrayOfArrays( 2 )

    ' Declare sub arrays

    Dim aArray0( 3 )

    Dim aArray1( 2 )

    Dim aArray2( 0 )

    ' Initialise sub arrays

    aArray0( 0 ) = 0

    aArray0( 1 ) = 1

    aArray0( 2 ) = 2

    aArray0( 3 ) = 3

    aArray1( 0 ) = 42

    aArray1( 1 ) = 0

    aArray1( 2 ) = -42

    aArray2( 0 ) = 1

    ' Assign sub arrays to the master array

    aArrayOfArrays( 0 ) = aArray0()

    aArrayOfArrays( 1 ) = aArray1()

    aArrayOfArrays( 2 ) = aArray2()

    ' Assign the master array to the array property

    oExample.ShortSequences = aArrayOfArrays()

In this situation, the runtime function Array() is useful. The example code can then be written much shorter:

    ' Declare master array

    Dim aArrayOfArrays( 2 )

    ' Create and assign sub arrays

    aArrayOfArrays( 0 ) = Array( 0, 1, 2, 3 )

    aArrayOfArrays( 1 ) = Array( 42, 0, -42 )

    aArrayOfArrays( 2 ) = Array( 1 )

    ' Assign the master array to the array property

    oExample.ShortSequences = aArrayOfArrays()

If you nest Array(), more compact code can be written, but it becomes difficult to understand the resulting arrays:

    ' Declare master array variable as variant

    Dim aArrayOfArrays

    ' Create and assign master array and sub arrays

    aArrayOfArrays = Array( Array( 0, 1, 2, 3 ), Array( 42, 0, -42 ), Array( 1 ) )

    ' Assign the master array to the array property

    oExample.ShortSequences = aArrayOfArrays()

Sequences of higher order can be handled accordingly.

Mapping of Structs

UNO struct types can be instantiated with the Dim As New command as a single instance and array.

    ' Instantiate a Property struct

    Dim aProperty As New com.sun.star.beans.Property

    ' Instantiate an array of Locale structs

    Dim Locales(10) As New com.sun.star.lang.Locale

UNO struct instances are handled like UNO objects. Struct members are accessed using the . operator. The Dbg_Properties property is supported. The properties Dbg_SupportedInterfaces and Dbg_Methods are not supported because they do not apply to structs.:

    ' Instantiate a Locale struct

    Dim aLocale As New com.sun.star.lang.Locale

    ' Display properties

    MsgBox aLocale.Dbg_Properties

    ' Access “Language” property

    aLocale.Language = "en"

Objects and structs are different. Objects are handled as references and structs as values. When structs are assigned to variables, the structs are copied. This is important when modifying an object property that is a struct, because a struct property has to be reassigned to the object after reading and modifying it.

In the following example, oExample is an object that has the properties MyObject and MyStruct.

Both oExample.MyObject.ObjectName and oExample.MyStruct.StructName should be modified. The following code shows how this is done for an object:

    ' Accessing the object

    Dim oObject

    oObject = oExample.MyObject

    oObject.ObjectName = “Tim”    ' Ok!

        ' or shorter

    oExample.MyObject.ObjectName = “Tim”    ' Ok!

The following code shows how it is done correctly for the struct (and possible mistakes):

    ' Accessing the struct

    Dim aStruct

    aStruct = oExample.MyStruct    ' aStruct is a copy of oExample.MyStruct!

    aStruct.StructName = “Tim”     ' Affects only the property of the copy!

        ' If the code ended here, oExample.MyStruct wouldn't be modified!

    oExample.MyStruct = aStruct    ' Copy back the complete struct! Now it's ok!

    ' Here the other variant does NOT work at all, because

    ' only a temporary copy of the struct is modified!

    oExample.MyStruct.StructName = “Tim”    ' WRONG! oExample.MyStruct is not modified!

Mapping of Enums and Constant Groups

Use the fully qualified names to address the values of an enum type by their names. The following examples assume that oExample and oExample2 support com.sun.star.beans.XPropertySet with a property Status of the enum type com.sun.star.beans.PropertyState:

    Dim EnumValue

    EnumValue = com.sun.star.beans.PropertyState.DEFAULT_VALUE

    MsgBox EnumValue    ' displays 1

    eExample.Status = com.sun.star.beans.PropertyState.DEFAULT_VALUE

Basic does not support Enum types. In Basic, enum values coming from UNO are converted to Long values. As long as Basic knows if a property or an interface method parameter expects an enum type, then the Long value is internally converted to the right enum type. Problems appear with Basic when interface access methods expect an Any:

    Dim EnumValue

    EnumValue = oExample.Status    ' EnumValue is of type Long

    ' Accessing the property implicitly

    oExample2.Status = EnumValue         ' Ok! EnumValue is converted to the right enum type

    ' Accessing the property explicitly using XPropertySet methods

    oExample2.setPropertyValue( “Status”, EnumValue )    ' WRONG! Will probably fail!

The explicit access could fail, because EnumValue is passed as parameter of type Any to setPropertyValue(), therefore Basic does not know that a value of type PropertyState is expected. There is still a problem, because the Basic type for com.sun.star.beans.PropertyState is Long. This problem is solved in the implementation of the com.sun.star.beans.XPropertySet interface. For enum types, the implicit property access using the Basic property syntax Object.Property is preferred to calling generic methods using the type Any. In situations where only a generic interface method that expects an enum for an Any, there is no solution for Basic.

Constant groups are used to specify a set of constant values in IDL. In Basic, these constants can be accessed using their fully qualified names. The following code displays some constants from com.sun.star.beans.PropertyConcept:

    MsgBox com.sun.star.beans.PropertyConcept.DANGEROUS    ' Displays 1

    MsgBox com.sun.star.beans.PropertyConcept.PROPERTYSET  ' Displays 2

A constant group or enum can be assigned to an object. This method is used to shorten code if more than one enum or constant value has to be accessed:

    Dim oPropConcept

    oPropConcept = com.sun.star.beans.PropertyConcept

    msgbox oPropConcept.DANGEROUS    ' Displays 1

    msgbox oPropConcept.PROPERTYSET  ' Displays 2

Case Sensitivity

Generally Basic is case insensitive. However, this does not always apply to the communication between UNO and Basic. To avoid problems with case sensitivity write the UNO related code as if Basic was case sensitive. This facilitates the translation of a Basic program to another language, and Basic code becomes easier to read and understand. The following discusses problems that might occur.

Identifiers that differ in case are considered to be identical when they are used with UNO object properties, methods and struct members.

    Dim ALocale As New com.sun.star.lang.Locale

    alocale.language = "en"    ' Ok

    MsgBox aLocale.Language    ' Ok

The exceptions to this is if a Basic property is obtained through com.sun.star.container.XNameAccess as described above, its name has to be written exactly as it is in the API reference. Basic uses the name as a string parameter that is not interpreted when accessing com.sun.star.container.XNameAccess using its methods.

        ' oNameAccessible is an object that supports XNameAccess

    ' including the names “Value1”, “Value2”

    x = oNameAccessible.Value1    ' Ok

    y = oNameAccessible.VaLUe2    ' Runtime Error, Value2 is not written correctly

                ' is the same as

    x = oNameAccessible.getByName( “Value1” )    ' Ok

    y = oNameAccessible.getByName( “VaLUe2” )    ' Runtime Error, Value2 is not written correctly

Exception Handling

Unlike UNO, Basic does not support exceptions. All exceptions thrown by UNO are caught by the Basic runtime system and transformed to a Basic error. Executing the following code results in a Basic error that interrupts the code execution and displays an error message:

    Sub Main

        Dim oLib

        oLib = BasicLibraries.getByName( "InvalidLibraryName" )

    End Sub

The BasicLibraries object used in the example contains all the available Basic libraries in a running office instance. The Basic libraries contained in BasicLibraries is accessed using com.sun.star.container.XNameAccess. An exception was provoked by trying to obtain a non-existing library. The BasicLibraries object is explained in more detail in 11.4 OpenOffice.org Basic and Dialogs - Advanced Library Organization.

The call to getByName() results in this error box:

Screenshot showing an error box of an unhandled exception in Basic
Illustration 1.21: Unhandled UNO Exception

However, the Basic runtime system is not always able to recognize the Exception type. Sometimes only the exception message can be displayed that has to be provided by the object implementation.

Exceptions transformed to Basic errors can be handled just like any Basic error using the On Error GoTo command:

    Sub Main

        On Error Goto ErrorHandler    ' Enables error handling

        Dim oLib

        oLib = BasicLibraries.getByName( "InvalidLibraryName" )

        MsgBox "After the Error"

        Exit Sub

    ' Label

    ErrorHandler:

        MsgBox "Error code: " + Err + Chr$(13) + Error$

        Resume Next    ' Continues execution at the command following the error command

    End Sub

When the exception occurs, the execution continues at the ErrorHandler label. In the error handler, some properties are used to get information about the error. The Err is the error code that is 1 for UNO exceptions. The Error$ contains the text of the error message. Executing the program results in the following output:

Screenshot showing an error box of an handled exception in Basic
Illustration 1.22: Handled UNO Exception

Another message box “After the Error” is displayed after the above dialog box, because Resume Next goes to the code line below the line where the exception was thrown. The Exit Sub command is required so that the error handler code would be executed again.

Listeners

Many interfaces in UNO are used to register listener objects implementing special listener interfaces, so that a listener gets feedback when its appropriate listener methods are called. OpenOffice.org Basic does not support the concept of object implementation, therefore a special RTL function named CreateUnoListener() has been introduced. It uses a prefix for method names that can be called back from UNO. The CreateUnoListener() expects a method name prefix and the type name of the desired listener interface. It returns an object that supports this interface that can be used to register the listener.

The following example instantiates an com.sun.star.container.XContainerListener. Note the prefix ContListener_:

    Dim oListener

    oListener = CreateUnoListener( "ContListener_", "com.sun.star.container.XContainerListener" )

The next step is to implement the listener methods. In this example, the listener interface has the following methods:

Methods of com.sun.star.container.XContainerListener

disposing()

Method of the listener base interface com.sun.star.lang.XEventListener, contained in every listener interface, because all listener interfaces must be derived from this base interface. Takes a com.sun.star.lang.EventObject

elementInserted()

Method of interface com.sun.star.container.XContainerListener. Takes a com.sun.star.container.ContainerEvent.

elementRemoved()

Method of interface com.sun.star.container.XContainerListener. Takes a com.sun.star.container.ContainerEvent.

elementReplaced()

Method of interface com.sun.star.container.XContainerListener. Takes a com.sun.star.container.ContainerEvent.

In the example, ContListener_ is specified as a name prefix, therefore the following subs have to be implemented in Basic.

Every listener type has a corresponding Event struct type that contains information about the event. When a listener method is called, an instance of this Event type is passed as a parameter. In the Basic listener methods these Event objects can be evaluated by adding an appropriate Variant parameter to the procedure header. The following code shows how the listener methods in the example could be implemented:

    Sub ContListener_disposing( oEvent )

        MsgBox "disposing"

        MsgBox oEvent.Dbg_Properties

    End Sub

    Sub ContListener_elementInserted( oEvent )

        MsgBox "elementInserted"

        MsgBox oEvent.Dbg_Properties

    End Sub

    Sub ContListener_elementRemoved( oEvent )

        MsgBox "elementRemoved"

        MsgBox oEvent.Dbg_Properties

    End Sub

    Sub ContListener_elementReplaced( oEvent )

        MsgBox "elementReplaced"

        MsgBox oEvent.Dbg_Properties

    End Sub

It is necessary to implement all listener methods, including the listener methods of the parent interfaces of a listener. Basic runtime errors will occur whenever an event occurs and no corresponding Basic sub is found, especially with disposing(), because the broadcaster might be destroyed a long time after the Basic program was ran. In this situation, Basic shows a "Method not found" message. There is no indication of which method cannot be found or why Basic is looking for a method.

We are listening for events at the basic library container. Our simple implementation for events triggered by user actions in the Tools - Macro - Organizer dialog displays a message box with the corresponding listener method name and a message box with the Dbg_Properties of the event struct. For the disposing() method, the type of the event object is com.sun.star.lang.EventObject. All other methods belong to com.sun.star.container.XContainerListener, therefore the type of the event object is com.sun.star.container.ContainerEvent. This type is derived from com.sun.star.lang.EventObject and contains additional container related information.

If the event object is not needed, the parameter could be left out of the implementation. For example, the disposing() method could be:

    ' Minimal implementation of Sub disposing

    Sub ContListener_disposing

    End Sub

The event objects passed to the listener methods can be accessed like other struct objects. The following code shows an enhanced implementation of the elementRemoved() method that evaluates the com.sun.star.container.ContainerEvent to display the name of the module removed from Library1 and the module source code:

    sub ContListener_ElementRemoved( oEvent )

        MsgBox "Element " + oEvent.Accessor + " removed"

        MsgBox "Source =" + Chr$(13) + Chr$(13) + oEvent.Element

    End Sub

When the user removes Module1, the following message boxes are displayed by ContListener_ElementRemoved():

Screenshot of a ContListener_ElementRemoved event callback
Screenshot of a ContListener_ElementRemoved event callback
Illustration 1.23: ContListener_ElementRemoved Event Callback

When all necessary listener methods are implemented, add the listener to the broadcaster object by calling the appropriate add method. To register an XContainerListener, the corresponding registration method at our container is addContainerListener():

    Dim oLib

    oLib = BasicLibraries.Library1            ' Library1 must exist!

    oLib.addContainerListener( oListener )    ' Register the listener

Tip graphics marks a hint section in the text

The naming scheme XSomeEventListener <> addSomeEventListener() is used throughout the OpenOffice.org API.

The listener for container events is now registered permanently. When a container event occurs, the container calls the appropriate method of the com.sun.star.container.XContainerListener interface in our Basic code.

3.4.4    Automation Bridge

Introduction

The OpenOffice.org software supports Microsoft's Automation technology. This offers programmers the possibility to control the office from external programs. There is a range of efficient IDEs and tools available for developers to choose from.

Automation is language independent. The respective compilers or interpreters must, however, support Automation. The compilers transform the source code into Automation compatible computing instructions. For example, the string and array types of your language can be used without caring about their internal representation in Automation, which is BSTR and SAFEARRAY. A client program that controls OpenOffice.org can be represented by an executable (Visual Basic, C++) or a script (JScript, VB Script). The latter requires an additional program to run the scripts, such as Windows Scripting Host (WSH) or Internet Explorer.

UNO was not designed to be compatible with Automation and COM, although there are similarities. OpenOffice.org deploys a bridging mechanism provided by the Automation Bridge to make UNO and Automation work together. The bridge consists of UNO services, however, it is not necessary to have a special knowledge about them to write Automation clients for OpenOffice.org. For additional information, refer to (see 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - The Bridge Services).

Different languages have different capabilities. There are differences in the manner that the same task is handled, depending on the language used. Examples in Visual Basic, VB Script and JScript are provided. They will show when a language requires special handling or has a quality to be aware of. Although Automation is supposed to work across languages, there are subtleties that require a particular treatment by the bridge or a style of coding. For example, JScript does not know out parameters, therefore Array objects have to be used. Currently, the bridge has been tested with C++, JScript, VBScript and Visual Basic, although other languages can be used as well.

The name Automation Bridge implies the use of the Automation technology. Automation is part of the collection of technologies commonly referred to as ActiveX or OLE, therefore the term OLE Bridge is misleading and should be avoided. Also, the bridge only supports the COM interfaces IDispatch and IdispatchEX. Refer to 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Unsupported COM Features for the extent these interfaces are supported.

Requirements

The Automation technology can only be used with OpenOffice.org on a Windows platform (Windows 95, 98, NT4, ME, 2000, XP). There are COM implementations on Macintosh OS and UNIX, but there has been no effort to support Automation on these platforms.

Using Automation involves creating objects in a COM-like fashion, that is, using functions like CreateObject() in VB or CoCreateInstance() in C. This requires the OpenOffice.org automation objects to be registered with the Windows system registry. This registration is carried out whenever an office is installed on the system. If the registration did not take place, for example because the binaries were just copied to a certain location, then Automation clients will not work correctly or not at all. Refer to 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - The Service Manager Component for additional information.

A Quick Tour

The following example shows how to access OpenOffice.org functionality through Automation. Note the inline comments. The only automation specific call is WScript.CreateObject() in the first line, the remaining are OpenOffice.org API calls. The helper functions createStruct() and insertIntoCell() are shown at the end of the listing

'This is a VBScript example

'The service manager is always the starting point

'If there is no office running then an office is started up

Set objServiceManager= WScript.CreateObject("com.sun.star.ServiceManager")

'Create the CoreReflection service that is later used to create structs

Set objCoreReflection= objServiceManager.createInstance("com.sun.star.reflection.CoreReflection")

'Create the Desktop

Set objDesktop= objServiceManager.createInstance("com.sun.star.frame.Desktop")

'Open a new empty writer document

Dim args()

Set objDocument= objDesktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, args)

'Create a text object

Set objText= objDocument.getText

'Create a cursor object

Set objCursor= objText.createTextCursor

'Inserting some Text

objText.insertString objCursor, "The first line in the newly created text document." & vbLf, false

'Inserting a second line

objText.insertString objCursor, "Now we're in the second line", false

'Create instance of a text table with 4 columns and 4 rows

Set objTable= objDocument.createInstance( "com.sun.star.text.TextTable")

objTable.initialize 4, 4

'Insert the table

objText.insertTextContent objCursor, objTable, false

'Get first row

Set objRows= objTable.getRows

Set objRow= objRows.getByIndex( 0)

'Set the table background color

objTable.setPropertyValue "BackTransparent", false

objTable.setPropertyValue "BackColor", 13421823

'Set a different background color for the first row

objRow.setPropertyValue "BackTransparent", false

objRow.setPropertyValue "BackColor", 6710932

'Fill the first table row

insertIntoCell "A1","FirstColumn", objTable // insertIntoCell is a helper function, see below

insertIntoCell "B1","SecondColumn", objTable

insertIntoCell "C1","ThirdColumn", objTable

insertIntoCell "D1","SUM", objTable

objTable.getCellByName("A2").setValue 22.5

objTable.getCellByName("B2").setValue 5615.3

objTable.getCellByName("C2").setValue -2315.7

objTable.getCellByName("D2").setFormula"sum "

objTable.getCellByName("A3").setValue 21.5

objTable.getCellByName("B3").setValue 615.3

objTable.getCellByName("C3").setValue -315.7

objTable.getCellByName("D3").setFormula "sum "

objTable.getCellByName("A4").setValue 121.5

objTable.getCellByName("B4").setValue -615.3

objTable.getCellByName("C4").setValue 415.7

objTable.getCellByName("D4").setFormula "sum "

'Change the CharColor and add a Shadow

objCursor.setPropertyValue "CharColor", 255

objCursor.setPropertyValue "CharShadowed", true

'Create a paragraph break

'The second argument is a com::sun::star::text::ControlCharacter::PARAGRAPH_BREAK constant

objText.insertControlCharacter objCursor, 0 , false

'Inserting colored Text.

objText.insertString objCursor, " This is a colored Text - blue with shadow" & vbLf, false

'Create a paragraph break ( ControlCharacter::PARAGRAPH_BREAK).

objText.insertControlCharacter objCursor, 0, false

'Create a TextFrame.

Set objTextFrame= objDocument.createInstance("com.sun.star.text.TextFrame")

'Create a Size struct.

Set objSize= createStruct("com.sun.star.awt.Size") // helper function, see below

objSize.Width= 15000

objSize.Height= 400

objTextFrame.setSize( objSize)

' TextContentAnchorType.AS_CHARACTER = 1

objTextFrame.setPropertyValue "AnchorType", 1

'insert the frame

objText.insertTextContent objCursor, objTextFrame, false

'Get the text object of the frame

Set objFrameText= objTextFrame.getText

'Create a cursor object

Set objFrameTextCursor= objFrameText.createTextCursor

'Inserting some Text

objFrameText.insertString objFrameTextCursor, "The first line in the newly created text frame.", _

false

objFrameText.insertString objFrameTextCursor, _

vbLf & "With this second line the height of the frame raises.", false

'Create a paragraph break

'The second argument is a com::sun::star::text::ControlCharacter::PARAGRAPH_BREAK constant

objFrameText.insertControlCharacter objCursor, 0 , false

'Change the CharColor and add a Shadow

objCursor.setPropertyValue "CharColor", 65536

objCursor.setPropertyValue "CharShadowed", false

'Insert another string

objText.insertString objCursor, " That's all for now !!", false

On Error Resume Next

    If Err Then

    MsgBox "An error occurred"

End If

Sub insertIntoCell( strCellName, strText, objTable)

    Set objCellText= objTable.getCellByName( strCellName)

    Set objCellCursor= objCellText.createTextCursor

    objCellCursor.setPropertyValue "CharColor",16777215

    objCellText.insertString objCellCursor, strText, false

End Sub

Function createStruct( strTypeName)

    Set classSize= objCoreReflection.forName( strTypeName)

    Dim aStruct

    classSize.createObject aStruct

    Set createStruct= aStruct

End Function

This script created a new document and started the office, if necessary. The script also wrote text, created and populated a table, used different background and pen colors. Only one object is created as an ActiveX component called com.sun.star.ServiceManager. The service manager is then used to create additional objects which in turn provided other objects. All those objects provide functionality that can be used by invoking the appropriate functions and properties. A developer must learn which objects provide the desired functionality and how to obtain them. The chapter 2 First Steps introduces the main OpenOffice.org objects available to the programmer.

The Service Manager Component

Instantiation

The service manager is the starting point for all Automation clients. The service manager requires to be created before obtaining any UNO object. Since the service manager is a COM component, it has a CLSID and a programmatic identifier which is com.sun.star.ServiceManager. It is instantiated like any ActiveX component, depending on the language used:

//C++

IDispatch* pdispFactory= NULL;

CLSID clsFactory= {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}};

hr= CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pdispFactory);

In Visual C++, use classes which facilitate the usage of COM pointers. If you use the Active Template Library (ATL), then the following example looks like this:

CComPtr<IDispatch> spDisp;

if( SUCCEEDED( spDisp.CoCreateInstance("com.sun.star.ServiceManager")))

{

    // do something

}

JScript:

var objServiceManager= new ActiveXObject("com.sun.star.ServiceManager");

Visual Basic:

Dim objManager As Object

Set objManager= CreateObject("com.sun.star.ServiceManager")

VBScript with WSH:

Set objServiceManager= WScript.CreateObject("com.sun.star.ServiceManager")

JScript with WSH:

var objServiceManager= WScript.CreateObject("com.sun.star.ServiceManager");


The service manager can also be created remotely, that is. on a different machine, taking the security aspects into account. For example, set up launch and access rights for the service manager in the system registry (see “DCOM”).

The code for the service manager resides in the office executable soffice.exe. COM starts up the executible whenever a client tries to obtain the class factory for the service manager, so that the client can use it.

Registry Entries

For the instantiation to succeed, the service manager must be properly registered with the system registry. The keys and values shown in the tables below are all written during setup. It is not necessary to edit them to use the Automation capability of the office. Automation works immediately after installation. There are three different keys under HKEY_CLASSES_ROOT that have the following values and subkeys:

Key

Value

CLSID\{82154420-0FBF-11d4-8313-005004526AB4}

"StarOffice Service Manager (Ver 1.0)"

Sub Keys

LocalServer32

"<OfficePath>\program\soffice.exe”

NotInsertable

ProgIDcom.sun.star.ServiceManager.1

"com.sun.star.ServiceManager.1"

Programmable

VersionIndependentProgID

"com.sun.star.ServiceManager"

Key

Value

com.sun.star.ServiceManager

"StarOffice Service Manager"

Sub Keys

CLSID

"{82154420-0FBF-11d4-8313-005004526AB4}"

CurVer

"com.sun.star.ServiceManager.1"

Key

Value

com.sun.star.ServiceManager.1

"StarOffice Service Manager (Ver 1.0)"

Sub Keys

CLSID

"{82154420-0FBF-11d4-8313-005004526AB4}"

The value of the key CLSID\{82154420-0FBF-11d4-8313-005004526AB4}\LocalServer32 reflects the path of the office executable.

All keys have duplicates under HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.

The service manager is an ActiveX component, but does not support self-registration. That is, the office does not support the command line arguments -RegServer or -UnregServer.
The service manager, as well as all the objects that it creates and that originate from it indirectly as return values of function calls are proper automation objects. They can also be accessed remotely through DCOM.

From UNO Objects to Automation Objects

The service manager is based on the UNO service manager and similar to all other UNO components, is not compatible with Automation. The service manager can be accessed through the COM API, because the service manager is an Active X component contained in an executable that is the OpenOffice.org. When a client creates the service manager, for example by calling CreateObject(), and the office is not running, it is started up by the COM system. The office then creates a class factory for the service manager and registers it with COM. At that point, COM uses the factory to instantiate the service manager and return it to the client.

When the function IClassFactory::CreateInstance is called, the UNO service manager is converted into an Automation object. The actual conversion is carried out by the UNO service com.sun.star.bridge.OleBridgeSupplier2 (see 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - The Bridge Services). The resulting Automation object contains the UNO object and translates calls to IDispatch::Invoke into calls to the respective UNO interface function. The supplied function arguments, as well as the return values of the UNO function are converted according to the defined mappings (see 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings). Returned objects are converted into Automation objects, so that all objects obtained are always proper Automation objects.

Using UNO from Automation

With the IDL descriptions and documentation, start writing code that uses an interface. This requires knowledge about the programming language, especially how UNO interfaces can be accessed in that language and how function calls work.

In some languages, such as C++, the use of interfaces and their functions is simple, because the IDL descriptions map well with the respective C++ counterparts. For example, the syntax of functions are similar, and interfaces and out parameters can also be realized. The C++ language is not the best choice for Automation, because all interface calls have to use IDispatch, which is difficult to use in C++. In other languages, such as VB and Jscript, the IDispatch interface is hidden behind an object syntax that leads to shorter and more understandable code.

Different interfaces can have functions with the same name. There is no way to call a function which belongs to a particular interface, because interfaces can not be requested in Automation . If a UNO object provides two functions with the same name, it is undefined which function will be called. A solution for this issue is planned for the future.

Not all languages treat method parameters in the same manner, especially when it comes to input parameters that are reused as output parameters. From the perspective of a VB programmer an out parameter does not look different from an in parameter. However, to realize out parameters in Jscript, use an Array or Value Object that is a special construct provided by the Automation bridge. JScript does not support out parameters through calls by reference.

Calling Functions and Accessing Properties

The essence of Automation objects is the IDispatch interface. All function calls, including the access to properties, ultimately require a call to IDispatch::Invoke. When using C++, the use of IDispatch is rather cumbersome. For example, the following code calls createInstance("com.sun.star.reflection.CoreReflection"):

OLECHAR* funcName= L"createInstance";

DISPID id;

IDispatch* pdispFactory= NULL;

CLSID clsFactory= {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}};

HRESULT hr= CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pdispFactory);

if( SUCCEEDED(pdispFactory->GetIDsOfNames( IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &id)))

{

    VARIANT param1;

    VariantInit( &param1);

    param1.vt= VT_BSTR;

    param1.bstrVal=   SysAllocString( L"com.sun.star.reflection.CoreReflection");

    DISPPARAMS dispparams= { &param1, 0, 1, 0};

    VARIANT result;

    VariantInit( &result);

    hr= pdispFactory->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                        &dispparams, &result, NULL, 0);

}

First the COM ID for the method name createInstance() is retrieved from GetIdsOfNames, then the ID is used to invoke() the method createInstance().

Before calling a certain function on the IDispatch interface, get the DISPID by calling GetIDsOfNames. The DISPID s are generated by the bridge, as required. There is no fixed mapping from member names to DISPIDs, that is, the DISPID for the same function of a second instance of an object might be different. Once a DISPID is created for a function or property name, it remains the same during the lifetime of this object.

Helper classes can make it easier. The next example shows the same call realized with helper classes from the Active Template Library:

CComDispatchDriver spDisp(pdispFactory);

CComVariant param(L“com.sun.star.reflection.CoreReflection“);

CComVariant result;       

hr= spUnk.Invoke1(L“createInstance“,param, result);

Some frameworks allow the inclusion of COM type libraries that is an easier interface to Automation objects during development. These helpers cannot be used with UNO, because the SDK does not provide COM type libraries for UNO components. While COM offers various methods to invoke functions on COM objects, UNO supports IDispatch only.

Programming of Automation objects is simpler with VB or JScript, because the IDispatch interface is hidden and functions can be called directly. Also, there is no need to wrap the arguments into VARIANT s.

//VB

Dim objRefl As Object

Set objRefl= dispFactory.createInstance(“com.sun.star.reflection.CoreReflection”)

//JScript

var objRefl= dispFactory.createInstance(“com.sun.star.reflection.CoreReflection”);

Pairs of get/set functions following the pattern

SomeType getSomeProperty()

void setSomeProperty(SomeType aValue)

are handled as COM object properties.

Accessing such a property in C++ is similar to calling a method. First, obtain a DISPID, then call IDispatch::Invoke with the proper arguments.

    DISPID dwDispID;

    VARIANT value;

    VariantInit(&value);

    OLECHAR* name= L“AttrByte“;

    HRESULT hr = pDisp->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dwDispID);

    if (SUCCEEDED(hr))

    {

        // Get the property

        DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

        pDisp->Invoke(dwDispID, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,

            &dispparamsNoArgs, &value, NULL, NULL);

        // The VARIANT value contains the value of the property       

       

        // Sset the property

        VARIANT value2;

        VariantInit( value2);

        value2.vt= VT_UI1;

        value2.bval= 10;

        DISPPARAMS disparams;               

        dispparams.rgvarg = &value2;

        DISPID dispidPut = DISPID_PROPERTYPUT;

        dispparams.rgdispidNamedArgs = &dispidPut;

        pDisp->Invoke(dwDispID, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT,

            &dispparams, NULL, NULL, NULL);

    }

When the property is an IUnknown*,IDispatch*, or SAFEARRAY*, the flag DISPATCH_PROPERTYPUTREF must be used. This is also the case when a value is passed by reference (VARIANT.vt = VT_BYREF | ...).

The following example shows using the ATL helper it looks simple:

CComVariant prop;

CComDispatchDriver spDisp( pDisp);

// get the property

spDisp.GetPropertyByName(L“AttrByte“,&prop);

//set the property

CComVariant newVal( (BYTE) 10);

spDisp.PutPropertyByName(L“AttrByte“,&newVal);

The following example using VB and JScript it is simpler:

//VB

Dim prop As Byte

prop= obj.AttrByte

Dim newProp As Byte

newProp= 10

obj.AttrByte= newProp

'or

obj.AttrByte= 10

//JScript

var prop= obj.AttrByte;

obj.AttrByte= 10;

Service properties are not mapped to COM object properties. Use interfaces, such as com.sun.star.beans.XPropertySet to work with service properties.

Return Values

There are three possible ways to return values in UNO:

Return values are commonplace in most languages, whereas inout and out parameters are not necessarily supported. For example, in JScript.

To receive a return value in C++ provide a VARIANT argument to IDispatch::Invoke:

 //UNO IDL

long func();

//

 DISPPARAMS dispparams= { NULL, 0, 0, 0};

 VARIANT result;

 VariantInit( &result);

 hr= pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                          &dispparams, &result, NULL, 0);

The following example shows using VB and JScript this is simple:

//VB

Dim result As Long

result= obj.func

//JScript

var result= obj.func

When a function has inout parameters then provide arguments by reference in C++:

//UNO IDL

void func( [inout] long val);

//C++

long longOut= 10;

VARIANT var;

VariantInit(&var);

var.vt= VT_BYREF | VT_I4;

var.plVal= &longOut;

DISPPARAMS dispparams= { &var, 0, 1, 0};

hr= pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                &dispparams, NULL, NULL, 0);

//The value of longOut will be modified by UNO function.

The above VB code is written like this, because VB uses call by reference by default. After the call to func(), value contains the function output:

Dim value As Long

value= 10

obj.func value

The type of argument corresponds to the UNO type according to the default mapping, cf . 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings. If in doubt, use VARIANTs.

Dim value As Variant

value= 10;

obj.func value

However, there is one exception. If a function takes a character (char) as an argument and is called from VB, use an Integer, because there is no character type in VB. For convenience, the COM bridge also accepts a String as inout and out parameter:

//VB

Dim value As String

// string must contain only one character

value= "A"

Dim ret As String

obj.func value

JScript does not have inout or out parameters. As a workaround, the bridge accepts JScript Array objects. Index 0 contains the value.

// Jscript

var inout= new Array();

inout[0]=123;

obj.func( inout);

var value= inout[0];

Out parameters are similar to inout parameters in that the argument does not need to be initialized.

//C++

long longOut;

VARIANT var;

VariantInit(&var);

var.vt= VT_BYREF | VT_I4;

var.plVal= &longOut;

DISPPARAMS dispparams= { &var, 0, 1, 0};

hr= pdisp->Invoke( dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                &dispparams, NULL, NULL, 0);

//VB

Dim value As Long

obj.func value

//JScript

var out= new Array();

obj.func(out);

var value= out[0];

Usage of Types
Interfaces

Many UNO interface functions take interfaces as arguments. If this is the case, there are three possibilities to get an instance that supports the needed interface:

If createInstance() is called on the service manager or another UNO function that returns an interface, the returned object is wrapped, so that it appears to be a COM dispatch object. When it is passed into a call to a UNO function then the original UNO object is extracted from the wrapper and the bridge makes sure that the proper interface is passed to the function. If UNO objects are used, UNO interfaces do not have to be dealt with. Ensure that the object obtained from a call to a UNO object implements the proper interface before it is passed back into another UNO call.

Structs

Automation does not know about structs as they exist in other languages, for example, in C++. Instead, it uses Automation objects that contain a set of properties similar to the fields of a C++ struct. Setting or reading a member ultimately requires a call to IDispatch::Invoke. However in languages, such as VB, VBScript, and JScript, the interface call is obscured by the programming language. Accessing the properties is as easy as with C++ structs.

// VB. obj is an object that implements a UNO struct

obj.Width= 100

obj.Height= 100

Whenever a UNO function requires a struct as an argument, the struct must be obtained from the UNO environment. It is not possible to declare a struct. For example, assume there is an office function setSize() that takes a struct of type Size. The struct is declared as follows:

// UNO IDL

struct Size

{

    long Width;

    long Height;

}

// the interface function, that will be called from script

void XShape::setSize( Size aSize)

You cannot write code similar to the following example (VBScript):

Class Size

    Dim Width

    Dim Height

End Class

'obtain object that implements Xshape

'now set the size

call objXShape.setSize( new Size) // wrong

The com.sun.star.reflection.CoreReflection service or the Bridge_GetStruct function that is called on any UNO object can be used to create the struct. The following example uses the CoreReflection service

'VBScript in Windows Scripting Host

Set objServiceManager= Wscript.CreateObject("com.sun.star.ServiceManager")

'Create the CoreReflection service that is later used to create structs

Set objCoreReflection= objServiceManager.createInstance("com.sun.star.reflection.CoreReflection")

'get a type description class for Size

Set classSize= objCoreReflection.forName("com.sun.star.awt.Size")

'create the actual object

Dim aSize

classSize.createObject aSize

'use aSize

aSize.Width= 100

aSize.Height= 12

'pass the struct into the function

objXShape.setSize aSize

The next example shows how Bridge_GetStruct is used.

Set objServiceManager= Wscript.CreateObject("com.sun.star.ServiceManager")

Set aSize= objServiceManager.Bridge_GetStruct("com.sun.star.awt.Size")

'use aSize

aSize.Width= 100

aSize.Height= 12

objXShape.setSize aSize

The Bridge_GetStruct function can be called on any UNO object, as well as the service manager.

The corresponding C++ examples look complicated, but ultimately the same steps are necessary. The method forName() on the CoreReflection service is called and returns a com.sun.star.reflection.XIdlClass which can be asked to create an instance using createObject():

// create the service manager of OpenOffice

IDispatch* pdispFactory= NULL;

CLSID clsFactory= {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}};

hr= CoCreateInstance( clsFactory, NULL, CLSCTX_ALL, __uuidof(IDispatch), (void**)&pdispFactory);

// create the CoreReflection service

OLECHAR* funcName= L"createInstance";

DISPID id;

pdispFactory->GetIDsOfNames( IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &id);

VARIANT param1;

VariantInit( &param1);

param1.vt= VT_BSTR;

param1.bstrVal=   SysAllocString( L"com.sun.star.reflection.CoreReflection");

DISPPARAMS dispparams= { &param1, 0, 1, 0};

VARIANT result;

VariantInit( &result);

hr= pdispFactory->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                          &dispparams, &result, NULL, 0);

IDispatch* pdispCoreReflection= result.pdispVal;

pdispCoreReflection->AddRef();

VariantClear( &result);

// create the struct's idl class object

OLECHAR* strforName= L"forName";

hr= pdispCoreReflection->GetIDsOfNames( IID_NULL, &strforName, 1, LOCALE_USER_DEFAULT, &id);

VariantClear( &param1);

param1.vt= VT_BSTR;

param1.bstrVal= SysAllocString(L"com.sun.star.beans.PropertyValue");

hr= pdispCoreReflection->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT,

                                DISPATCH_METHOD, &dispparams, &result, NULL, 0);

IDispatch* pdispClass= result.pdispVal;

pdispClass->AddRef();

VariantClear( &result);

// create the struct

OLECHAR* strcreateObject= L"createObject";

hr= pdispClass->GetIDsOfNames( IID_NULL,&strcreateObject, 1, LOCALE_USER_DEFAULT, &id)

IDispatch* pdispPropertyValue= NULL;

VariantClear( &param1);

param1.vt= VT_DISPATCH | VT_BYREF;

param1.ppdispVal= &pdispPropertyValue;

hr= pdispClass->Invoke( id, IID_NULL, LOCALE_USER_DEFAULT,

                DISPATCH_METHOD, &dispparams, NULL, NULL, 0);

// do something with the struct pdispPropertyValue contained in dispparams

// ...

pdispPropertyValue->Release();

pdispClass->Release();

pdispCoreReflection->Release();

pdispFactory->Release();

The Bridge_GetStruct example.

// object be some UNO object in a  COM environment

OLECHAR* strstructFunc= L"Bridge_GetStruct";

hr= object->GetIDsOfNames( IID_NULL, &strstructFunc, 1, LOCALE_USER_DEFAULT, &id);

VariantClear(&result);

VariantClear( &param1);

param1.vt= VT_BSTR;

param1.bstrVal= SysAllocString(

L"com.sun.star.beans.PropertyValue");

hr= object->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD,

                        &dispparams, &result, NULL, 0);

IDispatch* pdispPropertyValue= result.pdispVal;

pdispPropertyValue->AddRef();

// do something with the struct pdispPropertyValue

...

JScript:

// struct creation via CoreReflection

var objServiceManager= new ActiveXObject("com.sun.star.ServiceManager");

var objCoreReflection= objServiceManager.createInstance("com.sun.star.reflection.CoreReflection");

var classSize= objCoreReflection.forName("com.sun.star.awt.Size");

var outParam= new Array();

classSize.createObject( outParam);

var size= outParam[0];

//use the struct

size.Width=111;

size.Height=112;

// ----------------------------------------------------

// struct creation by bridge function

var objServiceManager= new ActiveXObject("com.sun.star.ServiceManager");

var size= objServiceManager.Bridge_GetStruct("com.sun.star.awt.Size");

size.Width=111;

size.Height=112;

Type Mappings

Mapping of Simple Types

Whenever a UNO interface function requires a value of a simple type, such as float, double, byte, short, long or char, it is provided by declaring a variable of that type (or a constant or temporary variable) in the programming language used and passes it as an argument. This is the customary way of programming. UNO simple types are the same as Automation types and the bridge has a method of converting them.

In some languages, the set of available types does not match those of UNO types. For instance, in Visual Basic, a character can not be declared, and a string is used instead. This does not concern a VB programmer, but if C++ is used, then you would typically provide a short value ('A') which is totally different from the BSTR string that is used when you write "A" in VB.

Other examples for languages with a different set of simple types (compared to UNO) are the scripting languages VBScript and JScript. They are considered to be type-less languages, because they do not allow variables of specific types to be declared. At a basic level they use Automation types as well. They may not use the whole range (this is an implementation detail and might differ between scripting engines). For example, they use a double for every floating point value and a signed long for all integer values. This does not pose a problem, because the bridge converts those values into the expected floating point or integer types. The programmer has to be aware of this fact to prevent unexpected results caused by providing a value that exceeds the range of the expected UNO type. For example, if you pass an integer value of 65536 where the UNO type is a byte (-128 to 127), the converted value is different then the one provided. Also the conversion of double to float or vice versa often results in slightly different values.

Automatic Type Conversion

Every UNO object obtained directly or indirectly from the service manager is an Automation object that contains the actual UNO object. The wrapper contains code that implements the IDispatch interface. During a call to IDispatch::Invoke, the wrapper-code converts the arguments to UNO values, and the respective UNO function of the contained object is called with those values as arguments.

The IDispatch interface reveals that all arguments and return values are actually VARIANTs. This is similar to the XInvocation interface where arguments and return values are of the any type. Both types carry values of a specific type. A VARIANT can contain all Automation types and an any can contain all UNO types. Therefore, it would be suitable to say that VARIANT values are converted into any values and vice versa. The contained values still have to be converted. When working with VARIANT s and anys, extract the contained values for further processing. Before calling those interfaces, put the values into VARIANT s or anys. This process is sometimes hidden by the programming language you use. For example:

//UNO IDL

string func([in] long value);

//VB

Dim value As Long

value= 100

Dim ret As String

ret= obj.func( value)

Since VARIANT and any are helper types that allow writing code when the specific types are not yet known, we need to focus on the mapping of the specific types. The VARIANT s or any s are only mentioned if there is reason to look beyond the mapping of the contained types.

The bridge converts arguments according to well-defined mappings. The default mappings are sufficient in most cases. The bridge also accepts arguments for flexibility whose types do not exactly match the default mappings, but are similar enough to be converted (3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings). In some situations, it may be necessary for an Automation client to specify how an argument should be treated. This can be the case in scripting languages, where the language does not provide specific types. Refer to 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings. When Automation objects are used from UNO, there is no construct as a Value Object. The bridge always uses the default mappings.

Default Mappings from Automation Types to UNO

This mapping applies in two situations. First, whenever you call a UNO function from an Automation environment, for example, from VB, the arguments flagged as in or inout parameters in the corresponding UNO IDL description are converted according to the following default mappings.
Second, when Automation objects are called from a UNO environment and return a value, the return values are converted to the corresponding UNO types.

Automation IDL Types (source)

UNO IDL Types
(target)

boolean

boolean

unsigned char

byte

double

double

float

float

short

short

long

long

BSTR

string

short

char

long

enum

IDispatch*

The IDispatch* is mapped to the expected interface if it is actually a UNO object implementing that interface, or an Automation implementation of that interface (see 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Usage of Types).
If the IDispatch* is a return value or out parameter, then it is mapped to XInvocation.
If the Automation object is a struct, UNO receives a struct (see 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Usage of Types).

SAFEARRAY or
IDispatch* in JScript

sequence< type >. A two-dimensional SAFEARRAY is converted to sequence< sequence< type >>, a three-dimensional SAFEARRAY is converted to sequence<sequence<sequence<type>>>, and so forth. In JScript one would provide an Array object that contains other Array objects.

Default Mappings from UNO Types to Automation

If an Automation client calls a function on a UNO object and the function returns values such as out parameters or the return value, then the mapping from UNO to Automation types applies. For example:

//UNO IDL

long func([out] long value);

//call from VB

Dim value As Long

Dim ret As Long

ret= obj.func(value)

The returned value is a UNO long that is converted into an Automation long, which fits the declaration of the variable ret. When the UNO function returns, then the bridge converts the out parameter according to the mapping and writes the value back into the variable value .

Note graphics marks a special text section

In some situations, the client code performs a conversion on its own (this behavior is covered in “Client-Side Conversions”).

This mapping is also used when you pass arguments to functions on an Automation object from a UNO environment.

UNO IDL Types
(source)

Automation IDL Types
(target)

boolean

boolean

char

short

byte

unsigned byte

double

double

float

float

short, unsigned short

short

long, unsigned long

long

string

BSTR

interface,struct

IDispatch*

sequence

SAFEARRAY(VARIANT)

If a UNO function returns interfaces or structs, they are converted into Automation objects. For example:

//UNO IDL

void func([out]com.sun.star.lang.XEventListener aInterface, [out]com.sun.star.lang.EventObject aStruct);

//VB

Dim objEventListener As Object

Dim objStruct As Object

func objEventListener, objStruct

A sequence returned by a UNO function is converted into a SAFEARRAY that contains VARIANTs. If a sequence contains nested sequences, the VARIANT s contain SAFEARRAY s. The OleObjectFactory creates Automation objects and provides an com.sun.star.script.XInvocation interface which can be used from the UNO environment. These objects might expect multi-dimensional SAFEARRAY s as arguments. In this is the case, provide an appropriate sequence, for example sequence<sequence<long>> for a two-dimensional array of longs. The contained sequences should have the same length, otherwise the bridge uses the longest sequence to stipulate the size of the respective dimension. If a sequence is shorter, then the remaining values are filled with null values.

For example, assume a sequence with two elements that are sequences of ten elements. The elements of the two sequences are long types and the first sequence could be mapped to an array, which is expressed in C (for convenience):

long ar[2][10];

Further assume that the second of the two contained sequences only contains five elements. The C array would still look the same. With the difference, that ar[0][0] through ar[0][4] contain the elements of that sequence, and ar[0][4] through ar[0][9] contain null values.

Sequences are mapped to SAFEARRAY s and not C arrays.

Conversion Mappings

As shown in the previous section, Automation types have a UNO counterpart according to the mapping tables. If a UNO function expects a particular type as an argument, then supply the corresponding Automation type. This is not always necessary as the bridge also accepts similar types. For example:

//UNO IDL

void func( long value);

// VB

Dim value As Byte

value = 2

obj.func valLong

The following table shows the various Automation types, and how they are converted to UNO IDL types if the expected UNO IDL type has not been passed.

Automation IDL Types
(source)

UNO IDL Types (target)

boolean (true, false)
unsigned char, short, long, float, double: 0 = false, > 0 = true
string: "true" = true, "false" = false

boolean

boolean, unsigned char, short, long, float, double, string

byte

double, boolean, unsigned char, short, long, float, string

double

float, boolean, unsigned char, short, string

float

short, unsigned char, long, float, double, string

short

long, unsigned char, long, float, double, string

long

BSTR, boolean, unsigned char, short, long, float, double

string

short, boolean, unsigned char, long, float, double, string (1 character long)

char

long, boolean, unsigned char, short, float, double, string

enum

When you use a string for a numeric value, it must contain an appropriate string representation of that value.

Floating point values are rounded if they are used for integer values.

Be careful using types that have a greater value space than the UNO type. Do not provide an argument that exceeds the value space which would result in an error. For example:

// UNO IDL

void func([in] byte value);

// VB

Dim value as Integer

value= 1000

obj.func value  'causes an error

The conversion mappings only work with in parameters, that is, during calls from an Automation environment to a UNO function, as far as the UNO function takes in parameters.

Client-Side Conversions

The UNO IDL description and the defined mappings indicate what to expect as a return value when a particular UNO function is called. However, the language used might apply yet another conversion after a value came over the bridge.

// UNO IDL

float func();

// VB

Dim ret As Single

ret= obj.func() 'no conversion by VB

Dim ret2 As String

ret2= obj.func() 'VB converts float to string

When the function returns, VB converts the float value into a string and assigns it to ret2. Such a conversion comes in useful when functions return a character, and a string is preferred instead of a VB Integer value.

// UNO IDL

char func();

// VB

Dim ret As String

ret= obj.func()        'VB converts the returned short into a string       

Be aware of the different value spaces if taking advantage of these conversions. That is, if the value space of a variable that receives a return value is smaller than the UNO type, a runtime error might occur if the value does not fit into the provided variable. Refer to the documentation of your language for client-side conversions.

Client-side conversions only work with return values and not with out or inout parameters. The current bridge implementation is unable to transport an out or inout parameter back to Automation if it does not have the expected type according to the default mapping.

Another kind of conversion is done implicitly. The user has no influence on the kind of conversion. For example, the scripting engine used with the Windows Scripting Host or Internet Explorer uses double values for all floating point values. Therefore, when a UNO function returns a float value, then it is converted into a double which may cause a slightly different value. For example:

// UNO IDL

float func();  //returns 3.14

// JScript

var ret= obj.func(); // implicit conversion from float to double, ret= 3.14000010490417

Mapping of Any

The any type is similar to VARIANT in COM. That is, it can contain values of different types. Do not put a value into a VARIANT if a function argument needs to be an any.

// UNO IDL

interface XSomething: XInterface

{

    void func([in] any value);

};

// Visual Basic

Dim param As Long

param= 10

// obj is the object that implements XSomething

obj.func param

In C++, set the value directly in the VARIANT that is put into the DISPPARAMS.rgvarg array. That is, there is no need to provide a VARIANT with the type VT_VARIANT | VT_BYREF.

Although an any can contain all possible UNO types, an any argument must contain a certain type. An example is the com.sun.star.beans.XPropertySet interface with its function:

// UNO IDL

void setPropertyValue( [in] string aPropertyName,

         [in] any aValue ) raises ...

As the name suggests, the function is used to set a value for a particular property. Usually the properties have a distinct type and are not anys. Lets assume that there is a property PropA of type float. Then a Single in VB or a float in C++ has to be provided. In JScript or VBScript, the scripting engine will probably pass a double to the function which would not be converted by the bridge. That is, setPropertyValue() would receive an any containing a double. If the programmer of the XPropertySet implementation was not careful converting the any into the type that is expected then the code will throw an exception. There is no rule about how tolerant the implementation has to be. The bridge does not know that the property is a float and hence it needs to be told. This it is done by providing a Value Object as argument. A Value Object is an Automation object that is provided by the bridge. It carries a value and the name of the type that it is supposed to be. For example:

// VBScript with Windows Scripting Host (WSH)

Set objServiceManager= WScript.CreateObject("com.sun.star.ServiceManager")
Set aFloat= objServiceManager.Bridge_GetValueObject()
aFloat.Set "float", 3.14

// obj is the implementation of XSomething

obj.setPropertyValue " PropA " , aFloat

Value Object s are covered in depth in chapter 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings.

In JScript, an any argument can cause problems when the client provides an array or object as a parameter, because the Array object is used for arrays. The bridge receives an IDispatch pointer in both cases, because it is an object. To make a decision about what conversion is applied, the bridge obtains type information about the function that is to be called and proceeds accordingly:

// UNO IDL

void func( XSomething obj);

void func2( sequence<long> ar);

The bridge has to convert an IDispatch in both cases.,The type information says exactly what IDispatch* supports: It must be an interface for func() , but a sequence for func2().

// UNO IDL

void func( any val);

// JScript

// obj is an automation object

func( obj);

The bridge obtains type information in the above example, but it only knows that the argument is of type any. The bridge does not know whether to convert IDispatch into an interface or a sequence. Since the conversions are different, a wrong decision will cause an error when the converted object is accessed later. In this situation, the bridge assumes that the object is an array if it has a property named 0. This will work, because an interface has a get0() or set0() function. If not, use a Value Object for arrays and objects which could contain a 0 property.

Mapping of String

A string is a data structure that is common in programming languages. Although the idea of a string is the same, the implementations and their creation can be quite different. For example, a C++ programmer has a range of possibilities to choose from (for example, char*, char[ ], wchar_t*, wchar_t[ ], std::string, CString, BSTR), where a JScript programmer only knows one kind of string. To use Automation across languages, it is necessary to use a string type that is common to all those languages that has the same binary representation. This particular string is declared as BSTR in COM. The name can be different depending on the language. For example, in C++ there is a BSTR type, in VB it is called String and in JScript every string defined is a BSTR. Refer to the documentation covering the BSTR's equivalent if using an Automation capable language not covered by this document.

Mapping of Sequence

The discussion about strings applies for arrays as well. The difference is the array type used by Automation is named SAFEARRAY in COM. The SAFEARRAY array is to be used when a UNO function takes a sequence as an argument. To create a SAFEARRAY in C++, use Windows API functions. The C++ name is also SAFEARRAY, but in other languages it might be named different. In VB for example, the type does not even exist, because it is mapped to an ordinary VB array:

Dim myarr(9) as String

JScript is different. It does not have a method to create a SAFEARRAY. Instead, JScript features an Array object that can be used as a common array in terms of indexing and accessing its values. It is represented by a dispatch object internally. JScript offers a VBArray object that converts a SAFEARRAY into an Array object. Therefore, it is possible to call functions on Automation objects which return SAFEARRAYs.

When a SAFEARRY is provided and a function is expecting a UNO sequence, the bridge accepts JScript Array objects and converts them into a UNO sequence.

Tip graphics marks a hint section in the text

If a SAFEARRAY is obtained in JScript as a result of a call to an ActiveX component or a VB Script function (for example, the Internet Explorer allows JScript and VBS code on the same page), then it can also be used as an argument of a UNO function without converting it to an Array object.

If a UNO function returns a sequence, a SAFEARRAY is returned in JScript. Use the VBArray object to convert the SAFEARRAY into a JScript Array to process the array.

Value Objects

Since the Automation bridge supports JScript, it has to deal with ambiguities when it comes to the conversion of arguments. Arguments, which are dispatch objects, can represent three different kinds of values: objects, arrays or out/inout parameters. To solve this problem, the bridge obtains type information about the UNO function that receives the argument. The bridge must always get type information if an argument is an object, because the bridge does not know the language that is being used. In a remote environment, this causes additional network roundtrips and slows down overall performance. With a Value Object, programmers can provide some type information and save the bridge from obtaining it.

When a UNO interface function takes an any as an argument and the bridge receives an object, then it be mistaken for a JScript array. This is described in paragraph 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings. With a Value Object, the bridge knows exactly what the object stands for and converts it correctly.

A Value Object is an Automation object. It offers functions for setting and getting a value, and determines if it represents an inout or out parameter. The client can use Value Object s for every possible argument of a UNO function.

A Value Object exposes four functions that can be accessed through IDispatch. These are:

void Set( [in]VARIANT type, [in]VARIANT value);

Assigns a type and a value.

void Get( [out,retval] VARIANT* val);

Returns the value contained in the object. Get is used when the Value Object was used as inout or out parameter.

void InitOutParam();

Tells the object that it is used as out parameter.

void InitInOutParam( [in]VARIANT type, [in]VARIANT value);

Tells the object that it is used as inout parameter and passes the value for the in parameter, as well as the type.

When the Value Object is used as in or inout parameter then specify the type of the value. The names of types correspond to the names used in UNO IDL, except for the “object” name. The following table shows what types can be specified.

Name (used with Value Object)

UNO IDL

char

char

boolean

boolean

byte

byte

unsigned

unsigned byte

short

short

unsigned short

unsigned short

long

long

unsigned long

unsigned long

string

string

float

float

double

double

any

any

object

some UNO interface

To show that the value is a sequence, put brackets before the names, for example:

[]char - sequence<char>

[][]char - sequence < sequence <char > >

[][][]char - sequence < sequence < sequence < char > > >

The Value Objects are provided by the bridge and can be obtained from UNO objects. Call the function Bridge_GetValueObject:

// object is some UNO wrapper object

var valueObject= object.Bridge_GetValueObject();

To use a Value Object as in parameter, specify the type and pass the value to the object:

// UNO IDL

void doSomething( [in] sequence< short > ar);

// JScript

var value= object.Bridge_GetValueObject();

var array= new Array(1,2,3);

value.Set("[]short",array);

object.doSomething( value);

In the previous example, the Value Object was defined to be a sequence of short values. The array could also contain Value Objects again:

var value1= object.Bridge_GetValueObject();

var value2= object.Bridge_GetValueObject();

value1.Set("short“, 100);

value2.Set("short", 111);

var array= new Array();

array[0]= value1;

array[1]= value2;

var allValue= object.Bridge_GetValueObject();

allValue.Set("[]short“, array);

object.doSomething( allValue);

If a function takes an out parameter, tell the Value Object like this:

// UNO IDL

void doSomething( [out] long);

// JScript

var value= object.Bridge_GetValueObject();

value.InitOutParam();

object.doSomething( value);

var out= value.Get();

When the Value Object is an inout parameter, it needs to know the type and value as well:

//UNO IDL

void doSomething( [inout] long);

//JScript

var value= object.Bridge_GetValueObject();

value.InitInOutParam("long", 123);

object.doSomething(value);

var out= value.Get();

Exceptions and Errorcodes

UNO interface functions may throw exceptions to communicate an error. Automation objects provide a different error mechanism. First, the IDispatch interface describes a number of error codes (HRESULTs) that are returned under certain conditions. Second, the Invoke function takes an argument that can be used by the object to provide descriptive error information. The argument is a structure of type EXCEPINFO and is used by the bridge to convey exceptions being thrown by the called UNO interface function. In case of an exception, the bridge fills in the following values:

EXCEPINFO::wCode = 1001

EXCEPINFO::bstrSource = “any ONE component”

EXCEPINFO::bstrDescription = type name of the exceptions

If the caller does not provide an EXCEPINFO argument, then Invoke returns a DISP_E_EXCEPTION as HRESULT.

As already stated, the functions of IDispatch return error codes. The reasons for those codes are shown in the following tables.

Possible HRESULT return values of IDispatch::Invoke are:

HRESULT

Reason

DISP_E_EXCEPTION

  • UNO interface function or property access function threw an exception and the caller did not provide an EXCEPINFO argument.

  • Bridge error. A ValueObject could not be created when the client called Bridge_GetValueObject.

  • Bridge error. A struct could not be created when the client called Bridge_GetStruct

  • Bridge error. The automation object contains a UNO object that does not support the XInvocation interface. Could be a failure of com.sun.star.script.Invocation service.

  • In JScript was an Array object passed as inout param and the bridge could not retrieve the property “0”.

  • A conversion of a VARIANTARG (DISPPARAMS structure) failed for some reason.

  • Parameter count does not tally with the count provided by UNO type information (only when one DISPPARAMS contains VT_DISPATCH). This is a bug. DISP_E_BADPARAMCOUNT should be returned.

DISP_E_NONAMEDARGS

  • The caller provided “named arguments” for a call to a UNO function.

DISP_E_BADVARTYPE

  • Conversion of VARIANTARGs failed.

  • Bridge error: Caller provided a ValueObject and the attempt to retrieve the value failed. This is possibly a bug. DISP_E_EXCEPTION should be returned.

  • A member with the current name does not exist according to type information. This is a bug. DISP_E_MEMBERNOTFOUND should be returned.

DISP_E_BADPARAMCOUNT

  • A property was assigned a value and the caller provided null or more than one arguments.

  • The caller did not provide the number of arguments as required by the UNO interface function.

DISP_E_MEMBERNOTFOUND

  • Invoke was called with a DISPID that was not issued by GetIDsOfName (OleBridgeSupplier2)

  • There is no interface function (also property access function) with the name for which Invoke is currently being called.

DISP_E_TYPEMISMATCH

The called provided an argument of a false type.

DISP_E_OVERFLOW

An argument could not be coerced to the expected type.
Internal call to XInvocation::invoke resulted in a CannotConvertException being thrown. The field reason has the value OUT_OF_RANGE which means that a given value did not fit in the range of the destination type.

E_UNEXPECTED

[2]results from com.sun.star.script.CannotConvertException of XInvocation::invoke with FailReason::UNKNOWN.
Internal call to XInvocation::invoke resulted in a com.sun.star.script.CannotConvertException being thrown. The field reason has the value UNKNOWN , which signifies some unknown error condition.

E_POINTER

Bridge_GetValueObject or Bridge_GetStruct called and no argument for return value provided.

S_OK

Ok.

Return values of IDispatch::GetIDsOfNames:

HRESULT

Reason

E_POINTER

Caller provided no argument that receives the DISPID.

DISP_E_UNKNOWNNAME

There is no function or property with the given name.

OleBridgeSupplierVar1: The name has been determined not to exist by a previous call to IDispatch::Invoke

S_OK

Ok.

The functions IDispatch::GetTypeInfo and GetTypeInfoCount return E_NOTIMPL.

When a call from UNO to an Automation object (OleObjectFactory) is performed, then the following HRESULT values are converted to exceptions. Keep in mind that it is determined what exceptions the functions of XInvocation are allowed to throw.

Exceptions thrown by XInvocation::invoke() and their HRESULT counterparts:

 HRESULT

Exception

DISP_E_BADPARAMCOUNT

com.sun.star.lang.IllegalArgumentException

DISP_E_BADVARTYPE

com.sun.star.uno.RuntimeException

DISP_E_EXCEPTION

com.sun.star.reflection.InvocationTargetException

DISP_E_MEMBERNOTFOUND

com.sun.star.lang.IllegalArgumentException

DISP_E_NONAMEDARGS

com.sun.star.lang.IllegalArgumentException

DISP_E_OVERFLOW

com.sun.star.script.CannotConvertException, reason= FailReason::OUT_OF_RANGE

DISP_E_PARAMNOTFOUND

com.sun.star.lang.IllegalArgumentException

DISP_E_TYPEMISMATCH

com.sun.star.script.CannotConvertException, reason= FailReason::UNKNOWN

DISP_E_UNKNOWNINTERFACE

com.sun.star.uno.RuntimeException

DISP_E_UNKNOWNLCID

com.sun.star.uno.RuntimeException

DISP_E_PARAMNOTOPTIONAL

com.sun.star.script.CannotConvertException, reason= FailReason::NO_DEFAULT_AVAILABLE

XInvocation::setValue() throws the same as invoke() except for:       

HRESULT

Exception

DISP_E_BADPARAMCOUNT

com.sun.star.uno.RuntimeException

DISP_E_MEMBERNOTFOUND

com.sun.star.beans.UnknownPropertyException

DISP_E_NONAMEDARGS

com.sun.star.uno.RuntimeException

XInvocation::getValue() throws the same as invoke() except for:

HRESULT

Exception

DISP_E_BADPARAMCOUNT

com.sun.star.uno.RuntimeException

DISP_E_EXCEPTION

com.sun.star.uno.RuntimeException

DISP_E_MEMBERNOTFOUND

com.sun.star.beans.UnknownPropertyException

DISP_E_NONAMEDARGS

com.sun.star.uno.RuntimeException

DISP_E_OVERFLOW

com.sun.star.uno.RuntimeException

DISP_E_PARAMNOTFOUND

com.sun.star.uno.RuntimeException

DISP_E_TYPEMISMATCH

com.sun.star.uno.RuntimeException

DISP_E_PARAMNOTOPTIONAL

com.sun.star.uno.RuntimeException

Automation Objects with UNO Interfaces

It is common that UNO functions take interfaces as arguments. As discussed in section 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Usage of Types, those objects are usually obtained as return values of UNO functions. With the Automation bridge, it is possible to implement those objects even as Automation objects and use them as arguments, just like UNO objects.

Although Automation objects can act as UNO objects, they are still not fully functional UNO components. That is, they cannot be created by means of the service manager.

Note graphics marks a special text section

However, that feature may be implemented in the future. The factories and how they map to COM class factories will have to be considered. Also a loader is needed (components from different environments have different loaders). The loader, however, could make use of the OleObjectFactory service.

One use case for such objects are listeners. For example, if a client wants to know when a writer document is being closed, it can register the listener object with the document, so that it will be notified when the document is closing.

Requirements

Automation objects implement the IDispatch interface, and all function calls and property operations go through this interface. We imply that all interface functions are accessed through the dispatch interface when there is mention of an Automation object implementing UNO interfaces. That is, the Automation object still implements IDispatch only.

Basically, all UNO interfaces can be implemented as long as the data types used with the functions can be mapped to Automation types. The bridge needs to know what UNO interfaces are supported by an Automation object, so that it can create a UNO object that implements all those interfaces. This is done by requiring the Automation objects to support the property Bridge_implementedInterfaces, which is an array of strings. Each of the strings is a fully qualified name of an implemented interface. If an Automation object only implements one UNO interface, then it does not need to support that property.

Note graphics marks a special text section

You never implement com.sun.star.script.XInvocation and com.sun.star.uno.XInterface. XInvocation cannot be implemented, because the bridge already maps IDispatch to XInvocation internally. Imagine a function that takes an XInvocation:

// UNO IDL

void func( [in] com.sun.star.script.XInvocation obj);

In this case, use any Automation object as argument. When an interface has this function,

void func( [in] com.sun.star.XSomething obj)

the automation object must implement the functions of XSomething, so that they can be called through IDispatch::Invoke.

Examples

The following example shows how a UNO interface is implemented in VB. It is about a listener that gets notified when a writer document is being closed.

To rebuild the project use the wizard for an ActiveX dll and put this code in the class module. The component implements the com.sun.star.lang.XEventListener interface.

Option Explicit

Private interfaces(0) As String

Public Property Get Bridge_ImplementedInterfaces() As Variant

    Bridge_ImplementedInterfaces = interfaces

End Property

Private Sub Class_Initialize()

interfaces(0) = "com.sun.star.lang.XEventListener"

End Sub

Private Sub Class_Terminate()

    On Error Resume Next

    Debug.Print "Terminate VBEventListener"

End Sub

Public Sub disposing(ByVal source As Object)

    MsgBox "disposing called"

End Sub

You can use these components in VB like this:

Dim objServiceManager As Object

Dim objDesktop As Object

Dim objDocument As Object

Dim objEventListener As Object

Set objServiceManager= CreateObject("com.sun.star.ServiceManager")

Set objDesktop= objServiceManager.createInstance("com.sun.star.frame.Desktop")

'Open a new empty writer document

Dim args()

Set objDocument= objDesktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, args)

'create the event listener ActiveX component

Set objEventListener= CreateObject("VBasicEventListener.VBEventListener")

'register the listener with the document

objDocument.addEventListener objEventlistener

The next example shows a JScript implementation of a UNO interface and its usage from JScript. To use JScript with UNO, a method had to be determined to realize arrays and out parameters. Presently, if a UNO object makes a call to a JScript object, the bridge must be aware that it has to convert arguments according to the JScript requirements. Therefore, the bridge must know that one calls a JScript component, but the bridge is not capable of finding out what language was used. The programmer has to provide hints, by implementing a property with the name “_environment”that has the value "JScript".

// UNO IDL: the interface to be implemented

interface XSimple : public com.sun.star.uno.XInterface

{

    void func1( [in] long val, [out] long outVal);

    long func2( [in] sequence< long > val, [out] sequence< long > outVal);

    void func3( [inout]long);

};

// JScript: implementation of XSimple

function XSimplImpl()

{

    this._environment= "JScript";

    this.Bridge_implementedInterfaces= new Array( "XSimple");

   

    // the interface functions

    this.func1= func1_impl;

    this.func2= func2_impl;

    this.func3= func3_impl;

}

function func1_impl( inval, outval)

{

    //outval is an array

    outval[0]= 10;

    ...

}

function func2_impl(inArray, outArray)

{

    outArray[0]= inArray;

    // or

    outArray[0]= new Array(1,2,3);

    return 10;

}

function func3_impl(inoutval)

{

    var val= inoutval[0];

    inoutval[0]= val+1;

}

Assume there is a UNO object that implements the following interface function:

//UNO IDL

void doSomething( [in] XSimple);

Now, call this function in JScript and provide a JScript implementation of XSimple:

<script language="JScript">

 

var factory= new ActiveXObject("com.sun.star.ServiceManager");

// create the UNO component that implements an interface with the doSomething function

var oletest= factory.createInstance("oletest.OleTest");

oletest.doSomething( new XSimpleImpl());

...

To build a component with C++, write the component from scratch or use a kind of framework, such as the Active Template Library (ATL). When a dual interface is used with ATL, the implementation of IDispatch is completely hidden and the functions must be implemented as if they were an ordinary custom interface, that is, use specific types as arguments instead of VARIANTs. If a UNO function has a return value, then it has to be specified as the first argument which is flagged as “retval”.

</script>

// UNO IDL

interface XSimple : public com.sun.star.uno.XInterface

{

    void func1( [in] long val, [out] long outVal);

    long func2( [in] sequence< long > val, [out] sequence< long > outVal);

};

//IDL of ATL component

[

    object,

    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),

    dual,

    helpstring("ISimple Interface"),

    pointer_default(unique)

]

interface ISimple : IDispatch

{

    [id(1), helpstring("method func1")]

                HRESULT func1( [in] long val, [out] long* outVal);

    [id(2), helpstring("method func2")]

                HRESULT func2([out,retval] long ret, [in] SAFEARRAY(VARIANT) val,

                        [out] SAFEARRAY(VARIANT) * outVal);

    [propget, id(4), helpstring("property_implementedInterfaces")]

        HRESULT Bridge_implementedInterfaces([out, retval] SAFEARRAY(BSTR) *pVal);

};

DCOM

The Automation bridge maps all UNO objects to automation objects. That is, all those objects implement the IDispatch interface. To access a remote interface, the client and server must be able to marshal that interface. The marshaling for IDispatch is already provided by Windows, therefore all objects which originate from the bridge can be used remotely.

To make DCOM work, apply proper security settings for client and server. This can be done by setting the appropriate registry entries or programmatically by calling functions of the security API within the programs. The office does not deal with the security, hence the security settings can only be determined by the registry settings which are not completely set by the office's setup. The AppID key under which the security settings are recorded is not set. This poses no problem because the dcomcnfg.exe configuration tools sets it automatically.

To access the service manager remotely, the client must have launch and access permission. Those permissions appear as sub-keys of the AppID and have binary values. The values can be edited with dcomcnfg. Also the identity of the service manager must be set to “Interactive User”. When the office is started as a result of a remote activation of the service manager, it runs under the account of the currently logged-on user (the interactive user).

In case of callbacks (office calls into the client), the client must adjust its security settings so that incoming calls from the office are accepted. This happens when listener objects that are implemented as Automation objects (not UNO components) are passed as parameters to UNO objects, which in turn calls on those objects. Callbacks can also originate from the automation bridge, for example, when JScript Array objects are used. Then, the bridge modifies the Array object by its IDispatchEx interface. To get the interface, the bridge has to call QueryInterface with a call back to the client.

To avoid these callbacks, VBArray objects and Value Objects could be used.

To set security properties on a client, use the security API within a client program or make use of dcomcnfg again. The API can be difficult to use. Modifying the registry is the easiest method, simplified by dcomcnfg. This also adds more flexibility, because administrators can easily change the settings without editing source code and rebuilding the client. However, dcomcnfg only works with COM servers and not with ordinary executables. To use dcomcnfg, put the client code into a server that can be registered on the client machine. This not only works with exe servers, but also with in-process servers, namely dlls. Those can have an AppID entry when they are remote, that is, they have the DllSurrogate subkey set. To activate them an additional executable which instantiates the in-process server is required. At the first call on an interface of the server DCOM initializes security by using the values from the registry, but it only works if the executable has not called CoInitializeSecurity beforehand.

To run JScript or VBScript programs, an additional program, a script controller that runs the script is required, for example, the Windows Scripting Host (WSH). The problem with these controllers is that they might impose their own security settings by calling CoInitializeSecurity on their own behalf. In that case, the security settings that were previously set for the controller in the registry are not being used. Also, the controller does not have to be configurable by dcomcnfg, because it might not be a COM server. This is the case with WSH (not WSH remote).

To overcome these restrictions write a script controller that applies the security settings before a scripting engine has been created. This is time consuming and requires some knowledge about the engine, along with good programming skills. The Windows Script Components (WSC) is easier to use. A WSC is made of a file that contains XML, and existing JScript and VBS scripts can be put into the respective XML Element. A wizard generates it for you. The WSC must be registered, which can be done with regsvr32.exe or directly through the context menu in the file explorer. To have an AppID entry, declare the component as remotely accessible. This is done by inserting the remotable attribute into the registration element in the wsc file:

<registration

    description="writerdemo script component"

    progid="dcomtest.writerdemo.WSC”

    version="1.00"

    classid="{90c5ca1a-5e38-4c6d-9634-b0c740c569ad}"

    remotable="true" >

When the WSC is registered, there will be an appropriate AppID key in the registry. Use dcomcnfg to apply the desired security settings on this component. To run the script. An executable is required. For example:

Option Explicit

Sub main()

    Dim obj As Object

    Set obj = CreateObject("dcomtest.writerdemo.wsc”)

    obj.run

End Sub

In this example, the script code is contained in the run function. This is how the wsc file appears:

<?xml version="1.0"?>

<component>

<?component error="true" debug="true"?>

<registration

    description="writerdemo script component"

    progid="dcomtest.writerdemo.WSC”

    version="1.00"

    classid="{90c5ca1a-5e38-4c6d-9634-b0c740c569ad}"

    remotable="true">

</registration>

<public>

    <method name="run">

    </method>

</public>

<script language="JScript">

<![CDATA[

var description = new jscripttest;

function jscripttest()

{

    this.run = run;

}

function run()

{

var objServiceManager= new ActiveXObject("com.sun.star.ServiceManager”,"\\jl-1036");

var objCoreReflection= objServiceManager.createInstance("com.sun.star.reflection.CoreReflection");

var objDesktop= objServiceManager.createInstance("com.sun.star.frame.Desktop");

var objCoreReflection= objServiceManager.createInstance("com.sun.star.reflection.CoreReflection");

var args= new Array();

var objDocument= objDesktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, args);

var objText= objDocument.getText();

var objCursor= objText.createTextCursor();

objText.insertString( objCursor, "The first line in the newly created text document.\n", false);

objText.insertString( objCursor, "Now we're in the second line", false);

var objTable= objDocument.createInstance( "com.sun.star.text.TextTable");objTable.initialize( 4, 4);

objText.insertTextContent( objCursor, objTable, false);

var objRows= objTable.getRows();

var objRow= objRows.getByIndex( 0);

objTable.setPropertyValue( "BackTransparent", false);

objTable.setPropertyValue( "BackColor", 13421823);

objRow.setPropertyValue( "BackTransparent", false);

objRow.setPropertyValue( "BackColor", 6710932);

insertIntoCell( "A1","FirstColumn", objTable);

insertIntoCell( "B1","SecondColumn", objTable);

insertIntoCell( "C1","ThirdColumn", objTable);

insertIntoCell( "D1","SUM", objTable);

objTable.getCellByName("A2").setValue( 22.5);

objTable.getCellByName("B2").setValue( 5615.3);

objTable.getCellByName("C2").setValue( -2315.7);

objTable.getCellByName("D2").setFormula("sum <A2:C2>");objTable.getCellByName("A3").setValue( 21.5);

objTable.getCellByName("B3").setValue( 615.3);

objTable.getCellByName("C3").setValue( -315.7);

objTable.getCellByName("D3").setFormula( "sum <A3:C3>");objTable.getCellByName("A4").setValue( 121.5);

objTable.getCellByName("B4").setValue( -615.3);

objTable.getCellByName("C4").setValue( 415.7);

objTable.getCellByName("D4").setFormula( "sum <A4:C4>");

objCursor.setPropertyValue( "CharColor", 255);

objCursor.setPropertyValue( "CharShadowed", true);

objText.insertControlCharacter( objCursor, 0 , false);

objText.insertString( objCursor, " This is a colored Text - blue with shadow\n", false);objText.insertControlCharacter( objCursor, 0, false );

var objTextFrame= objDocument.createInstance("com.sun.star.text.TextFrame”);

var objSize= createStruct("com.sun.star.awt.Size");

objSize.Width= 15000;

objSize.Height= 400;

objTextFrame.setSize( objSize);

objTextFrame.setPropertyValue( "AnchorType", 1);

objText.insertTextContent( objCursor, objTextFrame, false);

var objFrameText= objTextFrame.getText();

var objFrameTextCursor= objFrameText.createTextCursor();

objFrameText.insertString( objFrameTextCursor, "The first line in the newly created text frame.",

                false);

objFrameText.insertString(objFrameTextCursor,

                          "With this second line the height of the frame raises.", false );

objFrameText.insertControlCharacter( objCursor, 0 , false);

objCursor.setPropertyValue( "CharColor", 65536);

objCursor.setPropertyValue( "CharShadowed", false);

objText.insertString( objCursor, " That's all for now !!", false );

function insertIntoCell( strCellName, strText, objTable)

{

    var objCellText= objTable.getCellByName( strCellName);

    var objCellCursor= objCellText.createTextCursor();

    objCellCursor.setPropertyValue( "CharColor",16777215);

    objCellText.insertString( objCellCursor, strText, false);

}

function createStruct( strTypeName)

{

    var classSize= objCoreReflection.forName( strTypeName);

    var aStruct= new Array();

    classSize.createObject( aStruct);

    return aStruct[0];

}

}

]]>

</script>

</component>

This WSC contains the WriterDemo example written in JScript.

The Bridge Services

Service: com.sun.star.bridge.OleBridgeSupplier2

The component implements the com.sun.star.bridge.XBridgeSupplier2 interface and converts Automation values to UNO values. The mapping of types occurs according to the mappings defined in 3.4.4 Professional UNO - UNO Language Bindings - Automation Bridge - Type Mappings.

Note graphics marks a special text section

Usually you do not use this service unless you must convert a type manually.

A programmer uses the com.sun.star.ServiceManager ActiveX component to access the office. The COM class factory for com.sun.star.ServiceManager uses OleBridgeSupplier2 internally to convert the UNO service manager into an Automation object. Another use case for the OleBridgeSupplier2 might be to use the SDK without an office installation. For example, if there is a UNO component from COM, write code which converts the UNO component without the need of an office. That code could be placed into an ActiveX object that offers a function, such as getUNOComponent().

The interface is declared as follows:

module com {  module sun {  module star {  module bridge {  

interface XBridgeSupplier2: com::sun::star::uno::XInterface

{

    any createBridge( [in] any aModelDepObject,

                      [in] sequence< byte > aProcessId,

                      [in] short nSourceModelType,

                      [in] short nDestModelType )

        raises( com::sun::star::lang::IllegalArgumentException );

}; }; }; };

The value that is to be converted and the converted value itself are contained in anys. The any is similar to the VARIANT type in that it can contain all possible types of its type system, but that type system only comprises UNO types and not Automation types. However, it is necessary that the function is able to receive as well as to return Automation values. In C++, void pointers could have been used, but pointers are not used with UNO IDL. Therefore, the any can contain a pointer to a VARIANT and that the type should be an unsigned long.

To provide the any, write this C++ code:

Any automObject;

// pVariant is a VARIANT* and contains the value that is going to be converted

automObject.setValue((void*) &pVariant, getCppuType((sal_uInt32*)0));

Whether the argument aModelDepObject or the return value carries a VARIANT depends on the mode in which the function is used. The mode is determined by supplying constant values as the nSourceModelType and nDestModelType arguments. Those constant are defined as follows:

module com {  module sun {  module star {  module bridge {  

constants ModelDependent

{

    const short UNO = 1;

    const short OLE = 2;

    const short JAVA = 3;

    const short CORBA = 4;

};

}; }; }; };  

The table shows the two possible modes:

nSourceModelType

nDestModelType

aModelDepObject

Return Value

UNO

OLE

contains UNO value

contains VARIANT*

OLE

UNO

contains VARIANT*

contains UNO value

When the function returns a VARIANT* , that is, a UNO value is converted to an Automation value, then the caller has to free the memory of the VARIANT:

sal_uInt8 arId[16];

rtl_getGlobalProcessId( arId );

Sequence<sal_Int8> procId((sal_Int8*)arId, 16);

Any anyDisp= xSupplier->createBridge( anySource, procId, UNO, OLE);

IDispatch* pDisp;

if( anyDisp.getValueTypeClass() == TypeClass_UNSIGNED_LONG)

{

    VARIANT* pvar= *(VARIANT**)anyDisp.getValue();

    if( pvar->vt == VT_DISPATCH)

    {

        pDisp= pvar->pdispVal;

        pDisp->AddRef();

    }

    VariantClear( pvar);

    CoTaskMemFree( pvar);

}

The function also takes a process ID as an argument. The implementation compares the ID with the ID of the process the component is running in. Only if the IDs are identical a conversion is performed. Consider the following scenario:

There are two processes. One process, the server process, runs the OleBridgeSupplier2 service. The second, the client process, has obtained the XBridgeSupplier2 interface by means of the UNO remote bridge. In the client process an Automation object is to be converted and the function XBridgeSupplier2::createBridge is called. The interface is actually a UNO interface proxy and the remote bridge will ensure that the arguments are marshaled, sent to the server process and that the original interface is being called. The argument aModelDepObject contains an IDispatch* and must be marshaled as COM interface, but the remote bridge only sees an any that contains an unsigned long and marshals it accordingly. When it arrives in the server process, the IDispatch* has become invalid and calls on it might crash the application.

Service: com.sun.star.bridge.OleBridgeSupplierVar1

This service is a variation of the OleBridgeSupplier2 service. The functionality is the same, but the implementation is optimized for a deployment scenario where remote UNO objects are converted into Automation objects. The UNO object is only a proxy and the actual object resides in a different process or on a different machine. To get a proxy of a remote object, establish a connection to another process which can run on another machine by means of the respective UNO mechanisms. Refer to 3.3.1 Professional UNO - UNO Concepts - UNO Interprocess Connections for additional information. Calls on the proxy object result in an interprocess call that may take a long time.

To call a function of an Automation object, a DISPID must be obtained first. The ID is obtained by calling IDispatch::GetIDsOfNames. The GetIDsOfName takes a function or property name as an argument and returns a DISPID that is used in the Invoke call. Automation objects created by OleBridgeSupplier2 verify in their GetIDsOfName implementation if the function or property with the specified name exists, involving one or two calls to the UNO object the first time the object's GetIDsOfName function is called. OleBridgeSupplierVar1 handles that differently. The first time an object is being asked for a DISPID, the ID is generated and returned without verifying if there is a member of that name. When Invoke is called with that DISPID and the call fails, the bridge repeats the call with a verified name. Also, Invoke is often called with a combination of the flag DISPATCH_METHOD and one of the property flag, signifying that the DISPID represents a certain function or property. In that case, the bridge first presumes that the ID represents a function and performs the call accordingly. If that fails, it tries to access a property with that name. When the call eventually succeeds, the acquired information (for example, the verified name of the member, property or function) is cached in case the call is repeated.

The OleBridgeSupplier2 and OleBridgeSupplierVar1 services use the com.sun.star.script.Invocation service to convert UNO objects to UNO objects that implement com.sun.star.script.Invocation. Then the XInvocation objects are converted into IDispatch objects. OleBridgeSupplierVar1 can be passed a service manager as an argument during instantiation (com.sun.star.lang.XMultiServiceFactory:createInstanceWithArguments()). It will then use that service manager to create the invocation service. If the service manager happens to be the remote service manager (provided by the server, for example, a remote office), the Invocation service is created on the server-side. Hence, all conversions of UNO objects to XInvocation objects occur remotely on the server and do not cause excessive network traffic.

Service: com.sun.star.bridge.OleApplicationRegistration

This service registers a COM class factory when the service is being instantiated and deregisters it when the service is being destroyed. The class factory creates a service manager as an Automation object. All UNO objects created by the service manager are then automatically converted into Automation objects.

Service: com.sun.star.bridge.OleObjectFactory

This service creates ActiveX components and makes them available as UNO objects which implement XInvocation. For the purpose of component instantiation, the OleClient implements the com.sun.star.lang.XMultiServiceFactory interface. The COM component is specified by its programmatic identifier (ProgId).

Although any ActiveX component with a ProgId can be created, a component can only be used if it supports IDispatch and provides type information through IDispatch::GetTypeInfo.

Unsupported COM Features

The Automation objects provided by the bridge do not provide type information. That is, IDispatch::GetTypeInfoCount and IDispatch::GetTypeInfo return E_NOTIMPL. Also, there are no COM type libraries available and the objects do not implement the IProvideClassInfo[2] interface.

GetIDsOfName processes only one name at a time. If an array of names is passed, then a DISPID is returned for the first name.

IDispatch::Invoke does not support named arguments and the pExcepInfo and puArgErr parameter.

[ Previous document | Content Table | Next document ]