Bonobo foobarthing

Mathieu Lacage

Dirk-Jan C. Binema


Table of Contents

1. A small introduction to CORBA
Introduction
Distributed Systems
The www
The oldest Distriuted System: Sun's RPC
CORBA as a Distributed System
The IDL
A quick example
IDL Syntax
The C Mapping
Getting CORBA to work
CORBA::Object
Bootstrapping CORBA
The ORB dynamic framework
A simple CORBA client and server
Starting with the interfaces
A sample CORBA server in C
A sample CORBA client in C
2. Activating CORBA servers
Introduction
The Object Activation Framework
Why Bonobo Activation ? Why not Gnorba ?
Query langage
The .server files
The C API
Writing a client
The remote case
Building servers
3. Bonobo::Unknown
Lifecycle Management
The ref and unref methods
A sample implementation
The limits of Bonobo's refcounting architecture

Chapter 1. A small introduction to CORBA

Introduction

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:

Distributed Systems

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.

The www

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.

Figure 1.1. A www browse and a web server

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.

We will see in the folowing sections that each of the above layer can be well identified in each Distributed System examined, CORBA included.

The oldest Distriuted System: Sun's RPC

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.

The scope of the implementation issues begins to widen if you consider that the client could well be an x86 little endian based machine and the server could be a PPC big endian based machine: the serialization step cannot be just a dump of C values to a buffer (because of the endianess issue, a 32 bit integer leaving the client would have a different value on the server)...

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:

Figure 1.2. RPC code-flow vs normal function call code-flow

CORBA as a Distributed System

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:

  • At the langage level: a client written in C has to be able to access a server written in C++, Java or even Python :)

  • At the ORB (Object Request Broker: an ORB is a given CORBA implementation) level: a client, using a given CORBA implementation has to be able to talk to a server using another ORB.

Low level protocols

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]

Langage independance (IDL)

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 IDL

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).

A quick example

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.

Figure 1.3. CORBA design flow

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.

IDL Syntax

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

TypeSignification
short16 bit signed integer
unsigned short16 bit unsigned integer
long32 bit signed integer
unsigned long32 bit unsigned integer
float32 bit IEEE float
double64 bit IEEE float
booleanboolean value: TRUE or FALSE
octet8 bit byte
char8 bit character (ISO latin-1)
You can use these types directly or you can use them to create complex structures as follows:

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;

};
}
	  

The C Mapping

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.

Object Oriented programming in C

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:

  • Fully specify the method name by prepending the object name.

  • First parameter is always an object pointer which points to the instance of the object we want to apply the method upon.

Interfaces mapping

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.

Basic types

The types of these parameters is determined by the folowing mapping for the basic IDL types we presented previously.

Table 1.2. Basic CORBA types

IDL TypeC type mapping
shortCORBA_short
unsigned shortCORBA_unsigned_short
longCORBA_long
unsigned longCORBA_unsigned_long
floatCORBA_float
doubleCORBA_double
booleanCORBA_boolean
charCORBA_char

Interfaces' attributes

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.

Exceptions

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 () :).

Conclusion

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.

Getting CORBA to work

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
};
};
      

CORBA::Object

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.

Bootstrapping CORBA

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

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.

Naming/Activation Service

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 :).

The ORB dynamic framework

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.

  • Interface Repository

  • Dynamic Invocation Interface

  • Dynamic Skeleton Interface

Interface Repository

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.

Dynamic Invocation Interface

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.

Dynamic Skeleton Interface

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.)

A simple CORBA client and server

Starting with the interfaces

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]

A sample CORBA server in C

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

  • calculator-skels.h: skeleton code.

  • calculator-common.c: code used by the skeleton code.

  • calculator.h: header defining all the CORBA types implemented here.

  • calculator-skelimpl.c: fill-the-blanks sample implementation.

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.

A sample CORBA client in C

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:

  • calculator-stub.h: stub code.

  • calculator-common.c: code used by the stub code.

  • calculator.h: header defining all the CORBA types implemented here.

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.



[1] OMG

[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.

Chapter 2. Activating CORBA servers

Introduction

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...).

The Object Activation Framework

Why Bonobo Activation ? Why not Gnorba ?

Bonobo Activation is a replacement for the limited libgnorba library developed for the GNOME 1.x platform. Gnorba had a number of shortcommings:

  • It did not handled the remote case properly.

  • It was tied to the X windowing system: you could not activate CORBA servers when X was not running.

  • It had very limited query capabilities: you could request only one specific component by name.

The above limitations made it clear we needed a replacement.

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.

Figure 2.1. Bonobo Activation architecture

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.

Figure 2.2. Activation

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:

  • activate

  • query

  • activate_from_id

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];
}
	  

Query langage

A quick approach

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').

A more formal approach

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

NameExampleDescription
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

NameReturnsExampleDescription
defined (expression)booleandefined (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)booleanhas_one (['red','blue'], ['red'])indicates whether any of the strings in the 'stringv2' array are contained in the 'stringv1' array.
has_all (stringv1, stringv2)booleanhas_one (['red','blue'], ['red'])indicates whether all of the strings in the 'stringv2' array are contained in the 'stringv1' array.
has (stringv, string)booleanhas (['red','blue'], 'red')indicates whether 'string' is contained in the 'stringv' array.
prefer_by_list_order (string, stringv) numberprefer_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)expressionmax (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)expressionmin (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.

NameTypeDescription
iidstring 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_typestring Specifies the type of the server. The value can be: exe, factory or shlib.
location_infostring 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.
hostnamestring Contains the name of the machine the corresponding server can be found on.
Here folow a few examples of the kind of queries you can build for these fields.
"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 nameTypeSignificationMandatory ?
repo_idsstringvthe list of all IDL interfaces this component implementsyes
descriptionstringa human readable string describing what the component can doyes
namestringa short name for the componentyes
bonobo:editablebooleanif component allows editing of its contentno
bonobo:supported_mime_typesstringva 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_schemesstringva 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::Viewno
Using these fields in your queries is not very difficult either:
"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'])"
	    

The .server files

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.

The C API

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.

Writing a client

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:

  • bonobo_activation_init (to initialize your library),

  • bonobo_activation_query (to send queries to the Activation daemon),

  • bonobo_activation_activate (to activate a server through a query) and

  • bonobo_activation_activate_from_id (to activate a server with a given ID).

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

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

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];
      }
	    

The remote case

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 ?

Building servers

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.

Building an executable server

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

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.

Chapter 3. Bonobo::Unknown

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 methods used for the lifetime management of Bonobo servers/objects and

  • the methods used for interface aggregation.

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.

Lifecycle Management

The ref and unref methods

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.

A sample implementation

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 ?)

The limits of Bonobo's refcounting architecture