Uno/Article/Working with Environments, Mappings & Objects

From Apache OpenOffice Wiki
< Uno
Jump to: navigation, search

Environments, mappings and objects are at the heart of Uno. Understanding their relationship and how to use them is fundamental for understanding how Uno

  • transparently integrates different programming languages,
  • achieves remote transparency,
  • allows to trace one or multiple components objects (in preparation),
  • provides well defined back-doors for optimization purposes,
  • can implicitly handle contexts (such as the ComponentContext) (in preparation),
  • protects thread-unsafe objects (in preparation),
  • isolates thread-affine objects (in preparation),

and more.

Theory

The three entities are

The sets of available implementations of all of these three entities (environments, mappings, objects) are dynamically extendable.

Environments

Environments are fundamental to Uno. Environments

  • manage a set of objects, sharing some characteristics as the Binary Interface (OBI) or the "purpose", and
  • may be used to control the life cycle of any particular object belonging to an environment.

Environments are addressed by environment descriptors, e.g. "<OBI>[:purpose]*" An environment descriptor is basically composed of the OBI and zero or multiple purposes.

Examples for environments are:

  • "uno" - the environment managing Binary Uno standard OBI objects,
  • "gcc3" - the environment managing GCC3 C++ OBI objects,
  • "uno:unsafe" - the environment managing Binary Uno standard OBI thread-unsafe objects,
  • "jni:affine" - the environment managing JNI (Java Native Interface) OBI thread-affine objects.
  • "jni:affine:debug" - the environment managing JNI (Java Native Interface) OBI thread-affine debug objects.

Implementation Types

Environments requested from the Uno runtime are actually linear combined from exactly one OBI and zero or multiple purpose environments. E.g.

"gcc3:unsafe"

is backed by the GCC3 OBI environment implementation and the "unsafe" purpose environment implementation.

Environments having a purpose in their descriptors are actually called purpose environments, other environments may be called pure or pure OBI.

OBI Environment Implementations

OBI environment implementations do not control any state and are solely managing objects of a particular OBI.

Purpose Environment Implementations

Purpose environment implementations typically only manage objects of a default (in Binary Uno the "UNO") OBI, but do control state. Purpose environments alter their state when being activated respectively deactivated.

Life Cycle

Every environment is Uno runtime wide unique and global. Re-requesting a particular environment (e.g. "uno:unsafe") multiple times, always returns the same instance. An environments identity is the same as its description. An environment exists as long as an object or proxy is registered or an explicit reference is kept.

Activation

Environments may be activated either directly by being entered, or indirectly by being invoked. Depending on the particular purposes, semantics might differ (slightly). Purpose environments may control some global state, which may be altered in response to activation.

As only purpose environments control any state, their is actually no difference between activating two environments with different object OBIs but with the same purpose: "gcc3:unsafe" "uno:unsafe" Consequently, entering a "pure" OBI environment, such as "gcc3", has no effect at all.

Integrity

Direct manipulation (e.g. casting and calling) of an object of a particular environment must only be done, while the managing environment has been activated. In contrast, indirect manipulation, e.g. through the managing environment or a proxy, is guaranteed to always be safe.

Every object belongs to exactly one environment only.

Objects of different environments, even having the same OBI, must never be mixed, otherwise environment integrity may break (e.g. leading to a thread-safe object providing the same thread-unsafe object to multiple threads).

Substitution

Environments may be substituted with compatible environments. Environments are compatible if the to be substituted environments descriptor is a prefix of the substitutes descriptor, e.g.

"uno:unsafe" 

may be substituted with

"uno:unsafe:debug".

Naming

Environment naming has not been implemented for any Uno runtime yet. Naming is going to allow to have multiple environments of the same OBI and purpose, e.g.

"gcc3:unsafe;first_instance"

and

"gcc3:unsafe;second_instance"

This is going to enable the instantiation of different components implemented in the same OBI and requiring the same purposes into independent environments. E.g. two thread-unsafe C++ components may be instantiated into two independent "gcc3:unsafe" environments, allowing to be invoked in parallel, despite both being implemented as "C++:unsafe". This is also going to enable logging e.g. of inter-component calls of any two components.

Parameters

