LibreOffice Developer's Guide: Chapter 5 - Advanced UNO

From The Document Foundation Wiki
Jump to navigation Jump to search

Choosing an Implementation Language

The UNO technology provides a framework for cross-platform and language independent programming. All the LibreOffice components can be implemented in any language supported by UNO, as long as they only communicate with other components through their IDL interfaces.

Note pin.svg

Note:
Note: The condition "as long as they only communicate with other components through their IDL interfaces" is to be strictly taken. In fact, a lot of implementations within LibreOffice export UNO interfaces and still use private C++ interfaces. This is a tribute to older implementations that cannot be rewritten in an acceptable timeframe.

A developer can customize the office to their needs with this flexibility, but they will have to decide which implementation language should be selected for a specific problem.

Supported Programming Environments

The support for programming languages in UNO and LibreOffice is divided into three different categories.

  1. Languages that invoke calls on existing UNO objects are possibly implemented in other programming languages. Additionally, it may be possible to implement certain UNO interfaces, but not UNO components that can be instantiated by the service manager.
  2. Languages that implement UNO components. UNO objects implemented in such a language are accessible from any other language that UNO supports, just by instantiating a service by name at the servicemanager. For instance, the developer can implement a LibreOffice Calc addin (see Spreadsheet Documents).
  3. Languages that are used to write code to be delivered within LibreOffice documents and utilize dialogs designed with the LibreOffice dialog editor.

The following table lists programming languages currently supported by UNO. 'Yes' in the table columns denotes full support, 'no' denotes that there is no support and is not even planned in the future. 'Maybe in future' means there is currently no support, but this may change with future releases.

Language UNO scripting UNO components Deployment with LibreOffice documents
C++ yes yes no
C maybe in future maybe in future no
Java yes yes maybe in future
StarBasic yes no yes
OLE automation (win32 only) yes maybe in future maybe in future
Python maybe in future (under development) maybe in future (under development) maybe in future

Java

Java is a an accepted programming language offering a standard library with a large set of features and available extensions. Additional extensions will be available in the future, such as JAX-RPC for calling webservices. It is a typesafe language with a typesafe UNO binding. Although interfaces have to be queried explicitly, the type safety makes it suitable for larger projects. UNO components can be implemented with Java, that is, the Java VM is started on demand inside the office process when a Java component is instantiated. The OfficeBean allows embedding LibreOffice documents in Java Applets and Applications.

There is a constant runtime overhead of about 1 to 2 ms per call that is caused by the bridge conversion routines when calling UNO objects implemented in other language bindings. Since LibreOffice consists of C++ code, every Java call into the office needs to be bridged. This poses no problems if there are a few calls per user interaction. The runtime overhead will hurt the application when routines produce hundreds or thousands of calls.

C++

C++ is an accepted programming language offering third-party products. In addition to C++ being fast since it is compiled locally, it offers the fastest communication with LibreOffice because most of the essential parts of office have been developed in C++. This advantage becomes less important as you call into the office through the interprocess bridge, because every remote call means a constant loss of 1 to 2 ms. The fastest code to extend the office can be implemented as a C++ UNO component. It is appropriate for larger projects due to its strong type safety at compile time.

C++ is difficult to learn and coding, in general, takes longer, for example, in Java. The components must be built for every platform, that leads to a higher level of complexity during development and deployment.

LibreOffice Basic

LibreOffice Basic is the scripting language developed for and integrated directly into LibreOffice. It currently offers the best integration with LibreOffice, because you can insert code into documents, attach arbitrary office events, such as document loading, keyboard shortcuts or menu entries, to Basic code and use dialogs designed within the LibreOffice IDE. In Basic, calls are invoked on an object rather than on a specific interface. Interfaces, such as <idl>com.sun.star.beans.XPropertySet</idl> are integrated as Basic object properties. Basic always runs in the office process and thus avoids costly interprocess calls.

The language is type unsafe, that is, only a minimal number of errors are found during compilation. Most errors appear at runtime, therefore it is not the best choice for large projects. The language is LibreOffice specific and only offers a small set of runtime functionality with little third-party support. All office functionality is used through UNO. UNO components cannot be implemented with Basic. The only UNO objects that can be implemented are listeners. Finally, Basic does not offer any thread support.

OLE Automation Bridge

The OLE Automation bridge opens the UNO world to programming environments that support OLE automation, such as Visual Basic, JScript, Delphi or C++ Builder. Programmers working on the Windows platform can write programs for LibreOffice without leaving their language by learning a new API. These programmers have access to the libraries provided by their language. It is possible to implement UNO objects, if the programming language supports object implementation.

This bridge is only useful on a Win32 machine, thereby being a disadvantage. Scripts always run in a different process so that every UNO call has at least the usual interprocess overhead of 1 to 2 ms. Currently Automation UNO components cannot be implemented for the service manager, but this may change in the future.

Python

A Python scripting bridge (PyUNO) is currently developed by Ralph Thomas. It is available in an experimental alpha state with known limitations. For details, see PyUNO on www.openoffice.org/udk/.

Use Cases

The following list gives typical UNO applications for the various language environments.

Java

  • Servlets creating Office Documents on the fly, Java Server Pages
  • Server-Based Collaboration Platforms, Document Management Systems
  • Calc add-ins
  • Chart add-ins
  • Database Drivers

C++

  • Filters reading document data and generating Office Documents through UNO calls
  • Database Drivers
  • Calc add-ins
  • Chart add-ins

LibreOffice Basic

  • Office Automation
  • Event-driven data-aware forms

OLE Automation

  • Office Automation, creating and controlling Office Documents from other applications and from Active Server Pages

Python

  • Calc add-ins

Recommendation

All languages have their advantages and disadvantages as previously discussed, but there is not one language for all purposes, depending on your use. Consider carefully before starting a new project and evaluate the language to use so that it saves you time.

Sometimes it may be useful to use multiple languages to gain the advantages of both languages. For instance, currently it is not possible to attach a keyboard event to a Java method, therefore, write a small Basic function, which forwards the event to a Java component.

The number of languages supported by UNO may increase and some limitations shown in the table above may disappear.

Language Bindings

UNO language bindings enable developers to use and implement UNO objects in arbitrary programming languages. Thus, the existing language bindings connect between implementation environments, such as Java, C++, LibreOffice Basic and OLE Automation. The connection is accomplished by bridges. The following terms are used in our discussion about the implementation of language bindings.

In our context, the target language or target environment denotes the language or environment from which the UNO component model is accessed. The bridging language is the language used for writing the bridge code.

An object-oriented language determines the layout of its objects in memory. We call an object that is based on this layout a language object. The layout along with everything that relates to it, such as creation, destruction, and interaction, is the object model of a language.

A UNO proxy (short: proxy) is created by a bridge and it is a language object that represents a UNO object in the target language. It provides the same functionality as the original UNO object. There are two terms which further specialize a UNO proxy. The UNO interface proxy is a UNO proxy representing exactly one interface of a UNO object, whereas a UNO object proxy represents an uno object with all its interfaces.

An interface bridge bridges one UNO interface to one interface of the target language, that is, to a UNO interface proxy. When the proxy is queried for another interface that is implemented by the UNO object, then another interface proxy is returned. In contrast, an object bridge bridges entire UNO objects into UNO object proxies of the target language. The object proxy receives calls for all interfaces of the UNO object.

Implementing UNO Language Bindings

This section introduces the basic steps to create a new language binding. The steps required depend on the target language. The section provides an overview of existing language bindings to help you to decide what is necessary for your case. It is recommended that you read the sources for available language bindings and transfer the solutions they offer to the new circumstances of your target language.

Overview of Language Bindings and Bridges

