LibreOffice Developer's Guide: Chapter 5 - Advanced UNO
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.
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.
- 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.
- 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).
- 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 com.sun.star.beans.XPropertySet 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 (com.sun.star.script.Converter), and method invocations (com.sun.star.script.Invocation and com.sun.star.script.XInvocation). Furthermore, it supplies runtime type information and creates instances of certain UNO types, such as structs (com.sun.star.reflection.CoreReflection).
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 com.sun.star.bridge.UnoUrlResolver 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.
- 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.
- 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.
- 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).
- 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).
- 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 com.sun.star.uno.XInterface, 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 com.sun.star.lang.XTypeProvider 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 com.sun.star.script.Converter supporting the interface com.sun.star.script.XTypeConverter provides basic functionality that is important in the reflection context. It converts values to a particular type. For the method convertTo(), the target type is specified as type, allowing any type available in the UNO type system. The method convertToSimpleType() converts a value into a simple type that is specified by the corresponding com.sun.star.uno.TypeClass. If the requested conversion is not feasible, both methods throw a com.sun.star.script.CannotConvertException.
CoreReflection Service
The service com.sun.star.reflection.CoreReflection supporting the interface com.sun.star.reflection.XIdlReflection is an important entry point for the Uno Reflection API. The XIdlReflection
interface has two methods that each return a com.sun.star.reflection.XIdlClass 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 com.sun.star.reflection.XIdlField2 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 com.sun.star.beans.Introspection supporting the interface com.sun.star.beans.XIntrospection 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 com.sun.star.beans.XIntrospectionAccess 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 com.sun.star.beans.XIntrospectionAccess 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
.
com.sun.star.beans.XIntrospectionAccess also supports a categorization of properties and methods. For instance, it is possible to exclude "dangerous" methods, such as the reference counting methods acquire() and release() 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 com.sun.star.beans.XPropertySet interface is used that can be queried from the queryAdapter() method. This method also provides adapter interfaces for other generic access interfaces like com.sun.star.container.XNameAccess and com.sun.star.container.XIndexAccess, if these interfaces are also supported by the original object.
Invocation
The service com.sun.star.script.Invocation supporting the interface com.sun.star.lang.XSingleServiceFactory 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 createInstanceWithArguments() method. The returned XInterface
can then be queried for com.sun.star.script.XInvocation2 derived from com.sun.star.script.XInvocation.
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 com.sun.star.script.Converter 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 com.sun.star.script.InvocationInfo struct type (methods getInfo()
and getInfoForName()
).
Members of struct com.sun.star.script.InvocationInfo | |
---|---|
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 com.sun.star.script.InvocationAdapterFactory supporting the interfaces com.sun.star.script.XInvocationAdapterFactory and com.sun.star.script.XInvocationAdapterFactory2 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 createAdapter() 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 createAdapter() method is used.
XTypeDescription
Internally, types in UNO are represented by the type type. This type also has an interface representation com.sun.star.reflection.XTypeDescription. A number of interfaces derived from XTypeDescription
represent types. These interfaces are:
- com.sun.star.reflection.XArrayTypeDescription
- com.sun.star.reflection.XCompoundTypeDescription
- com.sun.star.reflection.XEnumTypeDescription
- com.sun.star.reflection.XIndirectTypeDescription
- com.sun.star.reflection.XUnionTypeDescription
- com.sun.star.reflection.XInterfaceTypeDescription
- com.sun.star.reflection.XInterfaceAttributeTypeDescription
- com.sun.star.reflection.XInterfaceMemberTypeDescription
- com.sun.star.reflection.XInterfaceMethodTypeDescription
The corresponding services are com.sun.star.reflection.TypeDescriptionManager and com.sun.star.reflection.TypeDescriptionProvider. These services support com.sun.star.container.XHierarchicalNameAccess and asks for a type description interface by passing the fully qualified type name to the getByHierarchicalName() 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.
XInvocation Bridge
Scripting Existing UNO Objects
This section describes UNO bridges for type-unsafe (scripting) programming languages. These bridges are based on the com.sun.star.script.Invocation 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 com.sun.star.bridge.UnoUrlResolver 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.
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 com.sun.star.script.Invocation service exists for this purpose. It offers a simple interface com.sun.star.lang.XSingleServiceFactory that creates a proxy for an arbitrary UNO object using the createInstanceWithArguments()
method and passing the object the proxy acts for. Use the com.sun.star.script.XInvocation interface that is exported by this proxy to invoke a method on the UNO object.
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 invoke() 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 invoke() 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 com.sun.star.reflection.CoreReflection and the com.sun.star.script.Invocation service and the com.sun.star.beans.XMaterialHolder 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 com.sun.star.reflection.InvocationTargetException. The exception has an additional any member, that contains the exception that was really thrown.
Note that the XInvocation
proxy may throw a com.sun.star.script.CannotConvertException 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 com.sun.star.script.Invocation 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 invoke('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 com.sun.star.io.XInputStream.
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 invoke() method is used directly, for instance LibreOffice Basic, except if the called object expects an XInputStream
.
The com.sun.star.script.InvocationAdapterFactory 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 invoke() 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.
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 com.sun.star.loader.XImplementationLoader. 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 com.sun.star.script.Invocation and com.sun.star.script.Converter, and calls PyUNO_new()
, passing the local ComponentContext
, a reference to the XSingleServiceFactory
interface of com.sun.star.script.Invocation and a reference to the XTypeConverter
interface of com.sun.star.script.Converter.
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 ("com.sun.star.script.Invocation"), UNO_QUERY);
Reference<XTypeConverter> tmp_tc (tmp_msf->createInstance (
OUString ("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 com.sun.star.lang.ServiceManager 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 com.sun.star.registry.ImplementationRegistration 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 com.sun.star.loader.XImplementationLoader 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 com.sun.star.lang.XSingleComponentFactory for a concrete implementation name.
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.
This section discusses the loader for local components written in C++ that are loaded by the com.sun.star.loader.SharedLibrary 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 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 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()
andrelease()
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, theTypeClass
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.
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.