Uno/Article/Working with Environments, Mappings & Objects

From Apache OpenOffice Wiki
< Uno
Revision as of 13:25, 1 September 2006 by Kr (Talk | contribs)

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

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

and more.

Theory

The three entities are

  • environments for managing objects,
  • mappings, basically connecting environments, to bring objects from one world to another, and certainly
  • objects, for doing implementations.

All of these three entities (environments, mappings, objects) are dynamically extendable.

Environments

Environments are at the fundamental of Uno. Environments

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

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 OBI thread affine objects.

The part in front of the colon being the OBI, and the part behind it being the purpose (note: purposes may be chained, e.g. "<OBI>:unsafe:debug"). The whole string being the environment descriptor.

Variants

While their is no difference in general, we differentiate environments to either be

Object Binary Interface

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

Purpose

Purpose environments typically only manage objects of the standard (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 process wide unique and global. Re-requesting a particular environment (e.g. "uno:unsafe") multiple times, always returns the same instance. An environments identity is uniquely derived from its description. An environment exists as long as an object or proxy is registered.

Activation

Environments may be activated either directly by being entered, or indirectly by being invoked. Depending on the particular environment, semantics might differ slightly. Environments may control some global state, which they may alter 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, is guaranteed to always be safe.

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 by

"uno:unsafe:debug"

Naming

Environment naming has not been implemented yet. Naming is going to allow to have multiple environments of the same type, e.g.

"gcc3:unsafe;impl_one"

or

"gcc3:unsafe;impl_two"

This is going to enable the instantiation of different components implemented in the same environment into independent environments. E.g. two thread-unsafe C++ components may be invoked in parallel. It would also enable the logging of the 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/Todo#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.

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 one environment may be "mapped" to another environment, actually providing a representation of the source object in terms of the destination environment.

Cascading

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

"gcc3" -> "gcc3:unsafe" 

gets actually mapped as

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

Life Cycle

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

Variants

While their is no difference in general, we differentiate mappings to either mediate between different

  • OBIs, or to mediate between different
  • purposes.

As in most cases it is needed to map back and forth, mappings are implemented as bridges. Typical names for bridges are:

"uno_gcc3"
"uno_remote"
"uno_uno_unsafe"

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 to another, objects are the implementation 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 from environment-specialized to environment-any, partly specialization being possible, e.g.

  • a library taking and returning "uno" objects only - this library is specialized on the "uno" environment,
  • a library taking and returning "gcc3:unsafe" objects only - this library is specialized on the "gcc3:unsafe" environment,
  • a library taking and returning "gcc3[:<purpose>]*" objects only - this library is partly specialized, namely on the OBI, only,
  • a library taking and returning "<OBI>[:<purpose>]*" objects - this library is basically able to deal with any kind of objects, it 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 dynamically, 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 specific 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 missing information from the 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 system with garbage collection keep objects alive as long as they are referenced only.

Activation

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

Practice

So, you may ask yourself, having read the above, what exactly can you do with this stuff?! Actually, the combination of environments, mappings and objects is very powerful. You can

  • achieve remote transparency,
  • trace one or multiple particular objects,
  • transparently access objects implemented in other programming languages,
  • provide well directed back-doors for optimization purposes,
  • implicitly handle contexts,
  • protect thread-unsafe objects,
  • and more.

More examples to come.

Environments

Mappings

Objects

Libraries&Applications

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: [cpp] // This function is environment specialized on "c++<purpose>*". uno::Reference<uno::XInterface> create_appropriateObject(void) {

 uno:Reference<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 "mapOut" the object and assign it to "result_Obj".
   result_Obj.set(cppu::mapOut(unsafeEnv_Obj, outerEnv), SAL_NO_ACQUIRE);
   // We may _not_ activate result_obj, as we are still in the "c++:unsafe" environment.
   // 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.
 }
 // Using "result_obj" is "safe" here.
 return result_Obj;

}

Caller: [cpp] ... {

 // 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 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: [cpp] // 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++ OBI and is thread-unsafe
 unsafeEnv_Obj->set(new MyUnsafeObject());

}

Caller: [cpp] ... {

 // 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