Creating a language binding for UNO involves the following tasks:

Language Specification and UNO Feature Support

When writing a language binding, consider how to map UNOIDL types to your target language, treat simple types and handle complex types, such as struct, sequence, interface and any. Furthermore, UNOIDL features, such as services, properties and exceptions must be matched to the capabilities of the target language and accommodated, if need be.

Code Generator

If the target language requires type definitions at compile time, a code generator must translate UNOIDL type definitions to the target language type definitions according to the language specification, so that the types defined in UNOIDL can be used.

UNO Bridge

UNO communication is based on calls to interfaces. Bridges supply the necessary means to use interfaces of UNO objects between implementation environments. The key for bridging is an intermediate environment called binary UNO,that consists of binary data formats for parameters and return values, and a C dispatch method used to call arbitrary operations on UNO interfaces. A bridge must be capable of the following tasks:

  • Between the target language and LibreOffice:
    • Converting operation parameters from the target language to binary UNO.
    • Transforming operation calls in the target language to calls in binary UNO in a different environment.
    • Transporting the operation call with its parameters to LibreOffice and the return values back to the target language.
    • Mapping return values from binary UNO to the target language.
  • Between LibreOffice and the target language, that is, during callbacks or when using a component in the target language:
    • Converting operation parameters from binary UNO to the target language.
    • Transforming operation calls in binary UNO to calls in the target language.
    • Transporting the operation call with its parameters to the target language and the return values back to LibreOffice.
    • Converting return values from the target language to binary UNO.

The Reflection API delivers information about UNO types and is used by bridges to support type conversions (<idl>com.sun.star.script.Converter</idl>), and method invocations (<idl>com.sun.star.script.Invocation</idl> and <idl>com.sun.star.script.XInvocation</idl>). Furthermore, it supplies runtime type information and creates instances of certain UNO types, such as structs (<idl>com.sun.star.reflection.CoreReflection</idl>).

UNO Component Loader

An implementation loader is required to load and activate code produced by the target language if implementations in the target language are to be instantiated. This involves locating the component files produced by the target language, and mechanisms to load and execute the code produced by the target language, such as launching a runtime environment. Currently, there are implementation loaders for jar files and locally shared libraries on the platforms supported by UNO.

Bootstrapping

A UNO language binding must prepare itself so that it can bridge to the UNO environments. It depends on the target environment how this is achieved. In Java, C++, and Python, a local service manager in the target environment is used to instantiate a <idl>com.sun.star.bridge.UnoUrlResolver</idl> that connects to LibreOffice. In the Automation bridge, the object com.sun.star.ServiceManager is obtained from the COM runtime system and in LibreOffice Basic the service manager is available from a special method of the Basic runtime environment, getProcessServiceManager().

Implementation Options

There are two different approaches when creating a UNO language binding.

  1. Programming languages checking types at compile time. Examples are the languages Java or C++. In these environments, it is necessary to query for interfaces at certain objects and then invoke calls compile-time-typesafe on these interfaces.
  2. Programming languages checking types at runtime. Examples are the languages StarBasic, Python or Perl. In these languages, the interfaces are not queried explicitly as there is no compiler to check the signature of a certain method. Instead, methods are directly invoked on objects. During execution, the runtime engine checks if a method is available at one of the exported interfaces, and if not, a runtime error is raised. Typically, such a binding has a slight performance disadvantage compared to the solution above.

You can achieve different levels of integration with both types of language binding.

  1. Call existing UNO interfaces implemented in different bindings. This is the normal scripting use case, for example, connect to a remote running office, instantiate some services and invoke calls on these services (unidirectional binding).
  2. Implement UNO interfaces and let them be called from different bindings.In addition to 1) above, a language binding is able to implement UNO interfaces, for example, for instance listener interfaces, so that your code is notified of certain events (limited bidirectional binding).
  3. Implement a UNO component that is instantiated on demand from any other language at the global service manager. In addition to 2) above, a binding must provide the code which starts up the runtime engine of the target environment. For example, when a Java UNO component is instantiated by the LibreOffice process, the Java VM must be loaded and initialized, before the actual component is loaded (bidirectional binding).

A language binding should always be bidirectional. That is, it should be possible to access UNO components implemented in the target language from LibreOffice, as well as accessing UNO components that are implemented in a different language from the target language.

The following table provides an overview about the capabilities of the different language bindings currently available for LibreOffice:

Language scripting (accessing office objects) interface implementation component development
C++ (platform dependent) yes yes yes
Java yes yes yes
StarBasic yes (only listener interfaces) no
OLE automation (Win32 only) yes yes no (maybe in the future)

The next section outlines the implementation of a C++ language binding. The C++ binding itself is extremely platform and compiler dependent, which provides a barrier when porting LibreOffice to a new platform. Although this chapter focuses on C++ topics, the chapter can be applied for other typesafe languages that store their code in a shared library, for instance, Delphi, because the same concepts apply.

The section UNO Reflection API considers the UNO reflection and invocation API, which offers generic functionality to inspect and call UNO objects. The section XInvocation Bridge explains how the Reflection API is used to implement a runtime type-checking language binding.

The final chapter Implementation Loader briefly describes the concept of implementation loaders that instantiates components on demand independently of the client and the implementation language. The integration of a new programming language into the UNO component framework is completed once you have a loader.

UNO C++ Bridges

This chapter focuses on writing a UNO bridge locally, specifically writing a C++ UNO bridge to connect to code compiled with the C++ compiler. This is an introduction for bridge implementers.. It is assumed that the reader has a general understanding of compilers and a of 80x86 assembly language. Refer to the section Implementation Loader for additional information.

Binary UNO Interfaces

A primary goal when using a new compiler is to adjust the C++-UNO data type generator (cppumaker tool) to produce binary compatible declarations for the target language. The tested cppu core functions can be used when there are similar sizes and alignment of UNO data types. The layout of C++ data types, as well as implementing C++-UNO objects is explained in C++ Language Binding.

When writing C++ UNO objects, you are implementing UNO interfaces by inheriting from pure virtual C++ classes, that is, the generated cppumaker classes (see .hdl files). When you provide an interface, you are providing a pure virtual class pointer. The following paragraph describes how the memory layout of a C++ object looks.

A C++-UNO interface pointer is always a pointer to a virtual function table (vftable), that is, a C++ this pointer. The equivalent binary UNO interface is a pointer to a struct _uno_Interface that contains function pointers. This struct holds a function pointer to a uno_DispatchMethod() and also a function pointer to acquire() and release():

// forward declaration of uno_DispatchMethod()

typedef void (SAL_CALL * uno_DispatchMethod)(
          struct _uno_Interface * pUnoI, 
          const struct _typelib_TypeDescription * pMemberType,
          void * pReturn,
          void * pArgs[],
          uno_Any ** ppException );

// Binary UNO interface

typedef struct _uno_Interface 
{
          /** Acquires uno interface.

              @param pInterface uno interface
  
          */
          void (SAL_CALL * acquire )( struct _uno_Interface * pInterface );
          /** Releases uno interface.

          @param pInterface uno interface
          */
          void (SAL_CALL * release )( struct _uno_Interface * pInterface );
          /** dispatch function
          */
          uno_DispatchMethod pDispatcher ;
} uno_Interface;

Similar to <idl>com.sun.star.uno.XInterface</idl>, the life-cycle of an interface is controlled using the acquire() and release() functions of the binary UNO interface. Any other method is called through the dispatch function pointer pDispatcher. The dispatch function expects the binary UNO interface pointer (this), the interface member type of the function to be called, an optional pointer for a return value, the argument list and finally a pointer to signal an exception has occurred.

The caller of the dispatch function provides memory for the return value and the exception holder (uno_Any).

