A bridge in the UNO environment is defined to be a software module that can create a proxy object in one environment (your language and hardware platform) so that the actual object represented by the proxy is available in a second environment. This proxy object looks like an object you have implemented in your environment, so it can be transparently used. The proxy object delegates all calls to the real object.
Example: The real object is implemented in C and the object should be accessed from C++. In this case we have to get the bridge from C++ to C. The name of the bridge is c_msci.dll. Remember that C++ is incompatible between different compilers and different switches. So msci means Microsoft Visual C++ compiler. This bridge creates a C++ proxy object in the msci environment, which delegates all calls to the real object implemented in C. Sometimes the bridge is called language binding, but this description is not exact, because bridges may connect object models, too.
To reduce the number of bridges, normally only bridges to the binary UNO specification environment exist. To make a bridge from C to C++ you delegate call traffic over two bridges. First the bridge from C to binary UNO and then the bridge from binary UNO to C++. This way we only need n -1 instead of n^2 -1 bridges and to find a way from one environment to another is very simple. The performance penalty should be small, because the binary UNO specification is very low level. Despite this, it is always possible to use a direct bridge, if one exists.
There is a mandatory feature a bridge has to support. The bridge must be able to create proxy objects only from the description of an interface. This implies, that code may be generated at runtime.
The following paragraphs explains bridging on a higher level.
If you look at the binary UNO specification of an interface you will probably recognize, that you don't want to call via binary (C-) UNO. Normally nobody wants to call the generic dispatch method. The method calls and attribute accesses should be transparent to the language from which call is invoked. This implies that the UNO object is encapsulated in an object which delivers and converts the language adapted calls to the UNO object. This object is the proxy of an UNO object in your language. Normally the proxy maps the calls to an object implemented in the binary UNO specification. The next picture shows a call to the UNO object:
In the proxy paragraph it was said "nobody wants to call the generic dispatch function". This applies to the implementation of an UNO object, too. The following picture describes a call via a stub to the UNO object:
First the dispatch function is called. The stub detects the interface and the method that is to be called at the UNO object. Then the call is converted into a language specific call and the UNO object is called. After that the return value is converted to the binary UNO type.
The stub object makes all conversions to the language in which the UNO object is implemented. So it is possible to implement the UNO objectin your favourite language. For example: In C++ you can use exceptionsand multiple inheritance.
The topic describes calling from one language or object model to another. With the proxy we can call from one language to an object implemented in the binary UNO specification. With the stub we can call through the binary UNO interface to the UNO object wherever it is implemented. The only thing that is left to do is create the proxy and stub and bind them. Now we call the UNO object from our language. The next picture shows the binding:
The first step is the generation of a stub object with an existing UNO object. The second step is to create a proxy object with the stub object. Each call to the proxy object is delivered to the stub object which prepares the call and calls the UNO object in the language it is implemented.
The next step shows by example the call from a language like "objective c" to an UNO object implemented in C++:
The actor uses the programming language objective c. The proxy makes the interface to the UNO object in the language objective c available.
- The actor only uses objective c to manipulate the UNO object. In the example it is the call char * pOldText = [ myObject changeText: "test"].
- The proxy object converts the parameter of type string to the binary UNO specification.
- Then it dispatches the call to the stub. All necessary information, .e.g. an exact method type description, the parameters, a place to put the return value and a place to put the exception, is delivered to the stub.
- The stub converts the string from the binary UNO specification to a C++ string.
- Then the stub calls the UNO object. In our example: pComponent->changeText( "test" ). The stub must catch all kind of exceptions thrown by the UNO object.
- If the method returns, the string is converted to the binary UNO specification and stored at the place given through the dispatch call. If an exception is to be thrown, the exception is converted and stored in the memory provided by the caller.
- After the dispatch call returns, the proxy converts the string to an objective c string and returns from the changeText call. If the call terminates by an exception, the exception is returned to the actor. It is up to the objective c language binding in which manner the exception occurs (the objective c language doesn't support exception handling).
The main feature of these model is that the actor only copes with the UNO object in its language environment and the developer of the object only uses a language that best fits to his problem.
Stub and Proxy Factory
To implement these factories three problems have to be solved. First a way to connect a source language to a destination language has to be found. The languages are not predefined they may be completely unknown to the binding tool.
Second the stub factory has to create a binary UNO interface implementation that delivers all calls to the UNO object. One requirement is that the factory should generate the binary UNO interface implementation only from a type description. This means no code generation at compile time.
Third the proxy factory must create an interface implementation in the specific language that dispatches all calls to the binary UNO interface. One requirement is that the factory should generate the interface implementation only from a type description. This means no code generation at compile time.
A type description in this context is meant to be the full formal description of an interface. This description includes, among other information, the super interface, the methods, attributes, method parameter attributes, method exceptions.
Draft of a possible solution:
To address a proxy factory we have to name our language or object model. This is a string, because it is extensible and the risk of double names is low. Next we call the binding tool, which is integrated in our UNO runtime library function. The first parameter is our language and the second parameter is uno. If the name of the function is uno_getMappingByName, an example call to create a proxy in objective c for an UNO object is uno_getMappingByName( ..., "objective_c", "uno" ). The implementation of the function may search a shared library named objective_c_uno to find the right library containing the proxy factory.
To create a stub you must only swap the parameters of the function. So our example call is uno_getMappingByName( "uno", "objective_c" ).
A stub object implements the binary UNO interface. The dispatch function of the stub calls the original object in its language specific way. This is simple if you are using languages like Java which have a core reflection API. It is very difficult for languages like C++ which have no binary standard and no API to call virtual methods.
Building a proxy object is normally difficult in all languages. The factory must generate method code to implement each method specified by the interface. The only information to do this is the type description of the interface.
Example: In Java (1.1) we have to generate a binary class file (*.class) and load this one with the class loader. In absence of a loader that can load direct binary classes we must write a loader.
In C++ we must generate virtual method tables that delegate each call to the binary UNO interface. In absence of a binary C++ specification we must explore each compiler (version, switch,...) to implement this. The hard part of this is exception handling.
The type repository contains all information about all types and specifies the API to access the type description. The API is normally used to get the information in the proxy and stub generation process.
The following table shows names for environments (language environments or object models). Use these names to get the mapping from one environment to another.
|Language Binding or Object Model||Naming|
|Binary UNO specification||"uno"|
|Microsoft C++ 4.2 - 6.0||"msci"|
|EGCS 2.9.1 with RTTI||"egcs29"|
|WorkShop Compiler 5.0||"sunpro5"|