Environment parameters have not been implemented yet. Environment parameters are essential for things as simplifying remote access (see Uno/To-Dos#Features). Environment parameters may look like this:

"remote[socket,host=0,port=12345;urp]"

which would pass the string

"socket,host=0,port=12345;urp"

to the remote environment implementation.

Environment parameters could be passed to any OBI or purpose named in the environment descriptor, e.g.

"remote[socket,host=0,port=12345;urp]:debug[logfile=<filename>.log]"

would log all calls to the remote objects to the named log file.

Mappings

Mappings connect any two particular environments in a way, that an object of the source environment may be "mapped" to the destination environment, actually providing a representation of the source object in terms of the destination environment.

Implementations

Mappings are backed by mapping implementations, actually mapping implementations are purpose independent, and only map from one OBI to another. As in most cases it is needed to map back and forth, mappings are mostly implemented as bridges ("bi-directional mappings").

Cascading

A mapping may not only be direct, but may very well be composed of multiple "smaller" mappings. E.g. if the Binary Uno runtime can not find any direct mapping, it tries to concatenate multiple mappings. See the cascaded mapping specification for details.

The following mapping

"gcc3" -> "gcc3:unsafe" 

gets actually composed as

"gcc3" -> "uno" -> "uno:unsafe" -> "gcc3:unsafe"

Life Cycle

Mappings live as long as objects are mapped or an explicit reference is hold. Releasing the last mapped object and the last explicit reference also releases the mapping. Re-requesting a particular mapping (e.g. "uno:unsafe" -> "uno") multiple times, always returns the same instance.

Objects

Objects are the third stand in the Uno architecture. Where environments deal with the management of objects, while mappings know how to forward a particular object from one OBI respectively purpose to another, objects are the functional parts.

Implementation

Basically, (Uno) objects may be implemented anywhere, in components, in libraries, in applications or in the network. Accessing and creating Uno objects solely through other Uno objects guarantees environment integrity. Unfortunately, only Uno components ensure the by-object-only access, while applications, libraries and network sockets may very well by-pass this. Therefor precautions need to be taken, to still ensure that environmental integrity can not break.

Specialization: Libraries as well as applications, may be implemented as environment-specialized, as environment-any, or something in between, e.g.

  • a function accepting and returning "uno" objects only is specialized on the "uno" environment,
  • a function acceptiong and returning "gcc3:unsafe" objects only is specialized on the "gcc3:unsafe" environment,
  • a function accepting and returning "gcc3[:<purpose>]*" objects only is partly specialized, namely on the GCC3 C++ OBI, only,
  • a function accepting and returning "<OBI>[:<purpose>]*" objects is basically able to deal with any kind of objects, it is not specialized at all and is therefor environment-any.

Mapping: Actually, any environment-specialized library (or function) can easily be wrapped into any other kind of environment-specialization, as long as the necessary mapping is available. Hence, exactly this is what the Uno runtime does automatically, in case Uno objects are used.

Type Safety: Depending on the capabilities of the particular programming language and language binding, correct dealing with environment-specialization may be enforced at compilation time, e.g. by dedicated reference types. Actually, it is recommended to use the most specific type as possible, or, the other way around, to be as specialized as the API only, unfortunately this is not always possible because of missing language-feature support.

An environment-any or partly -specialized library (or function) needs to request the lacking information at runtime, from the Uno runtime, this may be done by invoking the getCurrentEnvironment runtime function.

Note: Unfortunately, no type safe purpose aware references are yet available for any Uno language binding. So, this is planned.

Note: It is planned, to support the selection of an implementation environment, determining an implementations OBI and purpose environment at compile time, e.g. for 'C' like languages by a macro or an include. Some experiments have been done, so no final decisions have been made yet.

Life Cycle

An objects life cycle may depend on the base system, systems without garbage collection typically use reference counting, while systems with garbage collection keep objects alive as long as they are referenced only.

Invocation

An object may only be invoked, if the managing environment has been activated. Typically, a cast precedes the concrete invokation of an object.

Practice

The various Uno Implementations provide varying levels of convenience when dealing with Environments, Mappings and Objects. The most important and typical usage patterns are described below, including examples.

Environments

Environments can be

  • requested,
  • used to manage objects,
  • activated, and
  • implemented.

Access

C++ Example
#include <uno/environment.hxx>
...
{
  uno::Environment env(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("c++:unsafe")));
  ...
}