The pArgs array provides pointers to binary UNO values, for example, a pointer to an interface reference (_uno_Interface **) or a pointer to a SAL 32 bit integer (sal_Int32 *).

A bridge to binary UNO maps interfaces from C++ to binary UNO and conversely. To achieve this, implement a mechanism to produce proxy interfaces for both ends of the bridge.

C++ Proxy

A C++ interface proxy carries its interface type (reflection), as well as its destination binary UNO interface (this pointer). The proxy's vftable pointer is patched to a generated vftable that is capable of determining the index that was called ,as well as the this pointer of the proxy object to get the interface type.

The vftable requires an assembly code. The rest is programmed in C/C++. You are not allowed to trash the registers. On many compilers, the this pointer and parameters are provided through stack space. The following provides an example of a Visual C++ 80x86:

vftable slot0:
mov eax, 0
jmp cpp_vftable_call
vftable slot0:
mov eax, 1
jmp cpp_vftable_call
vftable slot0:
mov eax, 2
jmp cpp_vftable_call
...

static __declspec(naked) void __cdecl cpp_vftable_call(void)
{
__asm
        {
                sub              esp, 8           // space for immediate return type
                push             esp
                push             eax              // vtable index
                mov              eax, esp
                add              eax, 16
                push             eax              // original stack ptr
                call             cpp_mediate      // proceed in C/C++
                add              esp, 12
                // depending on return value, fill registers
                cmp              eax, typelib_TypeClass_FLOAT
                je               Lfloat
                cmp              eax, typelib_TypeClass_DOUBLE
                je               Ldouble
                cmp              eax, typelib_TypeClass_HYPER
                je               Lhyper
                cmp              eax, typelib_TypeClass_UNSIGNED_HYPER
                je               Lhyper
                // rest is eax
                pop              eax
                add              esp, 4
                ret
Lhyper:         pop              eax
                pop              edx
                ret
Lfloat:         fld              dword ptr [esp]
                add              esp, 8
                ret
Ldouble:        fld              qword ptr [esp]
                add              esp, 8
                ret
        }
}

The vftable is filled with pointers to the different slot code (snippets). The snippet code recognizes the table index being called and calls cpp_vftable_call(). That function calls a C/C++ function (cpp_mediate()) and sets output registers upon return, for example, for floating point numbers depending on the return value type.

Remember that the vftable handling described above follows the Microsoft calling convention, that is, the this pointer is always the first parameter on the stack. This is currently not the case for gcc that prepends a pointer to a complex return value before the this pointer on the stack if a method returns a struct. This complicates the (static) vftable treatment, because different vftable slots have to be generated for different interface types, adjusting the offset to the proxy this pointer:

Microsoft Visual C++ call stack layout (esp offset [byte]):
0: return address
4: this pointer
8: optional pointer, if return value is complex (i.e. struct to be copy-constructed)
12: param0
16: param1
20: ...

This is usually the hardest part for stack-oriented compilers. Afterwards proceed in C/C++ (cpp_mediate()) to examine the proxy interface type, read out parameters from the stack and prepare the call on the binary UNO destination interface.

Each parameter is read from the stack and converted into binary UNO. Use cppu core functions if you have adjusted the cppumaker code generation (alignment, sizes) to the binary UNO layout (see cppu/inc/uno/data.h).

After calling the destination uno_dispatch() method, convert any out/inout and return the values back to C++-UNO, and return to the caller. If an exception is signalled (*ppException != 0), throw the exception provided to you in ppException. In most cases, you can utilize Runtime Type Information (RTTI) from your compiler framework to throw exceptions in a generic manner. Disassemble code throwing a C++ exception, and observe what the compiler generates.

Binary UNO Proxy

The proxy code is simple for binary UNO. Convert any in/inout parameters to C++-UNO values, preparing a call stack. Then perform a virtual function call that is similar to the following example for Microsoft Visual C++:

void callVirtualMethod(
        void * pThis, sal_Int32 nVtableIndex,
        void * pRegisterReturn, typelib_TypeClass eReturnTypeClass,
        sal_Int32 * pStackLongs, sal_Int32 nStackLongs )
{
        // parameter list is mixed list of * and values
        // reference parameters are pointers

__asm
        {
                mov              eax, nStackLongs
                test             eax, eax
                je               Lcall
                // copy values
                mov              ecx, eax
                shl              eax, 2                // sizeof(sal_Int32) == 4
                add              eax, pStackLongs      // params stack space
Lcopy:            sub              eax, 4
                push             dword ptr [eax]
                dec              ecx
                jne              Lcopy
Lcall:
                // call
                mov              ecx, pThis
                push             ecx   // this ptr
                mov              edx, [ecx]  // pvft
                mov              eax, nVtableIndex
                shl              eax, 2                // sizeof(void *) == 4
                add              edx, eax
                call             [edx]                 // interface method call must be __cdecl!!!

                // register return
                mov              ecx, eReturnTypeClass
                cmp              ecx, typelib_TypeClass_VOID
                je               Lcleanup
                mov              ebx, pRegisterReturn
// int32
                cmp              ecx, typelib_TypeClass_LONG
                je               Lint32
                cmp              ecx, typelib_TypeClass_UNSIGNED_LONG
                je               Lint32
                cmp              ecx, typelib_TypeClass_ENUM
                je               Lint32
// int8
                cmp              ecx, typelib_TypeClass_BOOLEAN
                je               Lint8
                cmp              ecx, typelib_TypeClass_BYTE
                je               Lint8
// int16
                cmp              ecx, typelib_TypeClass_CHAR
                je               Lint16
                cmp              ecx, typelib_TypeClass_SHORT
                je               Lint16
                cmp              ecx, typelib_TypeClass_UNSIGNED_SHORT
                je               Lint16
// float
                cmp              ecx, typelib_TypeClass_FLOAT
                je               Lfloat
// double
                cmp              ecx, typelib_TypeClass_DOUBLE
                je               Ldouble
// int64
                cmp              ecx, typelib_TypeClass_HYPER
                je               Lint64
                cmp              ecx, typelib_TypeClass_UNSIGNED_HYPER
                je               Lint64
                jmp              Lcleanup                  // no simple type
Lint8:
                mov              byte ptr [ebx], al
                jmp              Lcleanup
Lint16:
                mov              word ptr [ebx], ax
                jmp              Lcleanup
Lfloat:
                fstp             dword ptr [ebx]
                jmp              Lcleanup
Ldouble:
                fstp            qword ptr [ebx]
                jmp             Lcleanup
Lint64:
                mov             dword ptr [ebx], eax
                mov             dword ptr [ebx+4], edx
                jmp             Lcleanup
Lint32:
                mov             dword ptr [ebx], eax
                jmp             Lcleanup
Lcleanup:
                // cleanup stack
                mov             eax, nStackLongs
                shl             eax, 2                    // sizeof(sal_Int32) == 4
                add             eax, 4                    // this ptr
                add             esp, eax
            }
}

First stack data is pushed to the stack., including a this pointer, then the virtual function's pointer is retrieved and called. When the call returns, the return register values are copied back. It is also necessary to catch all exceptions generically and retrieve information about type and data of a thrown exception. In this case, look at your compiler framework functions also.

Additional Hints

Every local bridge is different, because of the compiler framework and code generation and register allocation. Before starting, look at your existing bridge code for the processor, compiler, and the platform in module bridges/source/cpp_uno that is part of the LibreOffice source tree on www.openoffice.org.

Also test your bridge code extensively and build the module cppu with debug symbols before implementing the bridge, because cppu contains alignment and size tests for the compiler.

For quick development, use the executable build in cppu/test raising your bridge library, doing lots of calls with all kinds of data on mapped interfaces.

