UNOIDL is a declarative language to define data types abstractly. It is
similar to CORBA-IDL or MIDL, but differs slightly. If you are not familiar with IDL,
you should browse a little in the api-idl-files
to get an impression.
UNOIDL allows the developer to define UNO-datatypes independent of a programming language.
Introducing a new datatype in UNOIDL is necessary to make it available in all
programming languages supported by UNO. User-defined data-types are structs, enums,
constant groups, exceptions, interfaces and services.
Interfaces
Interfaces contain methods and/or attributes. Interfaces ( or better interfacemethods and
interfaceattributes ) must be filled with "flesh" by implementations.
Interfaces are always transported by reference (e.g. when an interface is mapped
to another process, only a reference is mapped). All calls to the interface are transported
to the original object (save acquire,release and [under certain
circumstances] queryInterface-calls ).
Attributes behave like methods (set/get-methods can replace attributes), so attributes are
never meant by value as someone may think. If you are
in an UNO-environment, that natively supports attributes, UNO-attributes are a very
fine thing to have. As a drawback to set/get methods,
no exceptions can be specified at attributes.
All interfaces specified in UNOIDL must be derived from
com.sun.star.uno.XInterface .
Interface names should start with a X prefix.
Structs
A struct is a structured container for data (very much like C-structs).
Structs are always transported by value ( e.g. as a method-argument or as a return value ).
Sequence
For every type in UNOIDL exists a sequence (or array) of this type, which
itself is a new type.
Sequences are transported by value. The number of elements within a sequence is
determined at runtime. There are no C-style arrays in UNOIDL.
Exceptions
Exceptions is the UNO-mechanism to propagate errors to the caller. As structs, exception
are a structured container for data. Exceptions cannot be a passed as a return value or
method argument, but they can be transported in an any. There are two kinds
of exceptions
- User-defined exceptions
The designer of an interface is responsible for declaring exceptions for every possible
error condition that might occur. He/she should declare different exceptions for
different reasons, if it makes sense for the caller to distinguish the error conditions.
The implementation may throw the specified exceptions and exceptions derived from
the specified exceptions.
The implementation must not throw unspecified exceptions. This implies, that if no
exception is specified, the implementation must not throw an exception ( save
RuntimeExceptions see below).
When a user-defined exception is thrown, the object should be left in the
state as it has been before the call. If this cannot be guaranteed, the exception
specification must describe the state of object; this is however disapproved, because
it is very nasty for the caller.
Every exception must be derived from com.sun.star.uno.Exception.
module com { module sun { module star { module uno {
exception Exception
{
/** specifies a detailed message of the exception
or an empty string if the callee does not describe
the exception.
*/
string Message;
/** an object that describes the reason for the
exception. May be NULL if the callee does not
describe the exception.
*/
com::sun::star::uno::XInterface Context;
};
}; }; }; };
|
The exception has two members. Message should contain a detailed human readable
description of the error, which is very nice for debugging purposes, though in general
it cannot be evaluated at runtime. The Context member should contain the object, that
threw the exception.
Example :
module com { module sun { module star { module beans {
[ uik(E227A3A8-33D6-11D1-AABE00A0-249D5590),
ident( "XPropertySet", 1.0 ) ]
interface XPropertySet: com::sun::star::uno::XInterface
{
[...]
/** @returns
the value of the property with the specified name.
@param PropertyName
This parameter specifies the name of the property.
@throws UnknownPropertyException
if the property does not exist.
@throws stardiv::uno::lang::WrappedTargetException
if the implementation has an internal reason for the
exception. In this case the original exception
is wrapped into that WrappedTargetException.
*/
[const] any getPropertyValue( [in] string PropertyName )
raises( com::sun::star::beans::UnknownPropertyException,
com::sun::star::lang::WrappedTargetException );
[...]
};
}; }; }; };
|
- Runtime exceptions
Runtime exceptions may be thrown by underlying bridges, if an unrecoverable error
has occurred ( e.g. an interprocess connection broke down ).
In UNO, the caller in general cannot know, if an object lives in his own process or
not, so this leads to the following rule :
The caller of an UNO-method must be aware, that every call may throw
a com.sun.star.uno.RuntimeException (except acquire and release).
This is independent of how many calls have been completed successfully before.
Every caller should ensure, that its own object keeps in a consistent state even if
a call to another object replied with a runtime exception. The caller should also ensure
that no resource leaks occur (memory, file descriptor, etc.) in theses cases.
If a Runtime exception occurs, the caller does not know, whether the call has been
completed successfully or not.
The com.sun.star.uno.RuntimeException is derived from com.sun.star.uno.Exception.
An UNO-object-implementation is in general not allowed to throw a
com.sun.star.uno.RuntimeException (one of the very rare cases is the DisposedException,
see discussion about disposed objects). If it does ( maybe because the implementation
must throw an exception, but it was not specified ), it is a HACK. This in general occurs,
when the interface was poorly designed. The correct solution is to change the
interface ( if it was not made public before ) or to switch to a new set of interfaces
( which often is quite a lot of work ... ).
Exception should not be misused as a new kind of programming flow mechanism. As a rule of thumb
it should always be possible that during a session of a program no exception is thrown. If this
is not the case, the interface design should be reviewed.
Service
A service is an abstract component specification.
It contains a list of mandatory interfaces, that an implementation must export to
be a valid implementation of the service. Furthermore, it specifies relations between
interfaces. A service is uniquely identified by a service name . Additionally,
properties can be specified, that the service must support.
There can be multiple implementations of a service. One component may implement
multiple services.
Example :
module com { module sun { module star { module io {
/**
This service is a connection of an outputstream and
an inputstream. All data written through the outputstream
is buffered until it is read again
from the input stream. The buffer should
grow in 2 ^ x steps if necessary.
The maximum size of the buffer is 2 ^ 31 -1.
*/
service Pipe
{
interface com::sun::star::io::XOutputStream;
interface com::sun::star::io::XInputStream;
};
}; }; }; };
|
Here com.sun.star.io.Pipe is the service name.
Service and implementation names
In UNO there is the notion of a service name and an implementation name.
The service name uniquely identifies a service
as it was specified in IDL.
If you try to instantiate an object at the servicemanager using the service name,
you will get an object, that complies to the service specification. A service name
is often derived from the name of the main interface (if available),
the component should support(see example below), but should not have the X interfaceprefix.
The implementation name uniquely identifies one implementation of a service
( or multiple services ). The name is determined by the implementation, it does not
appear in IDL. The suggested naming convention is
company-prefix.comp.module.unique-name-in-module . If you try to instantiate
an object at the servicemanager using the implementation name , you will
get an instance of exactly that implementation. An implementation name is the
equivalent to a concrete class name in Java.
Example :
The SimpleRegistry-service allows to access a tree-structured-registry.
The service name is com.sun.star.registry.SimpleRegistry .
One implementation of this service can be found in the udk/stoc module.
Therefor the implementation name is
com.sun.star.comp.stoc.SimpleRegistry .
As a result of this concept, the servicemanager has the freedom to choose between
multiple implementations of a certain service, but the application developer still
has the possibility to access a certain implementation (however this should only
be necessary in exceptional cases).
UNOIDL vs CORBA-IDL/MIDL.
feature |
UNOIDL |
CORBA-IDL |
MIDL |
multiple inheritance of interfaces |
no |
yes |
no |
exceptions |
yes |
yes |
no |
exceptions with inheritance |
yes |
no |
no |
mandatory superinterface |
yes |
no |
yes |
C-style arrays |
no |
yes |
? |
Life cycle concept |
yes (refcounting) |
no |
yes (refcounting) |
|
com.sun.star.uno.XInterface
com.sun.star.uno.XInterface must be used as a base interface for all other interfaces. XInterface
offers three methods.
module com { module sun { module star { module uno {
[ uik(E227A391-33D6-11D1-AABE00A0-249D5590), ident( "XInterface", 1.0 ) ]
interface XInterface
{
/** queries for a new interface to an existing UNO object.
*/
any queryInterface( [in] type aType );
/** increases the reference counter by one.
*/
[oneway] void acquire();
/** decreases the reference counter by one.
When the reference counter reaches 0, the object gets deleted.
*/
[oneway] void release();
};
}; }; }; };
|
acquire and release handle the lifecycle of an object. acquire increases the refcount
of an object by one, release decreases it by one. If the refcount drops to zero, the object
is destroyed. Note that it is not necessary, that every acquire and release call is delegated
to the original object, e.g. a proxy, that has once acquired the original object,
should take care of acquire and release calls, until its own refcount drops to zero (then it
sends a release to the original object to compensate the first acquire).
acquire and release must not throw exceptions ( even not RuntimeException ).
This is not allowed, because it
is not clear for the caller, whether the refcount was modified or not, which is fatal
for a system based on refcounting.
Note that here each language binding is free to choose how to implement this. E.g. a Java programmer
does not need to take care of refcounting.
The queryInterface method allows to asks an object for a certain interface. The object may
either return a valid reference or an empty reference. There are certain specifications,
a queryInterface must not violate.
- If queryInterface on a specific object has once returned a valid interface reference for
a given type, it must return the a valid reference for any successive queryInterface
call with the same type.
- If queryInterface on a specific object has once returned a null reference for a
given type, it must always return a null reference for the same type.
- If queryInterface on a reference A returns reference B, queryInterface on B for Type A must
return interface reference A or calls made on the returned reference must be equivalent
to calls made on reference A.
- If queryInterface on a reference A returns reference B, queryInterface on A and B for XInterface
must return the same interface reference (object identity).
com.sun.star.lang.XComponent
A general problem of a reference counted system is how to deal with cyclic references. Object A keeps
a reference on object B and B keeps one (direct or indirect) on object A. Even if all external
references to A and B are released, the objects are not destroyed, which results in a resource leak.
In UNO the developer must explicitly decide, when to break cyclic references.
To support this concept, there exists
the com.sun.star.lang.XComponent-interface.
module com { module sun { module star { module lang {
interface XEventListener: com::sun::star::uno::XInterface
{
/** gets called when the broadcaster is about to be disposed.
All listeners and all other
objects which reference the
broadcaster should release the references.
*/
void disposing( [in] com::sun::star::lang::EventObject Source );
};
/** controls the lifetime of components.
Actually the real lifetime of an UNO object is controlled
by references kept on interfaces of this object. But there
are two distinct meanings in keeping a reference to an
interface:
1st to own the object and 2nd to know the object. Especially
in case of cyclic references, the objects would never
get destroyed.
You are only allowed to keep references of interfaces
to UNO objects if you are by definition the owner of that
object or your reference is very temporary or you have
registered an EventListener at that object and cleared
the reference when "disposing" is called.
*/
[ uik(E227A39F-33D6-11D1-AABE00A0-249D5590), ident( "XComponent", 1.0 ) ]
interface XComponent: com::sun::star::uno::XInterface
{
/** The owner of a component calls this method to get rid of it.
Only the owner of this object calls the dispose method if
the object should be destroyed. All objects and components
must release the references to the objects. If the object is a
broadcaster, then all listeners are removed and the method
is called on all listeners.
*/
void dispose();
/** adds an event listener to the object.
The broadcaster fires the disposing method of this listener
if the method is called.
*/
void addEventListener( [in] XEventListener xListener );
/** removes an event listener from the listener list.
It is a "noop" if the specified listener is not registered.
*/
void removeEventListener( [in] XEventListener aListener );
};
}; }; }; };
|
When an object supports the XComponent interface, it allows other objects to add
themselves as XEventListener. When the dispose-method gets called, the objects notifies
all listeners via the XEventListener.disposing()-method and releases all interface references
thus breaking the cyclic references.
Note that an disposed object is in general unable to comply with its specification. So it
is necessary to ensure, that an object is not disposed before calling it. A useful concept for
this is an owner/user concept.
Only the owner of an object is allowed to call dispose and there
can be only one owner per object. So the owner is always free to call the object. The user of
an object knows, that the object may get disposed at some unspecified time. To get
to know, when an object gets disposed, it adds itself as event listener and releases the interface
reference to the object, when it gets notified. In this case, the user should not call
removeEventListener (the disposing object releases the reference to the user anyway).
If an disposed object gets called, it should behave as passive as possible. If there are
e.g. calls like add/remove listener, they should be ignored. However, if methods are called that
make it impossible for the object to comply with its interface specification, it should throw
a com.sun.star.lang.DisposedException , which is derived from RuntimeException. Note that this
is one of the very rare cases, when an implementation should throw a RuntimeException. Note that
the above described situation may occur in a multithreaded environment (except calls to the object
are externally synchronized ).
The owner/user concept may not be appropriate in any case, especially when there are more
than one possible owners. In theses cases, there should be no owner but only users. Note,
that it is then possible, that dispose gets called twice (in a multithreaded scenario).
XComponent.dispose gets also called, when it has not been called before
and the refcount of the object drops to zero. This may happen, when the listeners do not
hold a reference on the object.
XComponent does not need to be implemented, when there is only one owner and no further users.
com.sun.star.lang.XAggregation
Aggregation denotes the ability to stick two or more objects together and behave like
one single object, though the interfaces of all objects are accessible from the outside.
Objects are aggregated in a hierarchical way, we denote the object that aggregates as the master
and the object(s), that is/ are aggregated as the slave(s), as sketched here:
Slave objects implement a special interface called com.sun.star.uno.XAggregation .
The master has major control over the object, i.e. all calls to queryInterface() are delegated
to the master.
If the master does not support the demanded interface, it calls queryAggregation()
on its slaves.
C++ implementation warning:
If you use the cppu::OWeakAggObject
([include cppuhelper/weakagg.hxx], or any WeakAggImplHelperN<> implementation helper,
[include cppuhelper/implbaseN.hxx]) to implement
the com.sun.star.uno.XAggregation interface making your object aggregatable,
then beware of reference counting.
After the aggregation of objects has been done, the new unified object has to behave
like one and also the lifecycle has to be controlled in a unified way.
This is commonly implemented that after aggregation, only the master's reference count
will be modified.
The master will hold a hard reference on its aggregated slaves.
The OWeakAggObject slaves store a weak references to its delegator, i.e. its master thus
not modifying the master's reference count.
Any time there is an acquire() or release() call on a master
interface, then the master reference count is modified;
any time there is an acquire() or release() call on a slave
interface, then the master reference count is modified by delegation of that call, too.
So these calls modify uniquely one reference count and everything is fine,
if the reference count of all salves is one (1) right after the aggregation.
By the meaning of one the only interface reference to slaves is held by the master
(which has hard references to its slaves).
Any other references having modified the reference count, e.g. to three, will corrupt
the reference count of the master in a undefined manner!
They will perform a release() which will be delegated to the master!
These bugs are hard to fix, so beware of this.
As a general rule: only use aggregation in a well defined manner when constructing
a new object.
Don't use aggregation randomly with objects which are already heavily in use!
This concerns also a UNO specification: The set of interfaces an UNO object is supporting
does not change during the lifetime of that object!
So at the time an object is already in use and interfaces are published to other objects,
it is not allowed to support more interfaces by aggregation.
com.sun.star.lang.XUnoTunnel
The com.sun.star.lang.XUnoTunnel interface allows to access implementation
dependent data ( in general the this-pointer of an object ).
XUnoTunnel.getSomething takes a byte sequence as argument. This bytesequence
contains an identifier, that both the caller and the implementer must know. The implementer
returns the this pointer of the object,
if the bytesequence is equal to the bytesequence, the implementer has stored with the object.
This interface is usually used, to distinguish, if behind certain interface reference
hides on object you know, or if it is a completely unknown object.
See the C++ introduction about this interface, to see an example, how it works.
Note : The use of this interface
is highly disapproved and is nothing else than a bad hack, because components
that rely on the XUnoTunnel-interface in general can't be exchanged. It is only an admittance
to existing code, that can be split into components only with a large amount of work. But if
you are designing interfaces from scratch, DON'T USE IT !.
module com { module sun { module star { module lang {
/** the interface to tunnel UNO. This means providing access to data
or something else, which is not specified in the UNO-IDL.
*/
[ uik(22260a3c-e868-11d3-86c60050-04d8bbbe), ident( "XUnoTunnel", 1.0 ) ]
interface XUnoTunnel: com::sun::star::uno::XInterface
{
/** call this method to get something which is not specified in UNO.
The identifier specifies how the return is specified.
The identifier must be global unique, because it is unknown
where the implementation lies.
*/
hyper getSomething( [in] sequence< byte > aIdentifier );
};
}; }; }; };
|
Mandatory interfaces for a service
Every service should implement the interfaces
com.sun.star.lang.XServiceInfo and com.sun.star.lang.XTypeProvider.
module com { module sun { module star { module lang {
/** provides information regarding which services are implemented.
*/
[ uik(E227A3E0-33D6-11D1-AABE00A0-249D5590),
ident( "XServiceInfo", 1.0 ) ]
interface XServiceInfo: com::sun::star::uno::XInterface
{
/** @returns
an identifier for the implementation of this object.
In general this should be a qualified identifier like
"com.sun.star.comp.office.writer.SwTextDocument".
*/
string getImplementationName();
/** asks whether a service is supported or not.
*/
boolean supportsService( [in] string ServiceName );
/**
@returns a sequence of the names of all supported services.
This includes the services which are indirectly supported because
they are part of the specification of directly supported services.
*/
sequence getSupportedServiceNames();
};
[ uik(B163A7B0-C432-11d3-9F070050-04D8BBC7),
ident( "XTypeProvider", 1.0 ) ]
interface XTypeProvider: com::sun::star::uno::XInterface
{
/** @returns
Sequence of all types (usually interface types)
provided by the object. I
mportant: If the object aggregates other objects
the sequence also has to contain all types
supportet by the aggregated objects.
*/
sequence< type > getTypes();
/** @returns
a UUID as sequence of 16 bytes as an Id that can be
used to unambiguously distinguish between two sets
of types, e.g. to realise hashing functionality
when the object is introspected. Two objects that
return the same UUID also have to return the same
set of types in getTypes(). If an unique
implementation Id cannot be provided this method has
to return an empty sequence.
Important: If the object aggregates other objects
the UUID has to be unique
for the whole combination of objects.
*/
sequence getImplementationId();
};
}; }; }; };
|
UNO-runtime environment and com.sun.star.lang.XMain
[...] TODO
|
Events
The event concept is almost identical to the Java event concept.
Events occur at one object, e.g. a property is changed or a user mouse move event. These events
should be broadcasted to other interested objects. In general there is a 1 to n relation between
broadcaster and listener.
Vetoable events allows the listener to abort a certain action ( e.g. the
modification of a property or the closure of an document). Thus the notification
calls are synchronous calls ( performance hit for remote listeners ! ). If one listener vetos,
the action is aborted. The return value can give information about the abort reason.
The following values do NOT signal an abort: (any,void), (interfacereference,null),
(number,0),( boolean, true ), (string,empty). Vetoable listener methods should be
prefixed with approve.
There exist unicast- and multicast-event-broadcaster. Only one listener can be added
to the unicast-variant, which results in less flexibility and is therefor disapproved.
Unicast broadcaster throw a TooManyListenerException when a second listener is added.
In general the listeners methods should not throw exceptions.
Pay extreme attention in not locking any resources when firing listeners (e.g. mutexes),
because the deadlock-likelihood is very high.
Listeners may be added during firing the listeners. It is implementation dependent,
whether these listeners are notified or not by these event.
All listeners must be derived from com.sun.star.uno.XEventListener. All event-structs
should be derived from com.sun.star.uno.EventObject. The EventObject contains the
broadcaster of the message, which is in general the object, that the listener was added to.
Example :
struct MouseMoveEvent : EventObject
{
long X;
long Y;
};
interface XMouseMoveListener : XEventListener
{
// MouseMove occurred
void mouseMoved( [in]MouseMoveEvent Event );
};
struct ResizeEvent : EventObject
{
// do not use unsigned types, java doesn't support it
long width;
long height;
};
interface XResizeListener : XEventListener
{
// approve resize?
boolean approveResize( [in]ResizeEvent Event );
// resize occurred
void resized( [in]ResizeEvent Event );
};
interface XVclComponent : XInterface
{
void setLocation( [in] long X, [in] long Y );
// add Listener to the broadcaster list
void addMouseMoveListener( [in]XMouseMoveListener Listener );
// remove Listener from the broadcaster list
void removeMouseMoveListener( [in]XMouseMoveListener Listener );
// add Resize-Listener to the broadcaster list
void addResizeListener( [in]XMouseMoveListener Listener );
// remove Resize-Listener from the broadcaster list
void removeResizeListener( [in]XMouseMoveListener Listener );
};
|
Properties
Beside interfaces, uno-objects can export properties.
E.g. in services, properties can be specified beside interfaces.
Properties are in general implemented using
XPropertySet
,
XFastPropertySet
and
XMultiPropertySet
interfaces if the property concept is not supported directly by the language.
Properties allow generic tools ( such as a property editor )
to modify UNO-components at runtime.
There exists 3 property categories :
- Simple properties
Simple properties can simply be modified. No one can get a notification of this change.
- Bound properties
On modification, a bound property fires a PropertyChangeEvent to all listeners.
Listeners can be added using the
add/removePropertyChangeListener( string propertyName, XPropertyChangeListener )
methods at the XPropertySet interface.
If a property is set to a value it already had before,
the listeners are not fired.
- Constrained properties
For a constrained property, XVetoableChangeListener can be added/removed.
A constrained property fires before the property is changed. If one of the listeners
does not want the property to change, the listener must throw a PropertyVetoException.
If a property is set to a value it already had before,
the listeners are not fired.
|