Usage

C++ Example
#include <uno/environment.hxx>
...
{
  uno::Environment env(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("c++:unsafe")));
 
  void * pObject = ...
 
  // Acquire pObject.
  env.get()->pExtEnv->acquireInterface(env.get()->pExtEnv, pObject);
 
  // Release pObject.
  env.get()->pExtEnv->releaseInterface(env.get()->pExtEnv, pObject);
 
  ...
}

Activation

Entering
Environment Guard

The environment guard enters the given environment during construction and leaves it when destructed.

=C++ Example=

See Uno/Cpp/Spec/Environment Guard for details about the Environment Guard.

#include <cppu/EnvGuards.hxx>
...
{
  cppu::EnvGuard cppUnsafe(uno::Environment(RTL_CONSTASCII_USTRINGPARAM("c++:unsafe")));
  // From here onwards the environment is entered, until the end of the block.
  ...
}
Environment AntiGuard

The environment anti guard leaves the current environment during construction and re-enters it when destructed.

=C++ Example=

See Uno/Cpp/Spec/Environment AntiGuard for details about the Environment AntiGuard.

#include <cppu/EnvGuards.hxx>
...
{
  cppu::AntiEnvGuard antiEnvGuard;
  // From here onwards the previous environment has been left, at the end of the block it gets re-entered.
  ...
}
Invoking

Uno/Cpp/Snippet/Invoking an Environment

Implementation

Purpose Environment Implementation Helper

This helper allows the easy implementation of purpose environments. The important methods to implement are

  • enter - this method is called to "enter" an environment, it actually does not have parameters,
  • leave - this method is called to "leave" an environment, it actually does not have parameters,
  • callInto - this method is called with a function and its parameters, the given functions is to be called after the environment has been entered,
  • callOut - this method is called with a function and its parameters, this given function is to be called after the environment has been left,
  • isValid - this method gives information about being valid to actually invoke an object of this environment.

These methods have to be implemented according to the desired semantics.

C++ Example

See Uno/Cpp/Spec/PurpenvHelper for details about the implementation helper.

#include <cppu/helper/purpenv/Base.hxx>
#include <cppu/helper/purpenv/Mapping.hxx>
...
class SAL_DLLPRIVATE MyBridge : public cppu::helper::purpenv::Base
{
public:
	explicit MyBridge(uno_Environment * pEnv);
 
	virtual void v_callInto_v(uno_EnvCallee * pCallee, va_list param);
	virtual void v_callOut_v (uno_EnvCallee * pCallee, va_list param);
 
	virtual void v_enter(void);
	virtual void v_leave(void);
 
	virtual int  v_isValid(rtl::OUString * pReason);
};
 
MyBridge::MyBridge(uno_Environment * pEnv)
{
   ...
}
 
void MyBridge::v_callInto_v(uno_EnvCallee * pCallee, va_list param)
{
  ...
}
 
void MyBridge::v_callOut_v(uno_EnvCallee * pCallee, va_list param)
{
  ...
}
 
void MyBridge::v_enter(void)
{
  ...
}
 
void MyBridge::v_leave(void)
{
  ...
}
 
int MyBridge::v_isValid(rtl::OUString * pReason)
{
  ...
}
 
extern "C" void SAL_DLLPUBLIC_EXPORT SAL_CALL uno_initEnvironment(uno_Environment * pEnv)
	SAL_THROW_EXTERN_C()
{
	new MyBridge(pEnv);
}
 
extern "C" void SAL_DLLPUBLIC_EXPORT SAL_CALL uno_ext_getMapping(uno_Mapping     ** ppMapping,
								   uno_Environment  * pFrom,
								   uno_Environment  * pTo )
{
	cppu::helper::purpenv::createMapping(ppMapping, pFrom, pTo);
}

Mappings

Mappings can be accessed, used or implemented.

Access

Usage

MapIn
MapOut
MapInAny
MapOutAny

Implementation

Objects

Objects can be accessed, used (invoked) or implemeted.

Implementation

Invokation

Access

FreeReference

The free reference allows to deal with an abitrary object in an environment agnostic fashion. The reference itself basically ensures, that the managing environment is active when actually invoking a method. See Uno/Cpp/Spec/FreeReference for details about the FreeReference.