Also test your bridge in a non-debug build. Often, bugs in assembly code only occur in non-debug versions, because of trashed registers. In most cases, optimized code allocates or uses more processor registers than non-optimized (debug) code.

UNO Reflection API

This section describes the UNO Reflection API. This API includes services and interfaces that can be used to get information about interfaces and objects at runtime.

XTypeProvider Interface

The interface <idl>com.sun.star.lang.XTypeProvider</idl> allows the developer to retrieve all types provided by an object. These types are usually interface types and the XTypeProvider interface can be used at runtime to detect which interfaces are supported by an object. This interface should be supported by every object to make it scriptable from LibreOffice Basic.

Converter Service

The service <idl>com.sun.star.script.Converter</idl> supporting the interface <idl>com.sun.star.script.XTypeConverter</idl> provides basic functionality that is important in the reflection context. It converts values to a particular type. For the method <idlm>com.sun.star.script.XTypeConverter:convertTo</idlm>(), the target type is specified as type, allowing any type available in the UNO type system. The method <idlm>com.sun.star.script.XTypeConverter:convertToSimpleType</idlm>() converts a value into a simple type that is specified by the corresponding <idl>com.sun.star.uno.TypeClass</idl>. If the requested conversion is not feasible, both methods throw a <idl>com.sun.star.script.CannotConvertException</idl>.

CoreReflection Service

The service <idl>com.sun.star.reflection.CoreReflection</idl> supporting the interface <idl>com.sun.star.reflection.XIdlReflection</idl> is an important entry point for the Uno Reflection API. The XIdlReflection interface has two methods that each return a <idl>com.sun.star.reflection.XIdlClass</idl> interface for a given name (method forName()) or any value (method getType()).

The interface XIdlClass is one of the central interfaces of the Reflection API. It provides information about types, especially about class or interface, and struct types. Besides general information, for example, to check type identity through the method equals() or to determine a type or class name by means of the method getName(), it is possible to ask for the fields or members, and methods supported by an interface type (method getFields() returning a sequence of XIdlField interfaces and method getMethods() returning a sequence of XIdlMethod interfaces).

The interface XIdlField is deprecated and should not be used. Instead the interface <idl>com.sun.star.reflection.XIdlField2</idl> is available by querying it from an XIdlField interface returned by an XIdlClass method.

The interface XIdlField or XIdlField2 represents a struct member of a struct or get or set accessor methods of an interface type. It provides information about the field (methods getType() and getAccessMode()) and reads and - if allowed by the access mode - modifies its value for a given instance of the corresponding type (methods get() and set()).

The interface XIdlMethod represents a method of an interface type. It provides information about the method (methods getReturnType(), getParameterTypes(), getParameterInfos(), getExceptionTypes() and getMode()) and invokes the method for a given instance of the corresponding type (method invoke()).

Introspection

The service <idl>com.sun.star.beans.Introspection</idl> supporting the interface <idl>com.sun.star.beans.XIntrospection</idl> is used to inspect an object of interface or struct type to obtain information about its members and methods. Unlike the CoreReflection service, and the XIdlClass interface ,the inspection is not limited to one interface type but to all interfaces supported by an object. To detect the interfaces supported by an object, the Introspection service queries for the XTypeProvider interface. If an object does not support this interface, the introspection does not work correctly.

To inspect an object, pass it as an any value to the inspect() method of XIntrospection. The result of the introspection process is returned as <idl>com.sun.star.beans.XIntrospectionAccess</idl> interface. This interface is used to obtain information about the inspected object. All information returned refers to the complete object as a combination of several interfaces. When accessing an object through XIntrospectionAccess, it is impossible to distinguish between the different interfaces.

The <idl>com.sun.star.beans.XIntrospectionAccess</idl> interface provides a list of all properties (method getProperties()) and methods (method getMethods()) supported by the object. The introspection maps methods matching the pattern

 FooType getFoo()
 setFoo(FooType)

to a property Foo of type FooType.

<idl>com.sun.star.beans.XIntrospectionAccess</idl> also supports a categorization of properties and methods. For instance, it is possible to exclude "dangerous" methods, such as the reference counting methods <idlm>com.sun.star.uno.XInterface:acquire</idlm>() and <idlm>com.sun.star.uno.XInterface:release</idlm>() from the set of methods returned by getMethods(). When the Introspection service is used to bind a new scripting language, it is useful to block the access to functionality that could crash the entire LibreOffice application when used in an incorrect manner.

The XIntrospectionAccess interface does not allow the developer to invoke methods and access properties directly. To invoke methods, the invoke() method of the XIdlMethod interfaces returned by the methods getMethods() and getMethod() are used. To access properties, a <idl>com.sun.star.beans.XPropertySet</idl> interface is used that can be queried from the <idlm>com.sun.star.beans.XIntrospectionAccess:queryAdapter</idlm>() method. This method also provides adapter interfaces for other generic access interfaces like <idl>com.sun.star.container.XNameAccess</idl> and <idl>com.sun.star.container.XIndexAccess</idl>, if these interfaces are also supported by the original object.

Invocation

The service <idl>com.sun.star.script.Invocation</idl> supporting the interface <idl>com.sun.star.lang.XSingleServiceFactory</idl> provides a generic, high-level access (higher compared to the Introspection service) to the properties and methods of an object. The object that should be accessed through Introspection is passed to the <idlm>com.sun.star.lang.XSingleServiceFactory:createInstanceWithArguments</idlm>() method. The returned XInterface can then be queried for <idl>com.sun.star.script.XInvocation2</idl> derived from <idl>com.sun.star.script.XInvocation</idl>.

The XInvocation interface invokes methods and access properties directly by passing their names and additional parameters to the corresponding methods (method invoke(), getValue() and setValue()). It is also possible to ask if a method or property exists with the methods hasMethod() and hasProperty().

When invoking a method with invoke(), the parameters are passed as a sequence of any values. The Invocation service automatically converts these arguments, if possible to the appropriate target types using the <idl>com.sun.star.script.Converter</idl> service that is further described below. The Introspection functionality is suitable for binding scripting languages to UNO that are not or only weakly typed.

The XInvocation2 interface extends the Invocation functionality by methods to ask for further information about the properties and methods of the object represented by the Invocation instance. It is possible to ask for the names of all the properties and methods (method getMemberNames()) and detailed information about them represented by the <idl>com.sun.star.script.InvocationInfo</idl> struct type (methods getInfo() and getInfoForName()).

Members of struct <idl>com.sun.star.script.InvocationInfo</idl>
aName Name of the method or property.
eMemberType Kind of the member (method or property).
PropertyAttribute Only for property members: This field may contain zero or more constants of the com::sun::star::beans::PropertyAttribute constants group. It is not guaranteed that all necessary constants are set to describe the property completely, but a flag will be set if the corresponding characteristic really exists. For example, if the READONLY flag is set, the property is read only. If it is not set, the property nevertheless can be read only. This field is irrelevant for methods and is set to 0.
aType Type of the member, when referring to methods, the return type
aParamTypes Types of method parameters, for properties this sequence is empty
aParamModes Mode of method parameters (in, out, inout), for properties this sequence is empty.

The Invocation service is based on the Introspection service. The XInvocation interface has a method getIntrospection() to ask for the corresponding XIntrospectionAccess interface. The Invocation implementation currently implemented in LibreOffice supports this, but in general, an implementation of XInvocation does not provide access to an XInvocationAccess interface.

InvocationAdapterFactory

