Copyright © 2000/2001 by Mathieu Lacage and Dirk-Jan C. Binema
Table of Contents
Table of Contents
The only thing we know now for sure about CORBA is that to be able to understand what CORBA really is, you need to understand its philosophy rather than the nitty-gritty details of how to use it. As a consequence, you will find very little sample code in this chapter and a lot of lengthy explanations. Our belief is that [once the why is understood, the how follows fairly naturally] (some readers will recognize Don Box, in Essential COM).
Before you start reading further, please, take some time to go to the OMG website [1]. The Object Management Group is the entity which elaborates the CORBA (Common Object Request Broker Architecture) specification. The folowing documents describe the main parts of this specification:
CORBA roots itself in the Distributed Systems field of computer science. This field covers a lot of topics: from distributed algorithms to client-server architecture...
CORBA is a framework designed to build client-server architectures: a client program accesses a server's services to fulfill its task. Usually, client-server architectures are designed to work in distributed environments. That is, the client program lives on a machine (loukoum.enst.fr for example) and the server program lives on another machine (www.gnome.org).
The Internet, its www browsers and its web servers is the perfect example of these ideas.
What has become a trivial application bundled with every operating system (*sigh*) is nonetheless a pretty complex piece of software: its main components are reviewed below.
The above figure outlines the layered architecture of both the server and the client. Each layer specifies a necessary part of the langage to be used for communications:
The url layer specifies the action to be executed by the server.
The http protocol layer specifies an ascii request format to serialize the action requested into a stream of bytes.
The socket layer specifies the network protocol used to transfer bytes between client and server. Here, the protocol used is TCP/IP (XXX ref) but many others could be used.
Finally, the application itself specifies the message protocol implicitely used: pairs of request/answer. Many other message protocols could be used... For example, the server could send updates whenever a given file is updated resulting in series of startrequest/answer/answer/answer/.../answer/stoprequest.
NFS (Network File System) is one of the oldest existing distributed system: its goal is to allow many clients to access a server's filesystem, transparently, as if the files were local files for the clients.
Because Sun's engineers were no more stupid than you, they decided to layer the architecture of their filesystem: the server exports a number of low-level services and the clients use these services to access files. The low-level services available to clients consist of a number of C API functions which can be called remotely (hence the Remote Procedure Call acronym :).
RPC is a framework which can be used to export arbitrary C functions from a server and to call the exported functions from the clients. Now, before we go into the details on how RPC achieves the goals outlined above, I'd like you to think over the actual implementation needs:
You need to serialize to a byte buffer the function identification, its parameter values and the return values.
Then, you need to send the byte stream representing the actual request packet from the client to the server. You can decide on using TCP/IP or UDP/IP or any other less standard network protocol.
To solve all the problems I just briefly described, RPC uses DCE as the binary format to serialize C function calls. This process is more generally known as marshalling: the function call is marshalled on the client and demarshalled on the server. Then, the answer is marshalled on the server and demarshalled on the client.
The marshalling code itself (this code is very tedious to write: it is straightforward given the function prototype) is automatically generated with a special compiler. This compiler processes the C function declarations to be exported and generates the client marshaller/demarshaller and the server demarshaller/marshaller.
Once the special compiler has done its job, the clients just need to link against the functions defined in the stubs generated by the compiler and the server just needs to link against the functions defined in the skeletons. The folowing diagram shows the control flow of such a RPC during execution:
A good way to approach CORBA is to see CORBA as a sort of super RPC. The goal of CORBA is to distribute Objects rather than mere functions. CORBA also aims at complete interoperability between clients and servers at numerous levels:
To achieve complete interoperability between distributed CORBA objects, the CORBA standard specifies a messaging protocol named GIOP (General Inter-ORB Protocol [2]). There exist a specific instance of this protocol for each network-level protocol. For example, we have IIOP (Internet Inter-ORB Protocol [3]) which the IP version of GIOP.
While GIOP defines the sequence order of requests and answers between client and server, CDR (Common Data Representation [4] ), on the other hand, is the binary format used to serialize GIOP messages in byte streams. For example, CDR specifies how a 32 bit signed integer should be represented in a byte stream (as well as more complex data types...). CDR is the CORBA equivalent of RPC's DCE.
While understanding the details of these protocols can be interesting, It sure is not necessary for everyday use. I would thus suggest interested readers to refer to the relevant sections of the CORBA specification. Others will live happily with what they have just learned. [5]
CORBA objects are defined in a specific langage named IDL [6] (Interface Description Langage). The syntax for this langage is heavily inspired by C and C++.
interface Person {
readonly attribute string name;
boolean isSingle ();
};
interface FrenchPerson : Person {
boolean isEvil ();
};
The above example shows a single object's interface,
FrenchPerson which inherits from the interface
Person and provides a number of services through
its methods and attributes.
To allow any langage to access CORBA objects, the specification defines a number of mappings between the langage-independent definition of objects and langage-dependent constructs implementing these objects. Each of these langage-specific mapping is described in separate documents which are, as usual, available from the OMG website (pointers to the exact documents were presented in the introduction of this chapter).
For example, the folowing piece of C code would be used to access the method isSingle exported by the Person interface.
if (Person_isSingle (objectReference, &ev)) {
printf ("Mathieu is single!!! Can you believe this ?\n");
}
The folowing java code will operate similarly:
if (objectReference.isSingle ()) {
System.out.println ("Mathieu is single!!! Can you believe this ?\n");
}
IDL has three main uses:
It is used to specify the APIs of the ORB (the CORBA implementation) in a langage-independant way.
You can use it to specify your own application's APIs in a langage-independent way.
You can use it through an IDL compiler to generate the boiler-plate code for the client-side and the server-side marshalling and demarshalling stubs and skeletons (it should be noted that the CORBA specification does not specify the way the marshalling code should work: it just specifies its API and the semantics of the API for the users and the network-level byte stream. Everything else is completely ORB-dependant which is why the IDL compiler is so important: it hides from the user all ORB-dependant code).
The previous quick introduction should have given you a feeling of what the IDL can do. This section's goal is to give you a crash course in the IDL's syntax, semantics and real-life-uses. The CORBA specification is of course the only true source of knowledge concerning the IDL which is why I strongly suggest you to look into the relevant section of the specification (for reference, section 3).
Imagine you want to access the Bonobo::eat_banana method of the Bonobo object. First, you need to create the Monkeys.idl file which contains the folowing definitions.
module Monkeys {
interface Bonobo {
void eat_banana ();
}
}
If you were to use ORBit, the Gnome ORB, you would folow the diagram below: use orbit-idl to generate the stub and skeleton code. Then use your C compiler to compile both your own code and the autogenerated one. Last, link them all to get the server and the client executables.
The example above maps to C very easily: the CORBA C mapping says that the client is supposed to call the Monkeys_Bonobo_eat_banana (bonobo_object, exc) function. Your client code would thus simply call this function which would be implemented in the stub and defined in Monkeys.h. Your Server code would implement this function and the skeleton would transmit all incoming calls from the stub to your code.
As we have already discussed it sooner, the IDL is used to describe your objects' interfaces. The IDL is strongly typed so that there is no semantic loss when mapping the IDL to langage dependant structures. The IDL has a number of predefined standard types which are detailed in the CORBA specification. Here folows a list of the simplest/most obvious/most widely used ones.
Table 1.1. C Mapping for the basic CORBA types
| Type | Signification |
|---|---|
| short | 16 bit signed integer |
| unsigned short | 16 bit unsigned integer |
| long | 32 bit signed integer |
| unsigned long | 32 bit unsigned integer |
| float | 32 bit IEEE float |
| double | 64 bit IEEE float |
| boolean | boolean value: TRUE or FALSE |
| octet | 8 bit byte |
| char | 8 bit character (ISO latin-1) |
module calendar {
enum a_month {
january, february, march, april, may, june,
july, august, september, october, december
};
enum a_day {
monday, tuesday, wednesday, thursday,
friday, saturday, sunday
};
typedef long a_year;
struct a_date {
a_day the_day;
a_month the_month;
a_year the_year;
};
};
Now, to define Objects, you could do as follows (a minimalistic example).
module Monkeys {
/* this is a comment like in C */
// this is a comment like in C++ : both are valid
interface Bonobo {};
interface BonoboFemale : Bonobo {};
interface BonoboMale : Bonobo {};
};
The above example defines the Monkeys Namespace with 3 objects inside this namespace: the Bonobo, the BonoboMale and the BonoboFemale objects. BonoboMale/Female both inherit the Bonobo interface. Multiple interface inheritance is also possible. The folowing will define some methods to these objects.
module Monkeys {
interface Bonobo {
// this is the declaration of the eat_banana method
// which takes as parameters a boolean variable.
void eat_banana (in boolean eat_yes_or_not);
// returns whether the Bonobo is still hungry or not.
boolean is_hungry ();
};
interface BonoboMale : Bonobo {
// returns whether the almighty Bonobo is making love to
// one of his female counterpart. If yes, it also returns
// his partner's name and surname.
boolean is_making_love_to ( out string who_surname, out string who_name );
}
};
As you can see above, methods may have zero (like the is_hungry method) or one or more parameters (like the eat_banana method). Methods have only one return value (it can be void or any other defined type) and have zero or more parameters.
Parameters can be of three kinds: in, out or inout.
in parameters should be used when the data is sent from the client to the server.
out parameters should be used when the data is sent from the server to the client as an answer to the CORBA call.
inout parameters should be used when the data is first sent from the client to the server and then a reply is sent from the server to the client.
You can define objects' attributes together with fixed-size arrays:
module Monkeys {
interface BonoboFemale : Bonobo {
attribute boolean has_children;
// 1 dimensional fixed-size array
// which contains the children names.
// 20 children maximum :)
typedef string a_children_array[20];
attribute a_children_array the_children_array;
// 2 dimensional fixed-size array
// which contains the child arrays of our
// female's brothers and sisters.
typedef a_children_array a_nephew_array[20];
attribute a_nephew_array the_nephew_array;
};
};
It is also possible to define variable-sized arrays with the sequence keyword.
module Monkeys {
interface BonoboMale : Bonobo {
// This defines a one dimensional array
// of longs with a maximum size of 20.
typedef sequence <long,20> array_1;
// an unbounded one dimensional array of strings
typedef sequence <string> array_2;
// more tricky: a sequence of sequences: used
// to simulate multi dimensional variable size arrays
typedef sequence <sequence <long,20> > array_3;
};
};
Exceptions can be defined in IDL and used by interfaces' methods to return exception status data.
module Monkeys {
interface Bonobo {
// exception declaration
exception NotHungry {};
void eat_banana ()
raises NotHungry;
};
}
Many readers not used to do straight C programming now wonder how it is possible to map the concept of interfaces which closely matches that of objects in a langage which lacks such constructs.
Any experienced C programmer knows how to mimic Object Oriented programming in C: it just requires more discipline than in more friendly langages. An object can be represented as a pointer to a C struct. An object's attributes are simple fields of the structure. It is possible to define accessors for these fields. It is also possible to privatize the content of the structure from users by using certain tricks. Finally, all the Object Oriented frameworks available in C closely match that of CORBA in terms of syntax.
BonoboObject *pobject; pobject->doThisCrazyThingWeTalkedAbout ();
As an example, the above C++ code would be written in an OO way in C as folows:
BonoboObject *object; BonoboObjectdoThisCrazyThingWeTalkedAbout (object);
The rules applied can be summarized easily by:
The IDL C mapping folows these rules as shown below:
module Monkeys {
interface Bonobo {
void eat_banana (in boolean eat_yes_or_not );
};
};
is mapped to :
typedef CORBA_Object Monkeys_Bonobo; void Monkeys_Bonobo_eat_banana (Monkeys_Bonobo object, CORBA_boolean eat_yes_or_not, CORBA_Environment *ev);
C function names are build with the folowing rule: modulename_interfacename_methodname. The first parameter is always a modulename_interfacename structure. The last parameter is always a CORBA_Environment pointer. The other parameters correspond directly to the parameters of the object method.
The types of these parameters is determined by the folowing mapping for the basic IDL types we presented previously.
The mapping of IDL attributes is very simple: folowing good OOP practice, objects' attributes may not be accessed directly: two functions are defined: a _get-function and a _set-function. For example, the following two definitions are strictly equivalent:
module Monkeys {
interface Bonobo {
attribute boolean hungry;
};
};
// The following is theoretically equivalent to the first
// definition but is illegal as IDL forbids the use of
// identifiers preceded by an underscore.
module Monkeys {
interface Bonobo {
boolean _get_hungry (void);
void _set_hungry ( in boolean b );
};
};
Thus, to access the above attribute, you would use the folowing C functions: Monkeys_Bonobo__get_hungry and Monkeys_Bonobo__set_hungry.
The mapping of IDL exceptions is natural in terms of C++ or Java exceptions since these concepts are part of the langage itself but the C mapping itself is also pretty straightforward.
The last argument of every CORBA method of type CORBA_Environment is used to get exception information from the method execution. This structure is defined as folows in the C mapping:
typedef enum {
CORBA_NO_EXCEPTION=0,
CORBA_USER_EXCEPTION,
CORBA_SYSTEM_EXCEPTION
} CORBA_exception_type;
struct CORBA_Environment {
CORBA_exception_type _major;
CORBA_char *_repo_id;
void *_params;
CORBA_any *_any;
};
The _major field of the CORBA_Environment structure defines the type of exception: CORBA_NO_EXCEPTION denotes no that exception occured. CORBA_SYSTEM_EXCEPTION will be raised by the ORB implementation upon encountering low-level problems such as lack of memory or network connection death. Finally, CORBA_USER_EXCEPTION is set when a user-defined exception is raised.
A number of helper functions used to manipulate this structure are available:
void CORBA_exception_init(CORBA_Environment *ev); extern CORBA_char *CORBA_exception_id(CORBA_Environment *e); extern void *CORBA_exception_value(CORBA_Environment *ev); extern void CORBA_exception_free(CORBA_Environment *ev);
To test whether a method has raised an exception or not, you could do as in the folowing example:
CORBA_environment ev;
CORBA_boolean isBonoboHungry;
Monkeys_Bonobo bonobo;
CORBA_exception_init ( &ev );
isBonoboHungry = Monkeys_Bonobo__get_hungry (bonobo, &ev);
switch (ev._major){
case CORBA_NO_EXCEPTION:
printf ( "no exception\n" );
break;
case CORBA_SYSTEM_EXCEPTION:
printf ( "system exception. BAD THINGS HAPPENED.\n" );
exit (1);
break;
case CORBA_USER_EXCEPTION:
// This is not possible. No exceptions are defined by users on a attribute.
exit (1);
break;
}
How to raise and treat user exceptions is detailed in another example later. For now, it is enough to know how to deal with system level exceptions (that is, exit () :).
The mapping for fixed-sized arrays, variable-sized arrays (sequences) and structures is presented through examples in the folowing sections.
The exact syntax and semantics of the whole mapping can be easily understood from the C mapping specification: we use it ourselves everyday when writing CORBA code. Hence, readers are once more encouraged to read the reference document provided by the OMG.
The specification defines a set of VERY useful services. The CORBA Services and the CORBA facilities contain a set of CORBA objects which provide services like a distributed naming service, some security services, some Medical specific services, some Telecommunications specific services and so on...
Most of the above services are not part of the main CORBA specification and are described in separate documents. However, some core utilities are in the main specification. These core utilities are put together into the CORBA::ORB and the CORBA::Object interfaces.
module CORBA {
interface ORB {
// The ORB.
}
interface Object {
}
module IIOP {
// The IIOP protocol definitions.
};
module DCE_CIOP {
// The DCE_CIOP protocol definitions.
// This protocol was defined for systems integration
};
module GIOP {
// The GIOP protocol definitions
};
};
The structure called CORBA_Object we used in the C code above is a reference to the CORBA server which is used by the IDL-compiler-generated-code to locate the CORBA server and send it your method invocations.
A very common mistake which all CORBA users have done at least once is to mistaken this Object reference for the real CORBA object. CORBA::Objects are pseudo objects which represent a reference to the actual CORBA server. All CORBA::Object instances have a life constrained on the client side (which is why they are called pseudo-objects): they hold information on how to access a given CORBA server.
module CORBA {
// All the folowing method definitions do not involve the
// object implementations. Only the object references.
interface Object {
// returns a new CORBA::Object reference.
Object duplicate ();
// frees a CORBA::Object reference.
Object release ();
// two different object references which refer to the same
// object should be equivalent but the standard only specifies
// that two identical object references are equivalent.
boolean is_equivalent (in Object other_object);
// an object reference can be tested for the CORBA_OBJECT_NIL
// value with this method.
boolean is_nil ();
};
}
The above API defined in the CORBA spec is thus a way to access methods defined and implemented on the client-side object reference. I urge all readers to think about the folowing carefuly: if you invoke the duplicate method on a CORBA::Object instance, you will receive a new CORBA::Object instance. This new instance points to the same CORBA server the original one pointed to. It is now possible to destroy the original instance by invoking release. The call to release or duplicate resulted in client-side activity to instantiate or destroy client-side objects.
The reason why I have tried to make the above point clear is because everyone, at least once, thought that calling duplicate would return a new instance of a CORBA::Object pointing to a new instance of a CORBA server. duplicate and release can be used to manage the lifetime of a CORBA Object reference, not the actual CORBA server.
Users who want to be able to destroy on the fly CORBA servers when they are not used anymore should design their own API and implement it.
module Bonobo {
interface Unknown {
void ref ();
void unRef ();
};
};
The above example shows how this is done in Bonobo. However, users should
remember that this is a very tough problem more generally known as
distributed-garbage collection which has not yet found any satisfactory
solution. As an example of the problems which arise in real life,
imagine one of your Bonobo clients which once called
ref dies before calling the corresponding
unref. This will leak one reference count in the server and
means the server will never die while it logically should have gone when
the last client holding a reference to it died.
Being able to notify the server on each death has no deterministic
solution (because the network might be down, aso...).
[7]
Finally, interested readers will be happy to look into ORBit/src/orb/corba_object.h which contains the definition of the real CORBA Object used by ORBit.
Since the beginning of this introduction to CORBA, we have overlooked quite a lot of "details". Basically, we have seen how to call an object's method, how to read and set its attributes. However, all these actions explicitely require you to hold a reference to the CORBA object you want to invoke.
Here comes a classic initialization problem: how do you get its reference? There are (at least) two solutions to this problem. The simplest one from the programmer's point of view is to ask the user to enter the object reference directly. This can be done with IOR strings.
IOR strings are nothing but the serialized form of a CORBA object. The idea is that you can launch a CORBA server and have the server serialize its CORBA object reference. It is then possible from the client side to restore a valid CORBA object reference from the IOR string.
The CORBA::ORB::object_to_string function is used to serialize CORBA object references and the CORBA::ORB::object_to_string function can then be used to restore a CORBA object reference.
Once the client has gotten a CORBA object reference of the remote CORBA object, it can invoke the proper operations on it.
IOR Strings are rather long (often as long as a few hundred characters) which makes them not very practical to use. However, they have been standardized which means that you can launch a server using an ORB and use the IOR string it outputs as the entry of a client using another ORB. Below is a such a sample IOR string.
IOR:01f2ffbf1c00000049444c3a4f41462f4f626a6563744469726563746f72793a312e300002000000 caaedfba54000000010100402b0000002f746d702f6f726269742d6d6174686965752f6f72622d313730 353733333932383535343231323431370000000005081800000000000000137a4a747f9b995e01000000 88cd9ccf1a4b7629000000003c00000001010040160000006d6174686965752e72657a656c2e656e7374 2e66720061051800000000000000137a4a747f9b995e0100000088cd9ccf1a4b7629
The above solution seems to be working just fine, but it hides another simple problem. To call CORBA::ORB::object_to_string, you must be able to access a CORBA::ORB object which is the first parameter to the function call.
This leads us to the same initialization problem we solved above. This time, the solution is much simpler: we have the bootstrap function CORBA::ORB::init whose mapping is special. Here follows an example of the use of the C mapping.
int main (int argc, char **argv)
{
CORBA_Object orb;
CORBA_Environment ev;
orb = CORBA_ORB_init (&argc, argv, "orbit-local-orb", &ev);
};
The orbit-local-orb parameter is a string specific to each ORB implementation.
CORBA has another much nicer (and standardized!!) way to handle the initialization problem but which unfortunately does not work very well in ORBit.
Hopefully, there exist a simple and easy-to-use solution named Bonobo Activation (formerly known as OAF) which can be used on machines where it is installed. Bonobo Activation's architecture and its use are described in the folowing chapter. In the meantime, we'll use the two folowing magic functions exported by the activation library: bonobo_activation_orb_init and bonobo_activation_activate_from_id which can be used to initialize the ORB and activate a server (that is, launch the server if it does not exist and return an object reference to it :).
CORBA has many other nice features which we have not discussed. One of the cutest ones, is the dynamic framework. Until then, you have seen how to build a server and a client using the IDL to generate the Stubs and Skeletons for your application. While this method is useful in itself, it leads to a lot of code duplication. Everytime you link statically against the stubs or the skeletons, you add some code to your application and most of this code is very similar from one Stub to another.
The idea is to be able to automatically generate the GIOP messages during runtime. You could of course have a special IDL compiler which reads the IDL files during startup and which generates the Stub and Skeleton code on the fly and which compiles it... However, this method is a little heavyweight...
Which is why CORBA offers three mechanisms to replace all this.
The IR (Interface Repository) is a CORBA server defined in the CORBA specification which allows you to browse the definitions of the interfaces of the CORBA servers running on the system.
IDEs can make use of this feature: this would allow them to browse the definitions of the CORBA servers running on your machine so you can decrypt their API and call them directly.
The DII (Dynamic Invocation Interface) is a set of API functions in the ORB which allow you to create your own CORBA calls on the fly. Typically, you are using the IR to browse the objects of your system, you find one interesting and you call this object from a scripting langage. The scripting language could use the DII interface to build the proper CORBA request to the object.
The DSI (Dynamic Skeleton Interface) is the server counterpart of the DII. It allows you to handle dynamically CORBA requests. It is often used to create generic bridges: the CORBA requests received are translated into requests for another system on the fly using some predefined rules (CORBA <--> DCOM bridges often use DSI.)
Before dealing with the implementation of a CORBA client and server, it important to focus on the functionality you want the server to offer. Therefore, a good technique is to start by defining the interface of the server. In more realistic situations, defining interfaces is a process that may take careful consideration and discussion. For the sake of simplicity, our example will be very simple.
The interface for our 'calculator'-server looks like this (calculator.idl):
interface calculator {
// add two numbers, return the result
long add ( in long x, in long y );
// calculate the sum of a sequence of numbers
typedef sequence<long> Numbers;
long sum ( in Numbers numbers );
// divide a number
exception DivideByZero {};
double divide ( in long x, in long y ) raises ( DivideByZero );
};
Now, let's write a CORBA server implementing this
interface
[8]
To create a CORBA server, we can use ORBit's idl-compiler to generate the necessary plumbing code implementing the proxy code for us. Thus, we can do: $ orbit-idl-2 --skelimpl --nostubs calculator.idl The '--nostubs' tells the idl-compiler not to create any files needed for the client. The '--skelimpl' tells the idl-compiler to create a skeleton implementation as well. If you use this option, all you have to do is fill in the functions.
If we haven't left any error in the .idl-file, the idl-compiler will generate the following files
In calculator-skelimpl.c we can now fill in the necessary code. Most boilerplate code has been written for us by the idl-compiler.
The simplest function long add ( in long x, in long y ) we defined in IDL can be implemented as follows:
static CORBA_long
impl_calculator_add(impl_POA_calculator * servant,
CORBA_long x, CORBA_long y, CORBA_Environment * ev)
{
return x + y;
}
You may notice that the implementation has four parameters, instead of only two in IDL. As we already discussed it previously, the C mapping added two special parameters: the first parameter denotes the object being called [9] while the last parameter denotes the exception data structure. Obviously, 'x' and 'y' parameters correspond to the parameters with the same name in IDL.
We assume you can figure out how the function is actually implemented, so we won't discuss that. Note that we don't do anything with the servant, nor does it set any exception.
static CORBA_long
impl_calculator_sum(impl_POA_calculator * servant,
calculator_Numbers * numbers, CORBA_Environment * ev)
{
CORBA_long retval = 0;
unsigned int i;
for ( i = 0; i < numbers->_length; i++ )
retval += numbers->_buffer[i];
return retval;
}
The implementation of the sum is also quite simple. The only novelty compared to impl_calculator_add is the C-mapping of a CORBA sequence. In the mapping, the sequence becomes a structure with the three members:
typedef struct {
CORBA_unsigned_long _maximum;
CORBA_unsigned_long _length;
CORBA_long* _buffer;
} calculator_Numbers;
The _buffer member is a pointer to a number of CORBA_longs, and the actual number is stored in the _length member. The other parameter, _maximum, denotes the maximum number of elements that the client has allocated memory for (in _buffer). The _maximum is used when you need to deal with inout parameters, where the caller sends you a number a values (in _buffer), and you need to return a different number of values.
The type of the _buffer parameter is a CORBA_long in this case, but corresponds to the kind of sequence you have in the IDL file.
Again, the actual implementation of the function is quite obvious, so we won't discuss it any further.
static CORBA_double
impl_calculator_divide(impl_POA_calculator * servant,
CORBA_long x, CORBA_long y, CORBA_Environment * ev)
{
CORBA_double retval = 0;
if ( 0 == y ) {
retval = 0;
CORBA_exception_set ( ev, CORBA_USER_EXCEPTION,
ex_calculator_DivideByZero, NULL );
} else
retval = x / y;
return retval;
}
The last function we need to implement is impl_calculator_divide, which of course corresponds to the divide in the IDL description. The interesting thing this function adds to the previous discussion, is exceptions. We already discussed the way the CORBA C-binding deals with exceptions, and here we can see the implementation.
When the caller of impl_calculator_divide tries to do a divide-by-zero, we raise an exception. We can raise an exception by using the CORBA_exception_set function. The first parameter, ev, is the out-parameter that will convey the exception to the client. The second parameter denotes the type of exception, CORBA_SYSTEM_EXCEPTION or CORBA_USER_EXCEPTION. The first mainly deals with problems at the lower levels, in this case a CORBA_USER_EXCEPTION is the right type. Then, the ex_calculator_DivideByZero is the repository id of the exception: because we have declared the exception in the IDL description, we can identify it by it's repository id, In fact, if you look in calculator.h, you will find:
#define ex_calculator_DivideByZero "IDL:calculator/DivideByZero:1.0"
The last parameter, the NULL, can be used to convey the actual exception to the client, and may contain for example error messages. As our our exception is empty (as shown in IDL), this parameter is NULL.
After we have built a CORBA server, of course we would like to use it. So, let's write a client for this server. We'll use C for the client as well.
When writing a client, the first thing we need to do is compile the IDL description for the client: $ orbit-idl-2 --noskels calculator.idl We use the '--noskels' flag to inhibit the creation of the files needed for implementing the server, mirroring the '--nostubs' flags we used when compiling the IDL file for the server implementation. If we didn't left any errors in the IDL file, the following files are created:
For our server, we create a new file calculator_client.c which will contain our code to access the server. Note that the order in which we disuss this code is not necessarily the order of appearance in the source code.
First we need to initialize the ORB and the environment parameter ev we will use in our CORBA calls to gather exception information from the server.
CORBA_Environment ev;
CORBA_ORB orb;
CORBA_exception_init ( &ev );
orb = CORBA_ORB_init ( &argc, argv, "orbit-local-orb", &ev );
if ( ev._major != CORBA_NO_EXCEPTION ) {
printf ( "initializing ORB failed\n" );
return 1;
}
The call to CORBA_exception_init will set the members of the ev (which is actually a C-struct) to their default values. The call to CORBA_ORB_init does a lot of things. Depending on the settings in your /etc/orbitrc or ~/.orbitrc, it will connect to the naming service, the interface repository or the implementation repository. Also, it will connect to the Portable Object Adaptor (POA). After calling CORBA_ORB_init, we must check the ev to make sure the call succeeded (if a call succeeds, the ev will be equal to CORBA_NO_EXCEPTION.
Finaly, when we are done with our client, we must free the ev and orb variables:
CORBA_Object_release((CORBA_Object)orb, &ev); CORBA_exception_free ( &ev );
When we have a valid orb, we can connect to the server object:
calculator calculator_client;
calculator_client = CORBA_ORB_string_to_object( orb, argv[1], &ev );
if ( !calculator_client ) {
printf( "Cannot bind to %s\n", argv[1] );
return 1;
}
Note that the calculator is just a CORBA_Object in disguise.
The call to CORBA_ORB_string_to_object is actually a rather primitive way to get a reference to a remote CORBA server [10] . The second argument is a string (a char*) containing an IOR, in which information about the CORBA server is encoded. Using this string, the ORB will bind to the corresponding CORBA server (over boundaries of mountains, oceans, machines and operating systems), and return a reference in calculator_client.
No, let's take a look at some the useful thing we can do with the CORBA server.
CORBA_long nums[5] = { 2, 3, 5, 7, 11 };
calculator_Numbers numbers = { 5L, 5L, (CORBA_long*)nums };
printf ( "3 + 4 = %d\n", calculator_add ( calculator_client, 3, 4, &ev));
printf ( "sum ( 2, 3, 5, 7, 11 ) = %d\n", calculator_sum ( calculator_client, &numbers, &ev));
printf ( "3/4 = %f ", calculator_divide ( calculator_client, 3, 4, &ev));
if ( ev._major == CORBA_NO_EXCEPTION )
printf ( "(no exception)\n");
else
printf ( "(exception occured)\n");
printf ( "3/0 = %f ", calculator_divide ( calculator_client, 3, 0, &ev));
if ( ev._major == CORBA_NO_EXCEPTION )
printf ( "(no exception)\n");
else
printf ( "(exception occured)\n");
The client-side mapping is pretty straight-forward. The client-side mapping for sequences is identical to the server side mapping we already discussed.
Needless to say, for production code you should really check the ev for all calls. Compared to normal function calls, there are a lot of things that can go wrong for any CORBA call, even the trivial ones.
[2] See section 15 of the CORBA specification.
[3] See end of section 15 of the CORBA specification.
[4] See section 15.3 of the CORBA specification.
[5] The implementation of the GIOP protocol used in ORBit2 (the Gnome 2 CORBA implementation) is available in linc which should be available from the Gnome CVS server (module name, linc.
[6] See section 3 of the CORBA specification.
[7] Non-deterministic solutions exist. One of them is to use leases. Readers might want to consult the folowing link to learn more about leases (XXX find a good link).
[8] Here, we show the implementation of the interface using C; However, you can use any language that has a CORBA binding, and of course the server and client may be written in different languages. In the code accompanying this book, we have some examples of Perl and Python implementations.
[9] A servant is not itself the object called but it comes close. Explaining exactly how it relates to the object would require a lengthy explanation: this topic covers one of the most important features of CORBA which we have not presented here, the POA. The POA (Portable Object Adaptor) is the centerpiece around CORBA's portability and scalability. Interested readers should refer to section 11 of the specification.
[10] In the next chapter, we will discuss Bonobo Activation. For this example, CORBA_ORB_string_to_object suffices.
Table of Contents
Now, you know how to write your own CORBA server but you have understood how tedious it can be to setup your system to get it running: you have either to ask users to launch the servers by hand or to use a lot of evil hacks to make sure that your software can be bootstraped and can access the running and non-running CORBA servers.
The Nautilus file manager and the GNOME panel are both heavily based on the idea of small CORBA components which interact one with each other to provide both the User Interface and backend services. Imagine the hell it would be to have your users launch all the servers you need before running Nautilus.
You could make them run a small application which would launch all the necessary servers but the problem with this approach is that you do not need all the servers running all the time: many servers are used for particular tasks and not used at all after. The image viewer of nautilus for example would be used only when viewing pictures of your nude girl-friend (nude boy-friend); having it running in the background while listening to MP3s would clobber your system's memory and CPU.
What you want to be able to do is to lauch servers when you need them and to forget about them as soon as you do not need them anymore. This was possible in the GNOME 1.0.x and 1.2.x platforms thanks to Gnorba which was alas pretty limited. OAF (Object Activation Framework) replaced it in GNOME 1.4.x and was renamed to Bonobo Activation for Gnome 2.0.x. Bonobo Activation allows you to start any kind of server on the fly. The current implementation can start distant CORBA servers as well as local ones. It also supports activation of Dynamic-Library-based servers (of course, not distant dll servers...).
Bonobo Activation is a replacement for the limited libgnorba library developed for the GNOME 1.x platform. Gnorba had a number of shortcommings:
The Bonobo Activation architecture is based around the ActivationContext and the ObjectDirectory interfaces. Only one ActivationContext exists for a given user on a given machine. This ActivationContext is responsible for managing all the user's ObjectDirectories. Each ObjectDirectory is supposed to have knowledge about some potential CORBA servers. Each CORBA server is uniquely identified within the ObjectDirectory by an IID (Implementation Identification). The folowing diagram makes these relationships clear.
IIDs are supposed to have the folowing format:OAFIID:program_name:UUID
where UUID is a string as generated by the uuidgen utility (more information on this can be found in the Bonobo Activation reference documentation)[11].
ObjectDirectories can activate a server given an IID.
module Bonobo {
interface ObjectDirectory : Bonobo :: Unknown {
Object activate (in ImplementationID iid, in ActivationContext ac, in ActivationFlags flags)
context ("username", "hostname", "domain", "display");
RegistrationResult register_new (in ImplementationID iid, in Object obj);
void unregister (in ImplementationID iid, in Object obj, in UnregisterType notify);
};
};
The above partial interface listing shows the basic functionality of an
ObjectDirectory. To activate an object, you are supposed to call the
Bonobo::ObjectDirectory::activate method. Objects should
register with Bonobo::ObjectDirectory::register_new to the
ObjectDirectory once they started so that the ObjectDirectory
can return a valid object reference upon return of the
activate function.
An application which wants to start a server has no knowledge of the ObjectDirectories which reference the actual CORBA servers. The application requests a certain server to the ActivationContext object and the ActivationContext object requests one of its ObjectDirectories for the given server. The ActivationContext object server knows which of its Objectdirectory contains the requested server because it can query all of them through the Bonobo::ObjectDirectory::get_servers method which returns the list of all the CORBA servers a given ObjectDirectroy knows about.
module Bonobo {
interface ObjectDirectory : Bonobo :: Unknown {
ServerInfoListCache get_servers (in CacheTime only_if_newer);
ServerStateCache get_active_servers (in CacheTime only_if_newer);
};
};
Thus, when you make a request to an ActivationContext, the ActivationContext
calls Bonobo::ObjectDirectory::get_servers on each of its
ObjectDirectory to make sure it has an up-to-date list of all the CORBA
servers of each of its ObjetDirectories. Then, it serves the request by
calling the ObjectDirectory::activate method
of one of its ObjectDirectories.
The concept of ObjectDirectory is very important for the distributed case If you want your application to be able to use a server running on a distant machine, you just need to make sure there is an ObjectDirectory running on that distant machine and tell your local ActivationContext about it so that it calls the distant ObjectDirectory's get_servers method.
module Bonobo {
module Activation {
exception NotListed {};
exception AlreadyListed {};
};
interface ActivationContext : Bonobo::Unknown {
readonly attribute ObjectDirectoryList directories;
void add_directory (in ObjectDirectory dir)
raises (Bonobo::Activation::AlreadyListed);
void remove_directory (in ObjectDirectory dir)
raises (Bonobo::Activation::NotListed);
};
};
All you need to do to setup your system for the above scenario is to use the Bonobo::ActivationContext::add_directory method. An activation sample case is described below.
The only thing left for you to become a real Bonobo Activation master is to figure out how to query the ActivationContext for real objects and how to activate them.
module Bonobo {
interface ActivationContext : Bonobo::Unknown {
ActivationResult activate (in string requirements,
in Bonobo::StringList selection_order,
in ActivationFlags flags)
raises (Bonobo::Activation::ParseFailed,
Bonobo::Activation::IncompleteContext,
Bonobo::GeneralError)
context ("username", "hostname", "domain", "display");
ServerInfoList query (in string requirements,
in Bonobo::StringList selection_order)
raises (Bonobo::Activation::ParseFailed,
Bonobo::Activation::IncompleteContext)
context ("username", "hostname", "domain");
ActivationResult activate_from_id (in ActivationID aid, in ActivationFlags flags)
raises (Bonobo::Activation::ParseFailed,
Bonobo::Activation::IncompleteContext,
Bonobo::GeneralError)
context ("username", "hostname", "domain", "display");
};
};
The above declaration is what was left for you to discover. Three methods are of importance:
The activate_from_id method is the simplest one. It activates a server given an ActivationID. ActivationIDs' format is pretty simple:
OAFAID:[IID,user,host,domain]
As shown above, an AID contains the IID of the relevant server. The idea
is that the triplet user, host, domain identifies an
ObjectDirectory within an ActivationContext and the IID identifies the
CORBA server within the ObjectDirectory.
query and activate are very similar: both run a query given the requirements string. query will return the result of the query while activate will actually activate the best-matching server.
The result of this query is a Bonobo::ServerInfoList which is nothing but a sequence (ie: variable-sized array) of Bonobo::ServerInfo structures. Each Bonobo::ServerInfo contains a variable sized array of Bonobo::ActivationProperty structures each of which contains the name and value of one of the corresponding server properties.
module Bonobo {
struct ActivationProperty {
string name;
ActivationPropertyValue v;
};
/* Server */
struct ServerInfo {
ImplementationID iid;
string server_type;
string location_info;
string username, hostname, domain;
sequence<ActivationProperty> props;
};
typedef sequence<ServerInfo> ServerInfoList;
};
The folowing piece of C code will parse the resulting list for example.
for (i = 0; i < result->_length; i++) {
Bonobo_ServerInfo *server;
server = &result->_buffer[i];
}
The queries you can send to the activation daemon are specified as an ASCII string encoded in the Grand query langage. This langage is more detailed in the Bonobo Activation reference documentation but we will give an overview here nonetheless.
For exemple, the query below would match against all the components which both support the CORBA interfaces Bonobo::Control and Bonobo::ContentView or which support either Bonobo::Control or Bonobo::Embeddable as long as they support the Bonobo::PersistFile interface, and define the attribute foo:bar.
"(has_all (repo_ids, ['IDL:Bonobo/Control:1.0',
'IDL:Nautilus/ContentView:1.0'])
OR has_one (repo_ids, ['IDL:Bonobo/Control:1.0',
'IDL:Bonobo/Embeddable:1.0']))
AND has (repo_ids, 'IDL:Bonobo/PersistFile:1.0')
AND defined(foo:bar)"
Also, The above query could be used in Gnumeric to get access to its graph component. If you are sick about the default gnumeric graph component, you could develop your own or buy one from company X and install it. It would then be used by Gnumeric to provide graphs into all your spreadsheets.
has (repo_ids, 'IDL:GNOME/Graph/Layout:1.0')
The rationale behind this query langage is to use it to request a functionality rather than a particular implementation of a functionality. That is, it is considered Evil to build a query such as iid == 'OAFIID:myprogram:UUID' which would request a specific implementation of a component. Programmers should rather use a query such as repo_ids.has ('IDL:my_intername_name:version_number').
The activation query langage is very much like SQL in spirit: each server defines a set of properties and the query langage allows you to make tests on these properties. Each property has a type which is one among those defined below:
Table 2.1. Query Langage Types
| Name | Example | Description |
|---|---|---|
| string | 'mystring' | as in SQL, delimited by single quotes. |
| stringv | ['red','blue'] | string arrays: a comma-separated list of strings, surrounded by square brackets. |
| number | '3.1415' | Floating point decimals. |
| boolean | 'TRUE' | TRUE or FALSE (other common boolean value identifiers are also accepted, but not encouraged). |
Your queries can use any of the folowing functions:
Table 2.2. Query Langage Functions
| Name | Returns | Example | Description |
|---|---|---|---|
| defined (expression) | boolean | defined (repo_ids) | indicates whether the given expression is defined for the current record. For example, using a field name would indicate whether that field is defined for the record or not. |
| has_one (stringv1, stringv2) | boolean | has_one (['red','blue'], ['red']) | indicates whether any of the strings in the 'stringv2' array are contained in the 'stringv1' array. |
| has_all (stringv1, stringv2) | boolean | has_one (['red','blue'], ['red']) | indicates whether all of the strings in the 'stringv2' array are contained in the 'stringv1' array. |
| has (stringv, string) | boolean | has (['red','blue'], 'red') | indicates whether 'string' is contained in the 'stringv' array. |
| prefer_by_list_order (string, stringv) | number | prefer_by_list_order ('red', ['red', 'blue']) | This function is intended to use as a sort condition when you have a prioritized list of preferred values. It returns -1 if the 'string' is not in the 'stringv' array, otherwise it's position measured from the end of 'stringv'. The result is that the first item is most preferred, items after that are next most preferred, and items not in the list are lowest priority. |
| max (expression) | expression | max (field_name) | Evaluates 'expr' over all the available server information records in the database, and returns the maximum value as dictated by the normal sort order for the data type of 'expr'. This function is not valid for string vectors. |
| min (expression) | expression | min (field_name) | Evaluates 'expr' over all the available server information records in the database, and returns the minimum value as dictated by the normal sort order for the data type of 'expr'. This function is not valid for string vectors. |
Bonobo Activation specifies a set of standardized fields some of which are mandatory and others which you are free to use for integration in the GNOME environment. These fields are described in the Bonobo Activation reference documentation but we will include the most important ones here.
Table 2.3. Mandatory fields of Bonobo Activation records.
| Name | Type | Description |
|---|---|---|
| iid | string | Used by the ObjectDirectory to uniquely identify your component. It should be _unique_ among all the other components installed on your system. To do so, we recommend using the uuidgen utility. This utility creates UUIDs which can reasonably be considered unique among all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future (cf: man uuidgen). |
| server_type | string | Specifies the type of the server. The value can be: exe, factory or shlib. |
| location_info | string | Describes how Bonobo Activaion should find the corresponding server. For an exe server, it is the name of the executable, for an shlib server, it is the name of the shared library to load. |
| hostname | string | Contains the name of the machine the corresponding server can be found on. |
"iid == 'OAFIID:oaf_naming_service:7e2b90ef-eaf0-4239-bb7c-812606fcd80d'" "server_type == 'shlib'" "location_info == 'bonobo-activation-server'" "hostname == 'le-hacker.org'"It is actually really easy to try out these examples with the bonobo-activation-run-query test program: bonobo-activation-run-query "server_type == 'shlib'" will print on screen the list of the servers which match this query. You can experiment more with those by combining tests through boolean operators and you can use a set of many different operators on the fields: those are described in Bonobo Activation reference manual.
The four fields above are actually mandatory but Bonobo Activation defines a set of very important fields: none of them are mandatory but not defining them for your components would not make much sense.
Table 2.4. Normalized attributes
| Attribute name | Type | Signification | Mandatory ? |
|---|---|---|---|
| repo_ids | stringv | the list of all IDL interfaces this component implements | yes |
| description | string | a human readable string describing what the component can do | yes |
| name | string | a short name for the component | yes |
| bonobo:editable | boolean | if component allows editing of its content | no |
| bonobo:supported_mime_types | stringv | a list of mime types this component understands as input. In addition to specific mime types, it is possible to include supertypes (e.g. "image/*" or "text/*") or "*/*" to indicate the component can display any mime type. Specifying "*/*" is only necessary if "supported_uri_schemes" is not specified, otherwise it is assumed. This only really makes sense if the component implements one of the following interfaces: Bonobo::PersistStream, Bonobo::ProgressiveDataSink, Nautilus::View. | no |
| bonobo:supported_uri_schemes | stringv | a list of protocols this component knows how to handle. This only really makes sense if the component implements one of the following interfaces: Bonobo::PersistFile or Nautilus::View | no |
"bonobo:editable == TRUE" "has_one (repo_ids, ['IDL:Bonobo/Embeddable:1.0']) AND bonobo:editable == TRUE" "has_all (bonobo:supported_mime_types, ['image/x-png', 'image/png', 'image/jpeg'])" "has_one (bonobo:supported_uri_schemes, ['hardware', 'file'])"
Running a query on the Activation daemon is possible provided the daemon learns about the servers available on the system. Our implementation of the Activation interfaces (as provided in the Bonobo Activation package) provides a nice way to do so. Each CORBA server is supposed to provide an xml-based configuration file which describes its fields for the Bonobo Activation database. When installing your CORBA server, you are supposed to make sure the activation daemon implementation can read this file by editing the ${prefix}/etc/bonobo-activation/bonobo-activation-config.xml file [12] which lists the directories to browse to find the per-server configuration .server files.
Here is an example of this bonobo-activation-config.xml file:
<?xml version="1.0"?> <oafconfig> <searchpath> <item>/opt/gnome/share/oaf</item> <item>/usr/local/gnome/share/oaf</item> <item>/usr/local/share/oaf</item> <item>/opt/gnome/oaf/share/oaf</item> </searchpath> <searchpath> <item>/opt/gnome/oaf/share/oaf</item> </searchpath> </oafconfig>The activation daemon will check upon each request if these directories have not been modified since the last request to make sure it does not forget any new CORBA server.
The .server format is extremly simple:
<oaf_server iid="OAFIID:oaf_naming_service:7e2b90ef-eaf0-4239-bb7c-812606fcd80d"
type="exe" location="oafd">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:CosNaming/NamingContext:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="Name service"/>
<oaf_attribute name="description" type="string" value="CORBA CosNaming service."/>
</oaf_server>
The above file is distributed with Bonobo Activation and describes the activation
daemon itself.
Each <oaf_server> tag describes a CORBA server. This tag specifies the
type, location and
iid fields An <oaf_server> tag contains any number
of <oaf_attribute> tags each of which describes fields of the server record.
These fields have three properties: name,
type and value. The different types
available are those of the query langage and we allready went through them.
The main attributes you can specify are available above and all of them are
described in the Bonobo Activation documentation.
This section is more a howto than anything else but it will help you to get started quickly and to write your own Bonobo Activation-enabled CORBA applications easily.
Accessing all the CORBA interfaces we described by hand is of course possible (you might want to write some Java CORBA servers or clients... or C++ ??) but since we know the pain CORBA is (yes, you already hate CORBA ;-), the Bonobo Activation authors have written a nice C API which you can use to ease your life.
The client side of the library is extremly simple: there are 4 functions exported:
CORBA_ORB bonobo_activation_orb_init (int *argc,
char **argv);
Bonobo_ServerInfoList *bonobo_activation_query (const char *requirements,
char *const *selection_order,
CORBA_Environment * ev);
CORBA_Object bonobo_activation_activate (const char *requirements,
char *const *selection_order,
Bonobo_ActivationFlags flags,
Bonobo_ActivationID * ret_aid,
CORBA_Environment * ev);
CORBA_Object bonobo_activation_activate_from_id (const Bonobo_ActivationID aid,
Bonobo_ActivationFlags flags,
Bonobo_ActivationID * ret_aid,
CORBA_Environment * ev);
The folowing code sample demonstrates the most simple use of these functions:
#include <orbit/orbit.h>
#include <stdio.h>
#include <bonobo-activation/bonobo-activation.h>
int
main (int argc, char *argv[])
{
CORBA_Object obj_ref;
CORBA_Environment ev;
CORBA_exception_init(&ev);
bonobo_activation_init (argc, argv);
obj_ref = bonobo_activation_activate ("server_type == 'shlib'",
NULL, 0, NULL, &ev);
if (CORBA_OBJECT_NIL == obj_ref
|| ev._major != CORBA_NO_EXCEPTION) {
printf ("Could not bind to server\n" );
return 1;
}
/* from here on, obj_ref holds a valid object reference */
CORBA_Object_release( obj_ref, &ev);
CORBA_exception_free ( &ev );
return 0;
}
You will also find in our sample code
(c-client-activate/calculator-client.c) a complete
simple Bonobo Activation client.
bonobo_activation_activate's first two parameters are the same as bonobo_activation_query's first two parameters. They specify the query string to be used when requesting a server to the activation demon. The requirements parameter is the query string proper while the selection_order parameter is an array of query strings used to sort the output of the query in an ascending order.
static char *nautilus_sort_criteria[] = {
/* Prefer anything else over the loser view. */
"iid != 'OAFIID:nautilus_content_loser:95901458-c68b-43aa-aaca-870ced11062d'",
/* Prefer anything else over the sample view. */
"iid != 'OAFIID:nautilus_sample_content_view:45c746bc-7d64-4346-90d5-6410463b43ae'",
/* Sort alphabetically */
"name",
NULL
};
The above code was stolen from nautilus and shows how nautilus tries to
avoid the "loser" view which is nothing but a view used for debugging.
Sorting an output list with a criterion means evaluating the criterion against each element of the list and ordering the resulting set of values. In the (non-trivial) example above, we first sort with the content_loser criterion which means there will be two kind of servers: the ones which evaluate to TRUE (set A) and the ones which evaluate to FALSE (set B). Set A will be sorted at the end of the resulting output because TRUE > FALSE. Now, within sets A and B, we apply the second sorting criterion which will give out 4 sets. Finally, within each of the four set, we apply the third criterion. This criterion will order each server given their name. That is, it will order them in the alphabetical order, the ones starting with A first in the final output list.
Very few people use this parameter when querying the activation daemon because it is both difficult to understand how to set it up to do something sensible and the power it provides is very scarcely needed. I thus strongly suggest you to forget about its use (If you want to really use it, you'll prolly have to read the source code for the function named qexp_sort located in bonobo-activation-query.c in Bonobo Activation).
The flags parameter is used to specify the way the component is activated. It can take any of the folowing values: Bonobo_ACTIVATION_FLAG_NO_LOCAL, Bonobo_ACTIVATION_FLAG_PRIVATE or Bonobo_ACTIVATION_FLAG_EXISTING_ONLY. If the required server is already activated, Bonobo_ACTIVATION_FLAG_EXISTING_ONLY will make sure it is not created a second time. If Bonobo_ACTIVATION_FLAG_PRIVATE is set, the required server will not register in the CORBA NamingServer so that only the caller knows about this version of the server. If Bonobo_ACTIVATION_FLAG_NO_LOCAL is set, FIXME: I have no idea about what this thing does. I tried to figure it out through the code but could not find out.
The ret_aid parameter will contain a pointer to the AID of the activated server and the ev parameter will contain the status of the CORBA operations involved in the library.
It should be noted that using bonobo_activation_activate is actually really simple and users can happily forget about most of the parameters and NULLify or zeroify the ones which do not interest them. As an exemple, you can call bonobo_activation_activate as demonstrated below:
obj_ref = bonobo_activation_activate ("server_type == 'shlib'", NULL, 0, NULL, &ev);
bonobo_activation_query has only tree parameters. We already went through them all for bonobo_activation_activate. bonobo_activation_query will thus return a list of the servers which match the given requirements and in the given selection_order order.
Clients can then manipulate the list by parsing it directly and using some bonobo-activation conveniance functions to copy and store elements of the list. These functions are described in the bonobo-activation reference manual.
result = bonobo_activation_query (...);
for (i = 0; i < result->_length; i++) {
Bonobo_ServerInfo *server;
server = &result->_buffer[i];
}
How to setup Bonobo Activation to use remote objects. We need to look at the code in gnome-libs HEAD which implements something to do this. hints ?
Writing your own CORBA server activated by the activation daemon is not very complex from an API point of view. A simple example of this is available in c-server-activate-exe/calculator-exe-server.c and c-server-activate-shlib/calculator-shlib-server.c. To run and test this example, you should use the small client provided in c-client-activate/calculator-client.c: calculator-client --exe or calculator-client --shlib will start either the executable version of the calculator server or the shared library version. Please, read README for details on using this example.
To implement the CORBA server proper, we reuse the code from c-server/calculator-skelimpl.c which exports only one function: impl_create.
This function is used by both calculator-exe-server.c and calculator-shlib-server.c. The former, when compiled, will generate an executable which can be activated by Bonobo Activation and the latter a library which can be also activated by Bonobo Activation.
Creating an executable server implementing the calculator interface is as simple as calling two functions: bonobo_activation_active_server_register and bonobo_activation_active_server_unregister.
Bonobo_RegistrationResult bonobo_activation_active_server_register (const char *iid,
CORBA_Object obj);
void bonobo_activation_active_server_unregister (const char *iid,
CORBA_Object obj);
Once you have created the actual CORBA implementation, you should
register it and when the CORBA server decides it
wants to destroy itself, it is supposed to unregister
itself before destroying its implementation.
This process is pretty easy to understand with the folowing code.
#include <stdio.h>
#include <bonobo-activation/bonobo-activation.h>
#include "calculator.h"
extern calculator impl_create (PortableServer_POA poa, CORBA_Environment * ev);
int
main (int argc, char* argv[])
{
PortableServer_POA poa;
calculator objref;
CORBA_Environment ev;
CORBA_ORB orb;
CORBA_exception_init (&ev);
orb = bonobo_activation_init (argc, argv);
/* bootstrap the poa */
poa = (PortableServer_POA) CORBA_ORB_resolve_initial_references ( orb, "RootPOA", &ev );
PortableServer_POAManager_activate (PortableServer_POA__get_the_POAManager ( poa, &ev ), &ev);
/* create the implementation */
objref = impl_create (poa, &ev);
if (objref == CORBA_OBJECT_NIL
|| ev._major != CORBA_NO_EXCEPTION) {
printf ( "Cannot get objref\n" );
return 1;
}
/* tell bonobo-activation about this new implementation */
bonobo_activation_active_server_register ("OAFIID:calculator:20000527", objref);
/* get into glib main loop so that the server can actually receive requests */
while (1)
g_main_iteration (TRUE);
/* destroy this implementation. We will never do this in this particuliar
code because the server will never know when to destroy itself but if he
wanted to do it, this is what he should do.
bonobo_activation_active_server_unregister ("OAFIID:calculator:20000527", objref);
impl_calculator__destroy (poa, obj_ref, &ev);
*/
return 0;
}
The only problem about this server is that it will never unload itself
from memory so it will never call
bonobo_activation_active_server_unregister. Bonobo solves
this by defining the Bonobo::Unknown interface.
which manages the lifetime of the CORBA server. This topic will be covered in
the next chapter though.
Building a shared library server is also as simple as calling two functions: bonobo_activation_plugin_use and bonobo_activation_plugin_unuse.
void bonobo_activation_plugin_use (PortableServer_Servant servant,
gpointer impl_ptr);
void bonobo_activation_plugin_unuse (gpointer impl_ptr);
A library which wants to be able to create CORBA servers for the
activatoin daemon should define a global variable named
Bonobo_Plugin_info of
type BonoboActivationPlugin.
typedef struct {
const char *iid;
CORBA_Object (*activate) (PortableServer_POA poa,
const char *iid,
gpointer impl_ptr,
CORBA_Environment *ev);
} BonoboActivationPluginObject;
typedef struct {
const BonoboActivationPluginObject *plugin_object_list;
const char *description;
} BonoboActivationPlugin;
Here is an example on how to use these structures:
static CORBA_Object
calculator_shlib_make_object (PortableServer_POA poa,
const char *iid,
gpointer impl_ptr,
CORBA_Environment *ev)
{
return CORBA_OBJECT_NIL;
}
/*
This structure contains the list of plugins
bonobo-activation can load from this library.
Each plugin has an IID and a _factory_ function.
*/
static const BonoboActivationPluginObject calculator_plugin_list[] = {
{
"OAFIID:calculator:20000923",
calculator_shlib_make_object
},
{
NULL
}
};
/* exported symbol which bonobo-activation tries to find to
bootstrap the library */
const BonoboActivationPlugin Bonobo_Plugin_info = {
calculator_plugin_list,
"Calculator example"
};
When the activation daemon receives a call asking for the OAFIID:calculator:20000923 component, it will end up looking into our calculator.so library if we gave Bonobo Activation the folowing .server file.
<oaf_info>
<oaf_server iid="OAFIID:calculator:20000923" type="shlib" location="calculator.so">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:calculator:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="calculator"/>
<oaf_attribute name="description" type="string" value="Example of an shlib"/>
</oaf_server>
</oaf_info>
Bonobo Activation will thus first load the library in memory. Then it will try to find a symbol named Bonobo_Plugin_info. It uses this symbol to access the list of plugins to which the first field of Bonobo_Plugin_info points to. Each BonoboActivationPluginObject structure contains a function which can be called to get a CORBA object and the IID of the CORBA object this function will return.
Our implementation of the calculator server through a shared library is thus straightforward. We just need to implement the function which returns the CORBA Object reference to our server implementation.
static CORBA_Object
calculator_shlib_make_object (PortableServer_POA poa,
const char *iid,
gpointer impl_ptr,
CORBA_Environment *ev)
{
CORBA_Object object_ref;
/* FIXME: is this necessary ? */
PortableServer_POAManager_activate (PortableServer_POA__get_the_POAManager (poa, ev), ev);
object_ref = impl_create (poa, ev);
if (object_ref == CORBA_OBJECT_NIL
|| ev->_major != CORBA_NO_EXCEPTION) {
printf ("Server cannot get objref\n");
return CORBA_OBJECT_NIL;
}
bonobo_activation_plugin_use (poa, impl_ptr);
return object_ref;
}
You should call bonobo_activation_plugin_use with poa and impl_ptr as parameters. You should not really worry about the details of this API. The only thing you need to make sure is to store the impl_ptr to be able to call bonobo_activation_plugin_unuse later when the library wants to destroy itself. However, as in the precedent example (ie: the executable server), we cannot demonstrate this since our simple server will never unload itself from memory.
[11] IIDs start with an OAFIID string because Bonobo Activation used to be called OAF in Gnome 1.4.x and was renamed for Gnome 2.0.x. Unfortunatly, the renaming was not complete as we will show you again later...
[12] The bonobo-activation-sysconf command-line based tool can be used to add and remove entries from this file.
Table of Contents
The Bonobo::Unkonwn interface is used everywhere in Bonobo: all important Bonobo interfaces inherit from Bonobo::Unkonwn. As such, it is very imlportant to understand its design. Its methods can be split functionaly-wise in two groups:
The ref and unref methods belong to the first group while queryInterface belongs to the first. all COM and XPCOM programmers have already recognized in Bonobo::Unknown COM's IUnknown interface. As such, they can skip the next few sections.
All readers are expected to know what reference counting is and how it works. ref, which increases the server's reference count, and unref, which decreases the server's reference count, are used by the client to notify the server when it needs to use it and when it does not need it anymore. Servers are allowed to destroy themselves when their reference count reaches zero. Server's reference count is supposed to be initialized to one.
module Bonobo {
interface Unknown {
/**
* ref:
*
* increments the reference count
*/
void ref ();
/**
* unref:
*
* decrements the reference count
*/
void unref ();
/**
* queryInterface:
* @repoid: A string identifying an interface.
*
* Returns: A CORBA object exposing the interface
* specified by @repoid, or a nil object if the
* interface cannot be queried.
*/
Unknown queryInterface (in string repoid);
};
};
These two methods allow us to solve a problem we briefly discussed in the previous chapter. Namely, how a server can detect that it is not used anymore by clients and that it can safely decide to stop processing client's requests.
The folowing IDL shows how we modified the calculator example we discussed in the previous chapters to include ref and unref support. This IDL can be found in idl/calculator-unknown.idl.
#include <Bonobo_Unknown.idl>
interface calculator : Bonobo::Unknown {
// increment version number
#pragma version calculator 2.0
// add two numbers, return the result
long add ( in long x, in long y );
// calculate the sum of a sequence of numbers
typedef sequence<long> Numbers;
long sum ( in Numbers numbers );
// divide a number
exception DivideByZero {};
double divide ( in long x, in long y ) raises ( DivideByZero );
};
The only thing we did was adding the Unknown interface
as a base interface for the calculator interface.
We also incremented the version number of this interface to make sure that the
ORB will not mistaken a normal calculator server with a calculator server
supporting the Unknown interface.
As previously, we generated the skelimpl.c file with orbit-idl-2 and edited it to add the needed implementations. This time, we modified the impl_calculator__create function so that it returns a servant rather than an object reference:
static impl_POA_calculator *
impl_calculator__create(PortableServer_POA poa, CORBA_Environment * ev)
{
impl_POA_calculator *newservant;
PortableServer_ObjectId *objid;
newservant = g_new0(impl_POA_calculator, 1);
newservant->servant.vepv = &impl_calculator_vepv;
newservant->poa = poa;
POA_calculator__init((PortableServer_Servant) newservant, ev);
objid = PortableServer_POA_activate_object(poa, newservant, ev);
CORBA_free(objid);
return newservant;
}
Of course, we added the impl_create function to export it:
calculator
impl_create (PortableServer_POA poa, void (*destroy_callback) (calculator), CORBA_Environment * ev)
{
calculator retval;
impl_POA_calculator *servant;
servant = impl_calculator__create (poa, ev);
servant->destroy_callback = destroy_callback;
servant->refcount = 1;
retval = PortableServer_POA_servant_to_reference(servant->poa, servant, ev);
return retval;
}
This function now takes a new parameter: the destroy_callback
function pointer which will be invoked by the servant when the reference count reaches
zero. This function pointer is used to initialize the servant's corresponding field
we added in impl_POA_calculator:
typedef struct
{
POA_calculator servant;
PortableServer_POA poa;
/* folowing lines added */
int refcount;
void (*destroy_callback) (calculator object);
}
impl_POA_calculator;
The reference count is finally initialized to one to take into account the reference
we return from our constructor.
The implementations of impl_calculator_ref and impl_calculator_unref are straightforward:
static void
impl_calculator_ref(impl_POA_calculator * servant, CORBA_Environment * ev)
{
/* folowing line added */
servant->refcount++;
}
impl_calculator_ref simply increments the servant's refcount.
static void
impl_calculator_unref(impl_POA_calculator * servant, CORBA_Environment * ev)
{
calculator object;
/* folowing lines added */
servant->refcount--;
if (servant->refcount == 0) {
object = PortableServer_POA_servant_to_reference(servant->poa, servant, ev);
(*servant->destroy_callback) (object);
CORBA_Object_release (object, ev);
impl_calculator__destroy (servant, ev);
}
}
impl_calculator_unref, on the other hand, decrements the refcount
and destroys the server if the refcount reaches zero. The destruction is done in two steps:
first, we invoke the destroy_callback, second, we destroy the servant
structure itself.
The destroy callback will unregister the server from Bonobo Activation (code from
calculator-exe-server.c):
static void destroy_callback (calculator object)
{
bonobo_activation_active_server_unregister (IID, object);
}
and the servant destruction function does some CORBA magic which we do not need
to understand since it was automatically generated for us.
The server process itself will be killed by Bonobo Activation later. (XXX: is this true ?)