C++ Example - Temporary entering another environment
#include <cppu/EnvGuards.hxx>
...
{ 
  com::sun::star::uno::Reference<XFoo> ref(...)
  ...
  cppu::FreeReference<XFoo> free_xFoo(xFoo);
  cppu::EnvGuard otherEnv(cppu::Environment("just another env"));
 
  free_xFoo->doSomething(); // the free reference ensures that the correct environment etc. gets activated before invokation
  ...
}
C++ Example - Function always returning an appropriate object

The following example shows a function always returning an appropriate (correct OBI and purpose) object of type XInterface. For this function to work properly, the client must have activated the appropriate environment, as the uno::Reference is only partly (namely OBI) specialized.

Callee:

// This function is environment specialized on "c++<purpose>*".
uno::Reference<uno::XInterface> create_appropriateObject(void) {
  cppu::FreeReference<uno::XInterface> result_Obj;
 
  // We may want to open a new scope, to ensure that "result_Obj" does
  // not get destructed while "c++:unsafe" is active.
  {
    // We need to remember the callers environment, to "map-out/in"
    // the parameters and return values properly.
    uno::Environment outerEnv(uno::getCurrent());
 
    // We activate (enter) the "c++:unsafe" environment.
    // Note: Any other environment suiteable for "MyUnsafeObject" would work as well.
    cppu::EnvGuard unsafeGuard(uno::Environment(rtl::OUString(RTL_CONSTASCII_UPARAM("c++:unsafe"))));
 
    // This reference points to a "thread-unsafe" object.
    Reference<uno::XInterface> unsafeEnv_Obj(new MyUnsafeObject());
 
    // We may do some activations on "unsafeEnv_Obj".
    unsafeEnv_Obj->doThis();
    unsafeEnv_Obj->doThat();
 
    // We assign it to "result_Obj".
    // Using "result_obj" is "safe" anywhere.
    result_Obj.set(unsafeEnv_Obj, SAL_NO_ACQUIRE);
 
    // The unsafeEnv_Obj reference gets destructed here, actually calling the "release" method in the right environment.
    // The unsafeGuard gets destructed here, deactivating the "c++:unsafe" environment.
  }
 
  return result_Obj;
}

Caller:

...
{
  // Whatever "c++<purpose>*" we enter, the result of "create_appropriateObject" will 
  // always match.
  cppu::EnvGuard cppDebug_Guard(rtl::OUString(RTL_CONSTASCII_PARAM("c++:debug")));
 
  uno::Reference obj(create_appropriateObject());
}
...
C++ Example - Function accepting any kind of parameters

In the following example, the called function gets a parameter, which needs to be mapped appropriately to the "c++:unsafe" environment, to be able to pass a thread-unsafe object to the set method of the parameter. For the function to work properly, the client must have activated the appropriate environment, as the uno::Reference is only partly (namely OBI) specialized.

Callee:

// This function is environment specialized on "c++<purpose>*".
void takeAnyObject(uno::Reference<...> const & rObj) {
  // We need to remember the callers environment, to "map-out/in"
  // the parameters and return values properly.
  uno::Environment outerEnv(uno::getCurrent());
 
  // We now activate (enter) the "c++:unsafe" environment.
  // Note: Any other environment suiteable for "MyUnsafeObject" would work as well.
  cppu::EnvGuard unsafeGuard(uno::Environment(rtl::OUString(RTL_CONSTASCII_PARAM("c++:unsafe"))));
 
  // We "mapIn" the parameter.
  uno::Reference<...> unsafeEnv_Obj.set(cppu::mapIn(rObj, outerEnv), SAL_NO_ACQUIRE);
 
  // MyUnsafeObj has a C++ [[Uno/Term/Object Binary Interface|OBI]] and is thread-unsafe
  unsafeEnv_Obj->set(new MyUnsafeObject());
}

Caller:

...
{
  // Whatever "c++<purpose>*" we enter, the parameter passed to "takeAnyObject" will
  // always match.
  cppu::EnvGuard cppDebug_Guard(rtl::OUString(RTL_CONSTASCII_PARAM("c++:debug")));
 
  uno::Reference<...> obj(...);
  takeAnyObject(obj);
}
...
Personal tools