The service <idl>com.sun.star.script.InvocationAdapterFactory</idl> supporting the interfaces <idl>com.sun.star.script.XInvocationAdapterFactory</idl> and <idl>com.sun.star.script.XInvocationAdapterFactory2</idl> are used to create adapters that map a generic XInvocation interface to specific interfaces. This functionality is especially essential for creating scripting language bindings that do not only access UNO from the scripting language, but also to implement UNO objects using the scripting language. Without the InvocationAdapterFactory functionality, this would only be possible if the scripting language supported the implementation of interfaces directly.

By means of the InvocationAdapterFactory functionality it is only necessary to map the scripting language specific native invocation interface, for example, realized by an OLE IDispatch interface, to the UNO XInvocation interface. Then, any combination of interfaces needed to represent the services supported by a UNO object are provided as an adapter using the <idlm>com.sun.star.script.XInvocationAdapterFactory2:createAdapter</idlm>() method.

Another important use of the invocation adapter is to create listener interfaces that are passed to the corresponding add...Listener() method of an UNO interface and maps to the methods of an interface to XInvocation. In this case, usually the <idlm>com.sun.star.script.XInvocationAdapterFactory:createAdapter</idlm>() method is used.

XTypeDescription

Internally, types in UNO are represented by the type type. This type also has an interface representation <idl>com.sun.star.reflection.XTypeDescription</idl>. A number of interfaces derived from XTypeDescription represent types. These interfaces are:

  • <idl>com.sun.star.reflection.XArrayTypeDescription</idl>
  • <idl>com.sun.star.reflection.XCompoundTypeDescription</idl>
  • <idl>com.sun.star.reflection.XEnumTypeDescription</idl>
  • <idl>com.sun.star.reflection.XIndirectTypeDescription</idl>
  • <idl>com.sun.star.reflection.XUnionTypeDescription</idl>
  • <idl>com.sun.star.reflection.XInterfaceTypeDescription</idl>
  • <idl>com.sun.star.reflection.XInterfaceAttributeTypeDescription</idl>
  • <idl>com.sun.star.reflection.XInterfaceMemberTypeDescription</idl>
  • <idl>com.sun.star.reflection.XInterfaceMethodTypeDescription</idl>

The corresponding services are <idl>com.sun.star.reflection.TypeDescriptionManager</idl> and <idl>com.sun.star.reflection.TypeDescriptionProvider</idl>. These services support <idl>com.sun.star.container.XHierarchicalNameAccess</idl> and asks for a type description interface by passing the fully qualified type name to the <idlm>com.sun.star.container.XHierarchicalNameAccess:getByHierarchicalName</idlm>() method.

The TypeDescription services and interfaces are listed here for completeness. Ordinarily this functionality would not be used when binding a scripting language to UNO, because the high-level services Invocation, Introspection and Reflection provide all the functionality required. If the binding is implemented in C++, the type type and the corresponding C API are used directly.

The following illustration provides an overview of how the described services and interfaces work together. Each arrow expresses a "uses" relationship. The interfaces listed for a service are not necessarily supported by the service directly, but contain interfaces that are strongly related to the services.

UML diagram showing the a Scripting Language Binding

XInvocation Bridge

Scripting Existing UNO Objects

This section describes UNO bridges for type-unsafe (scripting) programming languages. These bridges are based on the <idl>com.sun.star.script.Invocation</idl> service.

The most common starting point for a new scripting language binding is that you want to control LibreOffice from a script running externally. To accomplish this, you need to know what your scripting language offers to extend the language, for example, Python or Perl extend the language with a module concept using locally shared libraries.

In general, your bridge must offer a static method that is called from a script. Within this method, bootstrap a UNO C++ component context as described in Bootstrapping a Service Manager.

Proxying a UNO Object

Next, this component context must be passed to the script programmer, so that you can instantiate a <idl>com.sun.star.bridge.UnoUrlResolver</idl> and connect to a running office within the script.

The component context can not be passed directly as a C++ UNO reference, because the scripting engine does not recognize it, therefore build a language dependent proxy object around the C++ object Reference.

A scripting call over a proxy instance

For example, Python offers an API to create a proxy. Typically calls invoked on the proxy from a script are narrowed into one single C function. The Python runtime passes method names and an array containing the arguments to this C function.

If a proxy is implemented for a concrete interface, the method names that you received could in theory be compared to all method names offered by the UNO interface. This is not feasible, because of all the interfaces used in LibreOffice. The <idl>com.sun.star.script.Invocation</idl> service exists for this purpose. It offers a simple interface <idl>com.sun.star.lang.XSingleServiceFactory</idl> that creates a proxy for an arbitrary UNO object using the createInstanceWithArguments() method and passing the object the proxy acts for. Use the <idl>com.sun.star.script.XInvocation</idl> interface that is exported by this proxy to invoke a method on the UNO object.

A scripting call using XInvocation
Argument Conversion

In addition, argument conversion must be considered by specifying how each UNO type should be mapped to your target language.

Convert the language dependent data types to UNO data types before calling <idlm>com.sun.star.script.XInvocation:invoke</idlm>() and convert the UNO datatypes (return value and out parameters) to language dependent types after the call has been exectuted. The conversion routines are typically recursive functions, because data values are nested in complex types, such as struct or any.

When UNO object references are returned by method calls to UNO objects, create new language dependent proxies as discussed above. When passing a previously returned UNO object as a parameter to a new method call, the language binding must recognize that it is a proxied object and pass the original UNO object reference to the <idlm>com.sun.star.script.XInvocation:invoke</idlm>() call instead.

A special case for conversions are UNOIDL structs. You want to call a method that takes a struct as an argument. The first problem is the struct must be created by the bridge and the script programmer must be able to set members at the struct. One solution is that the bridge implementer creates a UNO struct using core C functions from the cppu library, but this is complicated and results in a lot of difficulty.

Therefore, a solution has been created that accesses structs through the XInvocation interface, as if they were UNO objects. This simplifies struct handling for bridge programmers. Refer to the reference documentation of <idl>com.sun.star.reflection.CoreReflection</idl> and the <idl>com.sun.star.script.Invocation</idl> service and the <idl>com.sun.star.beans.XMaterialHolder</idl> interface.

Exception Handling

UNO method calls may throw exceptions and must be mapped to the desired target language appropriately, depending on the capabilities of your target language. Ideally, the target language supports an exception concept, but error handlers, such as in LibreOffice Basic can be used also. A third way and worst case scenario is to check after every API call if an exception has been thrown,. In case the UNO object throws an exception, the XInvocation proxy throws a <idl>com.sun.star.reflection.InvocationTargetException</idl>. The exception has an additional any member, that contains the exception that was really thrown.

Note that the XInvocation proxy may throw a <idl>com.sun.star.script.CannotConvertException</idl> indicating that the arguments passed by the script programmer cannot be matched to the arguments of the desired function. For example, there are missing arguments or the types are incompatible. This must be reported as an error to the script programmer.

Property Support

The <idl>com.sun.star.script.Invocation</idl> has special getProperty() and setProperty() methods. These methods are used when the UNO object supports a property set and your target language, for example, supports something similar to the following:

 object.propname = 'foo';.

Note that every property is also reachable by <idlm>com.sun.star.script.XInvocation:invoke</idlm>('setPropertyValue', ...), so these set or getProperty functions are optional.

Implementing UNO objects

When it is possible to implement classes in your target language, consider offering support for implementation of UNO objects. This is useful for callbacks, for example, event listeners. Another typical use case is to provide a datasource through a <idl>com.sun.star.io.XInputStream</idl>.

The script programmer determines which UNOIDL types the developed class implements, such as flagged by a special member name, for example, such as __supportedUnoTypes.

When an instance of a class is passed as an argument to a call on an external UNO object, the bridge code creates a new language dependent proxy that additionally supports the XInvocation interface. The bridge code hands the XInvocation reference of the bridge's proxy to the called object. This works as long as the <idlm>com.sun.star.script.XInvocation:invoke</idlm>() method is used directly, for instance LibreOffice Basic, except if the called object expects an XInputStream.

The <idl>com.sun.star.script.InvocationAdapterFactory</idl> service helps by creating a proxy for a certain object that implements XInvocation and a set of interfaces, for example, given by the __supportedUnoTypes variable. The proxy returned by the createAdapater() method must be passed to the called object instead of the bridge's XInvocation implementation. When the Adapter is queried for one of the supported types, an appropriate proxy supporting that interface is created.

If a UNO object invokes a call on the object, the bridge proxy's <idlm>com.sun.star.script.XInvocation:invoke</idlm>() method is called. It converts the passed arguments from UNO types to language dependent types and conversely using the same routines you have for the other calling direction. Finally, it delegates the call to the implementation within the script.

A call sequence

It may become difficult if you do not want to start with an external scripting engine, but want to use the scripting engine inside the LibreOffice process instead. This must be supported by the target language. Often it is possible to load some library dynamically and access the scripting runtime engine through a C API. It should be implemented as a UNO C++ component. There are currently no generic UNO interfaces for this case, except for the <idl>com.sun.star.loader.XImplementationLoader</idl>. Define your own interfaces that best match your requirements. You might instantiate from Basic and retrieve an initial object or start a script. Future versions of LibreOffice may have a more comprehensive solution.

Example: Python Bridge PyUNO

This section provides an example of how the Python UNO bridge PyUNO bootstraps a service manager and how it makes use of the Invocation service to realize method invocation. While some parts are implementation or Python specific, the example provides a general understanding of language bindings.

The Python bridge PyUNO uses the cppu helper library to bootstrap a local service manager that is asked for a UnoUrlResolver service in Python.

In UNO.py, Python calls PyUNO.bootstrap() and receives a local component context. Note the parameter setup in that, it points to an ini file that configures the bootstrapped service manager with a type library. The file setup.ini corresponds to the uno.ini file that is used with the global service manager of the office.

import PyUNO
import os

setup_ini = 'file:///%s/setup.ini' % os.getenv ('PWD')

class UNO:

    def __init__ ( self, connection='socket,host=localhost,port=2083;urp', setup=setup_ini ):
        """ do the bootstrap

            connection can be one or more of the following:

            socket,
            host = localhost | <hostname> | <ip-addr>,
            port = <port>,
            service = soffice,
            user = <username>,
            password = <password>
            ;urp

        """

        self.XComponentContext = PyUNO.bootstrap ( setup )
        self.XUnoUrlResolver, o = \
                self.XComponentContext.ServiceManager.createInstanceWithContext (
                        'com.sun.star.bridge.UnoUrlResolver', self.XComponentContext )
        self.XNamingService, o = self.XUnoUrlResolver.resolve (
                'uno:%s;StarOffice.NamingService' % connection )
        self.XMultiServiceFactory, o = self.XNamingService.getRegisteredObject (
                'StarOffice.ServiceManager')
        self.XComponentLoader, o = \
                self.XMultiServiceFactory.createInstance ( 'com.sun.star.frame.Desktop' )
        ...

Python uses function tables to map Python to C functions. PyUNO_module.cc defines a table with the mappings for the PyUNO object. As shown in the following example, PyUNO.bootstrap() is mapped to the C function newBootstrapPyUNO():

static struct PyMethodDef PyUNOModule_methods [] =
{
  {"bootstrapPyUNO", bootstrapPyUNO, 1},
  {"bootstrap ", newBootstrapPyUNO , 1}, 
  {"createIdlStruct", createIdlStruct, 1},
  {"true", createTrueBool, 1},
  {"false", createFalseBool, 1},
  {NULL, NULL}
};

The function newBootstrapPyUNO() calls Util::bootstrap() in PyUNO_Util.cc and passes the location of the setup.ini file.

static PyObject* newBootstrapPyUNO (PyObject* self, PyObject* args)
{
  char* ini_file_location;
  Reference<XComponentContext> tmp_cc;
  Any a;

  if (!PyArg_ParseTuple (args, "s", &ini_file_location))
    return NULL;
  tmp_cc = Util::bootstrap (ini_file_location);
  ...

Util::bootstrap() uses defaultBootstrap_InitialComponentContext(iniFile) from cppuhelper/bootstrap.hxx to create a local component context and its parameter iniFile points to the setup.ini file that configures the local service manager to use service.rdb and types.rdb (until OpenOffice.org 1.1.0 applicat.rdb). This local component context instantiates services, such as the UnoUrlResolver.

Reference<XComponentContext> bootstrap (char* ini_file_location)
{
  Reference<XComponentContext> my_component_context;
  try
    {
      my_component_context = defaultBootstrap_InitialComponentContext (
               OUString::createFromAscii (ini_file_location ));
    }
  catch (com::sun::star::uno::Exception e)
    {
      printf (OUStringToOString (e.Message, osl_getThreadTextEncoding ()).getStr ());
    }
  return my_component_context;
}

Now newBootstrapPyUNO() continues to set up a UNO proxy. It creates local instances of <idl>com.sun.star.script.Invocation</idl> and <idl>com.sun.star.script.Converter</idl>, and calls PyUNO_new(), passing the local ComponentContext, a reference to the XSingleServiceFactory interface of <idl>com.sun.star.script.Invocation</idl> and a reference to the XTypeConverter interface of <idl>com.sun.star.script.Converter</idl>.

static PyObject* newBootstrapPyUNO (PyObject* self, PyObject* args)
{
  char* ini_file_location;
  Reference<XComponentContext> tmp_cc;
  Any a;

  if (!PyArg_ParseTuple (args, "s", &ini_file_location))
    return NULL;
  tmp_cc = Util::bootstrap (ini_file_location) ;
  Reference<XMultiServiceFactory> tmp_msf (tmp_cc->getServiceManager (), UNO_QUERY);
  if (!tmp_msf.is ())
    {
      PyErr_SetString (PyExc_RuntimeError, "Couldn't bootstrap from inifile");
      return NULL;
    }
  Reference<XSingleServiceFactory> tmp_ssf (tmp_msf->createInstance (
      OUString (RTL_CONSTASCII_USTRINGPARAM ("com.sun.star.script.Invocation "))), UNO_QUERY);
  Reference<XTypeConverter> tmp_tc (tmp_msf->createInstance (
      OUString (RTL_CONSTASCII_USTRINGPARAM ("com.sun.star.script.Converter "))), UNO_QUERY);
  if (!tmp_tc.is ())
    {
      PyErr_SetString (PyExc_RuntimeError, "Couldn't create XTypeConverter");
      return NULL;
    }
  if (!tmp_ssf.is ())
    {
      PyErr_SetString (PyExc_RuntimeError, "Couldn't create XInvocation");
      return NULL;
    }
  a <<= tmp_cc;

  return PyUNO_new (a, tmp_ssf, tmp_tc) ;

}

PyUNO_new() in PyUNO.cc is the function responsible for building all Python proxies. The call to PyUNO_new() here in newBootstrapPyUno() builds the first local PyUNO proxy for the ComponentContext object a which has been returned by Util::bootstrap().

For this purpose, PyUNO_new() uses the Invocation service to retrieve an XInvocation2 interface to the ComponentContext service passed in the parameter a:

// PyUNO_new
//
// creates Python object proxies for the given target UNO interfaces
// targetInterface given UNO interface
// ssf                     XSingleServiceFactory interface of com.sun.star.script.Invocation service
// tc                      XTypeConverter interface of com.sun.star.script.Converter service

PyObject* PyUNO_new (Any targetInterface,
                  Reference<XSingleServiceFactory> ssf,
                  Reference<XTypeConverter> tc)
  ...
  Sequence<Any> arguments (1);
  Reference<XInterface> tmp_interface;
  ...
  // put the target object into a sequence of Any for the call to
  // ssf->createInstanceWithArguments()
  // ssf is the XSingleServiceFactory interface of the com.sun.star.script.Invocation service
  arguments[0] <<= targetInterface;

  // obtain com.sun.star.script.XInvocation2 for target object from Invocation
  // let Invocation create an XInvocation object for the Any in arguments
  tmp_interface = ssf->createInstanceWithArguments (arguments);
  // query XInvocation2 interface
  Reference<XInvocation2 > tmp_invocation (tmp_interface, UNO_QUERY);
  ...

The Python proxy invokes methods, and creates and converts UNO types. This Python specific and involves the implementation of several functions according to the Python API.

Finally __init__() in UNO.py in the above example uses the PyUNO object to obtain a local UnoUrlResolver that retrieves the initial object from the office.

Implementation Loader

When you are raising a service by name using the <idl>com.sun.star.lang.ServiceManager</idl> service, the service manager decides an implementation name, code location and an appropriate loader to raise the code. It is commonly reading out of a persistent registry storage, for example, services.rdb (until OpenOffice.org 1.1.0 applicat.rdb), for this purpose. Previously, the regcomp tool has registered components into that registry during the LibreOffice setup. The tool uses a service called <idl>com.sun.star.registry.ImplementationRegistration</idl> for this task.

A loader knows how to load a component from a shared library, a .jar or script file and is able to obtain the service object factory for an implementation and retrieve information being written to the registry. A specific loader defines how a component implementer has to package code so that it is recognized by UNO. For instance in C++, a component is a shared library and in Java it is a .jar file. In a yet to be developed loader, the implementer of the loader has to decide, what a component is in that particular language - it might as well be a single script file.

The <idl>com.sun.star.loader.XImplementationLoader</idl> interface looks like the following:

interface XImplementationLoader: com::sun::star::uno::XInterface
{

  com::sun::star::uno::XInterface activate ( [in] string implementationName,
        [in] string implementationLoaderUrl,
        [in] string locationUrl,
        [in] com::sun::star::registry::XRegistryKey xKey )
        raises( com::sun::star::loader::CannotActivateFactoryException );

  boolean writeRegistryInfo ( [in] com::sun::star::registry::XRegistryKey xKey,
        [in] string implementationLoaderUrl,
        [in] string locationUrl )
        raises( com::sun::star::registry::CannotRegisterImplementationException );
};

The locationUrl argument describes the location of the implementation file, for example, a jar file or a shared library. The implementationLoaderUrl argument is not used and is obsolete. The registry key xKey writes information about the implementations within a component into a persistent storage. Refer to Write Registration Info Using a Helper Method for additional information.

The method writeRegistryMethod() is called by the regcomp tool to register a component into a registry.

The activate() method returns a factory <idl>com.sun.star.lang.XSingleComponentFactory</idl> for a concrete implementation name.

Implmentation loaders

The loader is often implemented in C/C++. When the loader is instantiated, it is responsible for starting up the language runtime, for example, Java VM, Python interpreter, through implementation. After starting up the runtime, the loader starts up the UNO language binding as discussed in the previous chapter, and bridge the XRegistryKey interface and the initial factory interface.

Shared Library Loader

This section discusses the loader for local components written in C++ that are loaded by the <idl>com.sun.star.loader.SharedLibrary</idl> service. Every type safe programming language that stores its code in shared libraries should implement the bridge with environments and mappings as discussed in chapters UNO Bridge and UNO C++ Bridges. These programming languages can reuse the existing loader without creating a new one.

When the shared library is mapped into the running process, for example, using osl _loadModule(), the shared library loader retrieves defined C symbols out of the library to determine the compiler that built the code. This function symbol is called component_getImplementationEnvironment(). When the code is compiled with the Microsoft Visual C++ compiler, it sets a pointer to a string called "msci", with gcc 3.0.1 a string "gcc3" which is a UNO environment type name. A UNO environment is connected with the code that runs in it, for example, the code compiled with gcc3 runs in the UNO environment with type name gcc3.

In addition to the environment type name, a UNO environment defines a context pointer. The context pointer and environment type name define a unique UNO environment. Although the context pointer is mostly null, it is required to identify the environments apart for the same type, for example, to identify different Java virtual machine environments when running a UNO object in two different Java virtual machines within the same process. Both environments have the same type name "java", but different context pointers. In local (C++) code, the context pointer is irrelevant, that is, set to null. The type name determines the UNO runtime environment.

When the loader knows the environment the code comes from, it decides if bridging is required. Bridging is needed if the loader code is compiled with a different compiler, thus running in a different environment. In this case, the loader raises a bridge to speak UNO with the component code.

The loader calls on two more functions related to the above XimplementationLoader interface. All of these symbols are C functions and have the following signatures:

extern "C" void SAL_CALL component_getImplementationEnvironment(
      const sal_Char ** ppEnvTypeName, uno_Environment ** ppEnv );
extern "C" sal_Bool SAL_CALL component_writeInfo(
      void * pServiceManager, void * pRegistryKey );
extern "C" void SAL_CALL component_getFactory(
      const sal_Char * pImplName, void * pServiceManager, void * pRegistryKey );

The latter two functions expect incoming C++-UNO interfaces, therefore the loader needs to bridge interfaces before calling the functions as stated above.

Bridges

The loader uses the cppu core runtime to map an interface, specifying the UNO runtime environment that needs the interface mapping. The cppu core runtime raises and connects the appropriate bridges, and provides a unidirectional mapping that uses underlying bidirectional bridges. Under Unix, the name of the bridge library follows the naming convention lib<SourceEnvironment>_<TargetEnvironment>., Under Windows, <SourceEnvironment>_<TargetEnvironment>.dll is used. For instance, libgcc3_uno.so is the bridge library for mappings from gcc3 to binary UNO, and msci_uno.dll maps from MS Visual C++ to binary UNO. The bridges mentioned above all bridge to binary UNO. Binary UNO is only used as an intermediate environment. In general, do not program binary UNO in clients. The purpose is to reduce the number of necessary bridge implementations. New bridges have to map only to binary UNO instead of all conceivable bridge combinations.

Help with New Language Bindings

Every UNO language binding is different, therefore only most important points were stressed, that is, those that are likely to appear in almost every language binding implementation. Object issues, such as lifetime, object identity, any handling, and bootstrapping were not discussed, because they are too language dependent. For more information on these issues, subscribe to the dev@udk.openoffice.org mailing list to discuss these issues for your programming language.

Differences Between UNO and Corba

This subsection discusses the differences between UNO and CORBA by providing the fundamental differences and if the different concepts could be mapped into the world of the other model. Consider the following feature comparison. The column titled "Mapping possible" states if a feature could be mapped by a (yet to be developed) generic bridge.

UNO CORBA Mapping possible
multiple inheritance of interfaces no yes yes
inheritance of structs yes no yes
inheritance of exceptions yes no yes
mandatory base interface for all interfaces yes no yes
mandatory base exception for all exceptions yes no yes
context of methods no yes no
char no yes yes
8 bit string no yes yes
array no yes yes
union no yes yes
assigned values for enum yes no yes
meta type 'type' yes no yes
object identity yes no no
lifetime mechanism yes no no
succession of oneway calls yes no no
in process communication yes no no
thread identity yes no no
customized calls no yes yes
less code generation yes no no
  • Multiple Inheritance
CORBA supports multiple inheritance of interfaces, whereas UNO only supports single inheritance.
Mapping: Generates an additional interface with all methods and attributes of the inherited interfaces that must be implemented in addition to the other interfaces.
  • Inheritance of Structs
In contrast to CORBA, UNO supports inheritance of struct types. This is useful to define general types and more detailed subtypes.
Mapping: Generate a struct with all members, plus all members of the inherited structs.
  • Inheritance of Exceptions
CORBA does not support inheritance for exceptions, whereas UNO does. Inheritance of exceptions allows the specification of a complex exception concept. It is possible to make fine granular concepts using the detailed exceptions in the layer where they are useful and the base exception in higher levels. The UNO error handling is based on exceptions and with inheritance of exceptions it is possible to specify 'error classes' with a base exception and more detailed errors of the same 'error class' that inherit from this base exception. On higher level APIs it is enough to declare the base exception to specify the 'error class' and it is possible to support all errors of this 'error class'.
Mapping: Generates an exception with all members, plus all members of the inherited exceptions. This is the same solution as for structs.
  • Mandatory Base Interfaces
UNO specifies a mandatory base interface for all interfaces. This interface provides acquire() and release() functions for reference counting. The minimum life time of an object is managed by means of reference counting.
  • Mandatory Base Exception
UNO specifies a mandatory base exception for all exceptions. This base exception contains a string member Message that describes the reason for the exception in readable format. The base exception makes it also possible to catch all UNO exceptions separately.
  • Method Context
CORBA supports a request context. This context consists of a name-value pair which is specified for methods in UNOIDL. The context is used for describing the current state of the caller object. A request context provides additional, operation-specific information that may affect the performance of a request.
  • Type char
UNO does not support 8-bit characters. In UNO, char represents a 16-bit unicode character.Mapping: To support 8-bit characters it is possible to expand the TypeClass enum to support 8-bit characters and strings. The internal representation does not change anything, the TypeClass is only relevant for mapping.
  • 8 bit string
UNO does not support 8-bit strings. In UNO, string represents a 16-bit unicode string.
Mapping: The same possibility as for char.
  • Type array
UNO does not support arrays at the moment, but is planned for the future.
  • Type union
UNO does not support unions at the moment, but is planned for the future.
  • Assigned Values for enums
UNO supports the assignment of values for enum values in IDL. This means that it is possible to use these values directly to specify or operate with the required enum value in target languages supporting this feature, for example, . C, C++.
Mapping: Possible by using the names of the values.

UNO Design Patterns and Coding Styles

This chapter discusses design patterns and coding recommendations for LibreOffice. Possible candidates are:

  • Singleton: global service manager, Desktop, UCB
  • Factory: decouple specification and implementation, cross-environment instantiation, context-specific instances
  • Listener: eliminate polling
  • Element access: it is arguable if that is a design pattern or just an API
  • Properties: solves remote batch access, but incurs the problem of compile-time type indifference
  • UCB commands: universal dispatching of content specific operations
  • Dispatch commands: universal dispatching of object specific operations, chain of responsibility

Double-Checked Locking

The double-checked locking idiom is sometimes used in C/C++ code to speed up creation of a single-instance resource. In a multi-threaded environment, typical C++ code that creates a single-instance resource might look like the following example:

#include "osl/mutex.hxx"

T * getInstance1()
{
    static T * pInstance = 0;
    ::osl::MutexGuard aGuard(::osl::Mutex::getGlobalMutex());
    if (pInstance == 0)
    {
        static T aInstance;
        pInstance = &aInstance;
    }
    return pInstance;
}

A mutex guards against multiple threads simultaneously updating pInstance, and the nested static aInstance is guaranteed to be created only when first needed, and destroyed when the program terminates.

The disadvantage of the above function is that it must acquire and release the mutex every time it is called. The double-checked locking idiom was developed to reduce the need for locking, leading to the following modified function. Do not use.:

#include "osl/mutex.hxx"

T * getInstance2()
{
    static T * pInstance = 0;
    if (pInstance == 0)
    {
        ::osl::MutexGuard aGuard(::osl::Mutex::getGlobalMutex());
        if (pInstance == 0)
        {
            static T aInstance;
            pInstance = &aInstance;
        }
    }
    return pInstance;
}

This version needs to acquire and release the mutex only when pInstance has not yet been initialized, resulting in a possible performance improvement. The mutex is still needed to avoid race conditions when multiple threads simultaneously see that pInstance is not yet initialized, and all want to update it at the same time. The problem with getInstance2 is that it does not work.

Assume that thread 1 calls getInstance2 first, finding pInstance uninitialized. It acquires the mutex, creates aInstance that results in writing data into aInstance's memory, updates pInstance that results in writing data into pIntance's memory, and releases the mutex. Some hardware memory models a write the operations that transfer aInstance's and pInstance's data to main memory to be re-ordered by the processor executing thread 1. Now, if thread 2 enters getInstance2 when pInstance's data has already been written to main memory by thread 1, but aInstance's data has not been written yet (remember that write operations may be done out of order), then thread 2 sees that pInstance has already been initialized and exits from getInstance2 directly. Thread 2 dereferences pInstance thereafter, accessing aInstance's memory that has not yet been written into. Anything may happen in this situation.

In Java, double-checked locking can never be used, because it is broken and cannot be fixed.

In C and C++, the problem can be solved, but only by using platform-specific instructions, typically some sort of memory-barrier instructions. There is a macro OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER in osl/doublecheckedlocking.h that uses the double-checked locking idiom in a way that actually works in C and C++.

#include "osl/doublecheckedlocking.h"
#include "osl/mutex.hxx"

T * getInstance3()
{
    static T * p = 0;
    T * pInstance = p;
    if (p == 0)
    {
        ::osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex());
        p = pInstance;
        if (p == 0)
        {
            static T aInstance;
            p = &aInstance;
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
            pInstance = p;
        }
    }
    else
        OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
    return p;
}

The first (inner) use of OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER ensures that aInstance's data has been written to main memory before pInstance's data is written, therefore a thread can not see pInstance to be initialized when aInstance's data has not yet reached main memory. This solves the problem described above.

The second (outer) usage of OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER is required to solve a problem concerning the reordering on Alpha processors.

Note pin.svg

Note:
For more information about this problem, see Reordering on an Alpha processor by Bill Pugh (https://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html) and Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects by Douglas C. Schmidt et al (Wiley, 2000). Also see the Usenet article Re:Talking about volatile and threads synchronization by Davide Butenhof (October 2002) on why the outer barrier can be moved into an else clause.

If you are coding in C++, there is an easier way to use double-checked locking without worrying about the fine points. Use the rtl_Instance template from rtl/instance.hxx:

#include "osl/getglobalmutex.hxx"
#include "osl/mutex.hxx"
#include "rtl/instance.hxx"

namespace {
    struct Init()
    {
        T * operator()()
        {
            static T aInstance;
            return &aInstance;
        }
    };
}

T * getInstance4()
{
    return rtl_Instance< T, Init, ::osl::MutexGuard, ::osl::GetGlobalMutex >::create(
    Init(), ::osl::GetGlobalMutex());
}

Note that an extra function class is required in this case. The documentation of rtl_Instance contains further examples of how this template can be used.

Note pin.svg

Note:
If you are looking for more general information, the article The "Double-Checked Locking is Broken" Declaration (https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) is a good source on double-checked locking, while Computer Architecture: A Quantitative Approach, Third Edition by John L. Hennessy and David A. Patterson (Morgan Kaufmann, 2002) and UNIX® Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers by Curt Schimmel (Addison-Wesley, 1994) offer detailed information about hardware memory models.

Heckert GNU white.svg

Content on this page is licensed under the Public Documentation License (PDL).