Quick Start with C

The programming language C is widely used for implementations of servers all over the world. Most often C is used because of performance and because of linking to third party libraries. Just imagine a huge application project shared by various companies each one implementing a specific module of the overall system. As all modules shall be linked to a single binary in the end, it is necessary to agree on APIs but also on binary compatibility. If choosing programming language C most compilers are binary compatible, in contrast to C++ compilers. The following chapter demonstrates how to add an ORBit2 remote method call interface to an existing library/module, using programming language C.

For some reason someone did implement a mailbox system for management of incoming and outgoing mails for his hometown postoffice and now the postoffice wants to offer its services in internet for distributed client/server interaction. The management did invest a big amount of money and time allready for the mailbox system and they would like to minimize risc and investment for the upcoming internet application. They have to choose between SOAP and CORBA. After some investigation they find out that SOAP would interfere with pre-existing Web server sharing same resources and as SOAP is based on stringified encoding it would not suffice performance criteria. In contrast, CORBA is based on binary encoding and up to 20 times faster, it is a widespread standard, widely used since years supporting strong security mechanisms. For that reason they make their choice for CORBA, adding a remote interfaces as thin wrapper for existing code. As we will see

Please have a look for the code here: postoffice C code

The interface of the mailbox system is given as C header file and looks like:

Table 5. counter.h

#ifndef __MAILBOX_H__
#define __MAILBOX_H__

#include <lib.h>

G_BEGIN_DECLS

typedef struct
{
      char* sender;
      char* recipient;
      char* content;
} Letter;

typedef struct
{
      GList* box;
} Mailbox;

Mailbox* mailbox_new  (void);
void     mailbox_free (Mailbox* mailbox);

gboolean  mailbox_has_letter_for   (Mailbox* mailbox, const char*   recipient);
Letter*   mailbox_fetch_letter_for (Mailbox* mailbox, const char*   recipient);
void      mailbox_send_letter      (Mailbox* mailbox, const Letter* letter);

G_END_DECLS

#endif /*  __MAILBOX_H__ */

NoteConst Annotation
 

If function arguments are declared as 'const' the function will copy the data if needed; the function will not store any reference to the user's data and therefore you may free the data any time after the method call.

Now the professionals design an extendible remote interface, that wraps the existing desktop system and introduces concepts of object oriented programming and exception handling. Finally they come to an IDL that equals the IDL we allready used for our python implementation of server/client, but, well, good design often comes to similar results **grin**

Table 6. postoffice.idl

module PostOffice {
      exception UnknownRecipient { string message; };
      exception UnknownSender    { string message; };
      exception MalformedContent { string message; };
     
      struct Letter {
        string      sender;
        string      recipient;
        string      content;
      };

      interface Counter {
        boolean   has_letter_for   (in string recipient);

        Letter fetch_letter_for (in string recipient)
           raises (UnknownRecipient);

        void   send_letter     (in Letter letter)
           raises (MalformedContent, UnknownSender, UnknownRecipient);
      };

};

Implementing the remote interface is quite simple. As first step you need to generate intermediate C code from IDL file. This code will do marshaling and demarshaling of data for you every time data is being exchanged between client and server.

bash$ orbit-idl-2  postoffice.idl
orbit-idl-2 2.4.1 compiling
 small mode, show preprocessor errors, passes: stubs skels common headers skel_impl imodule
   

This command has created a number of files, their role for client and server is shown in table:

Table 7. orbit-idl-2 generated files

 clientserver
exclusive filespostoffice-stubs.cpostoffice-skels.c
shared filespostoffice.h & postoffice-common.c

Server Application

The implementation of our server shall be based on those files generated previously, and two additional files 'postoffice-server.c' and 'postoffice-skelimpl.c'. Whereas the first file contains the 'main' function and is used to set up the servant, the latter is a template file generated from 'postoffice.idl' and must be extended by user for specific features of servant. The template file is going to be generated with command:

bash$ orbit-idl-2 --skeleton-impl postoffice.idl
orbit-idl-2 2.4.1 compiling
 small mode, show preprocessor errors, passes: stubs skels common headers imodule
      

The user shall insert its code into template file 'postoffice-skelimpl.c' which represents the adaptor to 'postoffice-skel.c'. For each interface and method declared in file 'postoffice.idl' a body fragment exists where the user shall insert its code. All interface methods are bundled in single virtual method table (vtable) that is handed over to to CORBA object manager. For each incoming request the object manager consults this vtable to find the corresponding method implementation.

Table 8. postoffice.h (vtable)

...
/** POA structures **/
#ifndef _defined_POA_PostOffice_Counter
#define _defined_POA_PostOffice_Counter 1
   typedef struct
   {
      void *_private;
       CORBA_boolean(*has_letter_for) (PortableServer_Servant _servant,
				       const CORBA_char * recipient,
				       CORBA_Environment * ev);
      PostOffice_Letter *(*fetch_letter_for) (PortableServer_Servant _servant,
					      const CORBA_char * recipient,
					      CORBA_Environment * ev);
      void (*send_letter) (PortableServer_Servant _servant,
			   const PostOffice_Letter * letter,
			   CORBA_Environment * ev);
   }
   POA_PostOffice_Counter__epv;
   typedef struct
   {
      PortableServer_ServantBase__epv *_base_epv;
      POA_PostOffice_Counter__epv *PostOffice_Counter_epv;
   }
   POA_PostOffice_Counter__vepv;
   typedef struct
   {
      void *_private;
      POA_PostOffice_Counter__vepv *vepv;
   }
   POA_PostOffice_Counter;
   extern void POA_PostOffice_Counter__init(PortableServer_Servant servant,
					    CORBA_Environment * ev);
   extern void POA_PostOffice_Counter__fini(PortableServer_Servant servant,
					    CORBA_Environment * ev);
#endif				/* _defined_POA_PostOffice_Counter */
...

The function POA_PostOffice_Counter__init represents the constructor whereas POA_PostOffice_Counter__fini represents the desctructor of managed PostOffice objects. These functions mark lifetime of PostOffice objects and must be invoked at beginning and end. These function are independend of user defined service interfaces and are registered seperately. The user may extend the destructor using a wrapper but must register its function for CORBA object manager in the following structure. This fragment of of postoffice-skelimpl.c shows all prototypes of interface methods and of user defiend desctructor and its registration for CORBA framework.

Table 9. orbit.h (object vtable)

...
/*** Implementation stub prototypes ***/

static 
void 
impl_PostOffice_Counter__destroy         (impl_POA_PostOffice_Counter *
   			                  servant, CORBA_Environment * ev);
static 
CORBA_boolean
impl_PostOffice_Counter_has_letter_for   (impl_POA_PostOffice_Counter * servant,
				          const CORBA_char * recipient,
				          CORBA_Environment * ev);

static
PostOffice_Letter *
impl_PostOffice_Counter_fetch_letter_for (impl_POA_PostOffice_Counter *   servant,
			                  const CORBA_char * recipient,
					  CORBA_Environment * ev);

static 
void
impl_PostOffice_Counter_send_letter      (impl_POA_PostOffice_Counter * servant,
				          const PostOffice_Letter * letter,
				          CORBA_Environment * ev);

/*** epv structures ***/

static PortableServer_ServantBase__epv impl_PostOffice_Counter_base_epv = {
   NULL,			/* _private data */
   (gpointer) & impl_PostOffice_Counter__destroy,	/* finalize routine */
   NULL,			/* default_POA routine */
};
static POA_PostOffice_Counter__epv impl_PostOffice_Counter_epv = {
   NULL,			/* _private */
   (gpointer) & impl_PostOffice_Counter_has_letter_for,

   (gpointer) & impl_PostOffice_Counter_fetch_letter_for,

   (gpointer) & impl_PostOffice_Counter_send_letter,
};

/*** vepv structures ***/

static POA_PostOffice_Counter__vepv impl_PostOffice_Counter_vepv = {
   &impl_PostOffice_Counter_base_epv,
   &impl_PostOffice_Counter_epv,
};
...

Now that we have these structures we can deal about creating instances. Well, but how shall its class structure look like? What attributes shall an object contain storing state? Here the generated file postoffice-skelimpl.c gives you a hand caontaining a predefined structure that fullfills all constrains of CORBA framework and may be extended further by user. Additional attributes may be declared at end of structure only.

Table 10. postoffice-skelimpl.c (object structure)

...
/*** App-specific servant structures ***/

typedef struct
{
   POA_PostOffice_Counter servant;
   PortableServer_POA poa;

   /* ------ add private attributes here ------ */
   Mailbox*     mailbox;             /* mailbox */
   /* ------ ---------- end ------------ ------ */
}
impl_POA_PostOffice_Counter;
...

The following function demonstrates how to extend the predefined function in postoffice-skelimpl.c with user code. In case of failures the user code must contain apropiat exception handling. Here the object is being destroyed and function returns the NIL object.

Table 11. postoffice-skelimpl.c (object creation)

...
/*** Stub implementations ***/

static PostOffice_Counter
impl_PostOffice_Counter__create(PortableServer_POA poa,
				CORBA_Environment * ev)
{
   PostOffice_Counter retval;
   impl_POA_PostOffice_Counter *newservant;
   PortableServer_ObjectId *objid;

   newservant = g_new0(impl_POA_PostOffice_Counter, 1);
   newservant->servant.vepv = &impl_PostOffice_Counter_vepv;
   newservant->poa = poa;
   POA_PostOffice_Counter__init((PortableServer_Servant) newservant, ev);
   /* Before servant is going to be activated all
    * private attributes must be initialized.  */

   /* ------ init private attributes here ------ */
   newservant->mailbox = mailbox_new();
   if (newservant->mailbox==NULL) 
   {
      /* creation of mailbox failed - finalize servant structures */ 
      POA_PostOffice_Counter__fini((PortableServer_Servant) newservant, ev);
      g_free(newservant);
      return CORBA_OBJECT_NIL;
   }
   /* ------ ---------- end ------------- ------ */

   objid = PortableServer_POA_activate_object(poa, newservant, ev);
   CORBA_free(objid);
   retval = PortableServer_POA_servant_to_reference(poa, newservant, ev);

   return retval;
}
...

In similar fashion user must extend the predefined destructor template. After the object has been deactivated and no further remote request can occure the object manager calls the user defined destructor, ie. impl_PostOffice_Counter__destroy. Here the user must free those resources the object did collect during lifetime. Finally the memory allocted by structure of servant itsself will be released by object manager. .

Table 12. postoffice-skelimpl.c (object destruction)

...
static void
impl_PostOffice_Counter__destroy(impl_POA_PostOffice_Counter * servant,
				 CORBA_Environment * ev)
{
   /* No further remote method calls are delegated to 
    * servant; you may free your private attributes now. */

   /* ------ free private attributes here ------ */
   mailbox_free (servant->mailbox);
   servant->mailbox = NULL;
   /* ------ ---------- end ------------- ------ */

   POA_PostOffice_Counter__fini((PortableServer_Servant) servant, ev);

   /* finally, object manager will release this structure */
}
...

Let's have a look at those three functions that are part of the interface. As you can see each CORBA function is a wrapper for some function of the mailbox system. In fact only little code has to be written for the remote invocation interface. Please note that the wrapper functions transform incoming middleware-data to data-structures used by the old mailbox system. The reason is that the ORBit system (CORBA) and the mailbox system are using different kinds of memory management. Data allocated by ORBit system should never be freed by code of mailbox system code and vice versa.

Table 13. postoffice-skelimpl.c (object method definitions)

...
static CORBA_boolean
impl_PostOffice_Counter_has_letter_for(impl_POA_PostOffice_Counter * servant,
				       const CORBA_char * recipient,
				       CORBA_Environment * ev)
{
   CORBA_boolean retval;

   /* ------   insert method code here   ------ */
   retval = mailbox_has_letter_for (servant->mailbox, recipient);
   /* ------ ---------- end ------------ ------ */

   return retval;
}

/* ... */

static PostOffice_Letter *
impl_PostOffice_Counter_fetch_letter_for(impl_POA_PostOffice_Counter *
					 servant,
					 const CORBA_char * recipient,
					 CORBA_Environment * ev)
{
   PostOffice_Letter *retval;

   /* ------   insert method code here   ------ */
   {
      Letter* letter = mailbox_fetch_letter_for (servant->mailbox, recipient);
      
      if (letter!=NULL)
      {
	 PostOffice_Letter* corba_letter = to_corba_letter (letter);
	 internal_letter_free (letter);
	 retval = corba_letter;
      }
      else
      {
	 /* throw exception */
	 PostOffice_UnknownRecipient* exdata
	    = PostOffice_UnknownRecipient__alloc();
	 exdata->message = CORBA_string_dup ("Unknown recipient");
	 
	 CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
			      ex_PostOffice_UnknownRecipient,
			      exdata);
	 retval = NULL;
      }
      
   }
   /* ------ ---------- end ------------ ------ */

   return retval;
}

static void
impl_PostOffice_Counter_send_letter(impl_POA_PostOffice_Counter * servant,
				    const PostOffice_Letter * letter,
				    CORBA_Environment * ev)
{
   /* ------   insert method code here   ------ */
   {
      Letter* internal_letter = to_internal_letter (letter);
      
      mailbox_send_letter (servant->mailbox, internal_letter);
      
      internal_letter_free (internal_letter);
   }
   /* ------ ---------- end ------------ ------ */
}
...

Three "helper" functions are going to be used for copying and freeing of data:

Table 14. postoffice-skelimpl.c

...
static
PostOffice_Letter*
to_corba_letter (const Letter* src)
{
   PostOffice_Letter* dest = PostOffice_Letter__alloc ();

   dest->sender    = CORBA_string_dup (src->sender);
   dest->recipient = CORBA_string_dup (src->recipient);
   dest->content   = CORBA_string_dup (src->content);

   return dest;
}

static
Letter*
to_internal_letter (const  PostOffice_Letter* src)
{
   Letter* dest = g_new0 (Letter, 1);

   dest->sender    = g_strdup (src->sender);
   dest->recipient = g_strdup (src->recipient);
   dest->content   = g_strdup (src->content);

   return dest;
}

static
void
internal_letter_free (Letter* letter)
{
   g_free (letter->sender);
   g_free (letter->recipient);
   g_free (letter->content);
   g_free (letter);
}
...

All that is missing now is setting up the application, including signal handling with specifi 'handler' function:

Table 15. postoffice-server.c


#include <signal.h>
#include <orbit/orbit.h>
#include <assert.h>
#include "postoffice.h"
#include "mailbox.h"

#include "postoffice-skelimpl.c"

/*   MACROS */
#define ABORT_IF_EXCEPTION(ev) if (ev->_major != CORBA_NO_EXCEPTION)        \
            { g_error ("caught exception %s", CORBA_exception_id (ev));     \
              CORBA_exception_free (ev); abort(); }

/*   VAR   */
static CORBA_ORB  global_orb = CORBA_OBJECT_NIL; /*global declaration of orb*/
static CORBA_Environment ev[1];

/*************************************************************************/
/****                     signal handler                              ****/
/*************************************************************************/

static
void 
signal_handler(int sig)
{
   // if using Linux-kernel 2.4 printing anything to console within
   // sighandler function is prohibited, as this might cause deadlocks

   // ignore further signals 
   struct sigaction ignore;

   ignore.sa_handler = SIG_IGN;

   sigemptyset (&ignore.sa_mask);

   ignore.sa_flags = 0;

   if (sigaction (SIGINT, &ignore, (struct sigaction *)0) == -1)
      abort ();

   if (sigaction (SIGTERM, &ignore, (struct sigaction *)0) == -1)
      abort ();

   if (sigaction (SIGHUP, &ignore, (struct sigaction *)0) == -1)
      abort (); 

   /*  Terminate event loop  */

   // usual code
   if (global_orb != CORBA_OBJECT_NIL)
   {
        CORBA_exception_init(local_ev);

        CORBA_ORB_shutdown (global_orb, FALSE, local_ev);
        IGNORE_IF_EXCEPTION (local_ev); 

        global_orb=CORBA_OBJECT_NIL;
   }
}

/*************************************************************************/
/****                            main                                 ****/
/*************************************************************************/

int
main(int argc, char *argv[])
{
   PostOffice_Counter               servant;
   PortableServer_POA               root_poa;
   PortableServer_POAManager        pm;
   CORBA_char                      *objref = NULL;

   struct sigaction sa;            /* New signal state */

   sa.sa_handler = signal_handler; /* Set handler function */
   sigfillset(&sa.sa_mask);        /* Mask all other signals while handler
				      runs */
   sa.sa_flags =0 |SA_RESTART;     /* Restart interrupted syscalls */
   
   if (sigaction (SIGINT, &sa, (struct sigaction *)0) == -1)
      abort ();
   
   if (sigaction(SIGHUP, &sa, (struct sigaction *)0) == -1)
      abort ();
   
   if (sigaction(SIGTERM, &sa, (struct sigaction *)0) == -1)
      abort();
   
   CORBA_exception_init(ev);

   /* create Object Request Broker (ORB) */

   global_orb = CORBA_ORB_init(&argc, argv, "orbit-local-orb", ev);
   ABORT_IF_EXCEPTION(ev);

   /* create Portable Object Adaptor (POA) */

   root_poa = (PortableServer_POA)
      CORBA_ORB_resolve_initial_references(global_orb,  "RootPOA", ev);
   ABORT_IF_EXCEPTION(ev);

   /* create servant instance(s) */   

   servant = impl_PostOffice_Counter__create (root_poa, ev);
   ABORT_IF_EXCEPTION(ev);

   /* activate POA Manager */

   pm = PortableServer_POA__get_the_POAManager(root_poa, ev);
   ABORT_IF_EXCEPTION(ev);

   PortableServer_POAManager_activate(pm, ev);
   ABORT_IF_EXCEPTION(ev);

   /* write objref to file */

   objref = CORBA_ORB_object_to_string(global_orb, servant, ev);
   ABORT_IF_EXCEPTION(ev);

   /* print ior to terminal */
   g_print ("%s\n", objref);
   
   CORBA_free (objref); 
   objref = NULL;

   /* enter main loop */
 
   CORBA_ORB_run(global_orb, ev);
   ABORT_IF_EXCEPTION(ev);

   /* main loop has been left due to CORBA_ORB_shutdown(.) */
 
   /* release managed object of servant */
   CORBA_Object_release(servant, ev);
   ABORT_IF_EXCEPTION(ev); 

   /* tear down the ORB */
   if (global_orb != CORBA_OBJECT_NIL)
   {
      /* going to destroy orb.. */
      CORBA_ORB_destroy(global_orb, ev);
      ABORT_IF_EXCEPTION(ev);
   }

   /* return with SUCCESS  */
   exit (0);
}

If you want to test this server, you can use the version implemented with Python, or you read further on. The next lines will explain how to write the client using programming language C.

Client Application

Lets write a first PostOffice-client using programming language C. This client will do synchronous method calls to server. Other examples shall demonstrate how to realize asynchronous method invocation with CORBA, but this is another story.

As you can see here is is simple to write exception safe CORBA clients.

Table 16. postoffice-client.c


#include <stdio.h>
#include <stdlib.h>

#include "postoffice.h"

/*   MACROS */
#define RAISED_EXCEPTION(_ev)  ((_ev)->_major != CORBA_NO_EXCEPTION)

#define IF_SYSTEM_EXCEPTION(_ev, op)                      \
if ((_ev)->_major == CORBA_SYSTEM_EXCEPTION)              \
   do { op;} while(0)

#define IF_USER_EXCEPTION(_ev, exception_id, op)          \
if((_ev)->_major == CORBA_USER_EXCEPTION                  \
   && (!(exception_id)                                    \
      || !strncmp (exception_id, CORBA_exception_id (_ev),\
		   strlen (exception_id))))               \
   do {op;} while (0)

#define ELSIF_USER_EXCEPTION(_ev, exception_id, op)       \
else if(((_ev)->_major == CORBA_USER_EXCEPTION)           \
   && (!(exception_id)                                    \
      || !strncmp (exception_id, CORBA_exception_id (_ev),\
		   strlen (exception_id))))               \
   do { op; } while(0)


#define ABORT_IF_EXCEPTION(_ev)                              \
if ((_ev)->_major != CORBA_NO_EXCEPTION) {                   \
  g_error(CORBA_exception_id (_ev));                         \
  CORBA_exception_free (_ev);                                \
  abort();                                                   \
}

#define IGNORE_IF_EXCEPTION(_ev)                             \
if ((_ev)->_major != CORBA_NO_EXCEPTION) {                   \
  CORBA_exception_free (_ev);                                \
}

/*   CONST */ 
#define PO_MAX_SEND 50

/*   VAR   */ 
CORBA_ORB         global_orb;  // accessible from signal handler

PostOffice_Letter letter1 = {"goldfinger&64;gnome.org",             // sender
			     "bond&64;gnome.org",                   // recipient
			     "what is your name?"};              // content

PostOffice_Letter letter2 = {"bond&64;gnome.org",                   // sender
			     "goldfinger&64;gnome.org",             // recipient
			     "My Name is Bond, James Bond!"};    // content

PostOffice_Letter letter3 = {"goldfinger&64;gnome.org",             // sender
			     "pussy&64;gnome.org",                  // recipient
			     "Please, show Mr. Bond his room."}; // content


/** reads an IOR-string from file
 *  @arg file containing IOR as character stream
 *  
 */
static 
gchar* 
import_objref (FILE* file)
{
   gchar *buf=NULL;
   
   fscanf (file, "%as", &buf);

   return buf;
}


static 
void
send_letter (PostOffice_Counter po, 
	     PostOffice_Letter *letter,
	     CORBA_Environment *ev)
{
   PostOffice_Counter_send_letter (po, letter, ev);
   if (RAISED_EXCEPTION(ev))
   {
      IF_SYSTEM_EXCEPTION (ev, ABORT_IF_EXCEPTION (ev));
      /* handles for specified user exceptions */
      ELSIF_USER_EXCEPTION (ev, ex_PostOffice_UnknownSender,
			    CORBA_exception_free (ev));
      ELSIF_USER_EXCEPTION (ev, ex_PostOffice_UnknownRecipient,
			    CORBA_exception_free (ev));
      ELSIF_USER_EXCEPTION (ev, ex_PostOffice_MalformedContent,
			    CORBA_exception_free (ev));
      /* abort if any other exception */
      ELSIF_USER_EXCEPTION (ev, NULL, ABORT_IF_EXCEPTION (ev));
   }	 
   g_print ("+ ");
}

static
gboolean
has_letter (PostOffice_Counter po, 
	    char *recipient,
	    CORBA_Environment *ev)
{
   CORBA_boolean has
      =PostOffice_Counter_has_letter_for (po, recipient, ev);
   if (RAISED_EXCEPTION(ev))
   { 
      /* handles for specified user exceptions */
      IF_SYSTEM_EXCEPTION (ev, ABORT_IF_EXCEPTION (ev));
      /* abort if any other exception */
      ELSIF_USER_EXCEPTION (ev, NULL, ABORT_IF_EXCEPTION (ev));
   }	 
   return has;
}

static 
void
fetch_letter (PostOffice_Counter po, 
	      char *recipient,
	      CORBA_Environment *ev)
{
   PostOffice_Letter *letter 
      = PostOffice_Counter_fetch_letter_for (po, recipient, ev);
   if (RAISED_EXCEPTION(ev))
   {
      g_print ("! ");
      IF_SYSTEM_EXCEPTION (ev, ABORT_IF_EXCEPTION (ev));
      /* handles for specified user exceptions */
      ELSIF_USER_EXCEPTION (ev, ex_PostOffice_UnknownRecipient, 
			    CORBA_exception_free (ev));
      /* abort if any other exception */
      ELSIF_USER_EXCEPTION (ev, NULL, ABORT_IF_EXCEPTION (ev));
   }	 
   else
   {
      /* -- do something with letter -- */
      /* ------------------------------ */
      
      /* free memory */
      
      PostOffice_Letter__freekids (letter, NULL); 
      CORBA_free (letter);
      g_print ("- ");
   }
}

int
main (int argc, char *argv[])
{
   long i=0;
   gchar              *objref;
   PostOffice_Counter  obj    = CORBA_OBJECT_NIL;

   CORBA_Environment ev[1] = {NULL};
   CORBA_exception_init(ev);

   global_orb = CORBA_ORB_init(&argc, argv, "orbit-local-orb", ev);
   ABORT_IF_EXCEPTION(ev);

   /* read from file */
   objref=import_objref (stdin);
   if (objref==NULL) abort;

   obj = (PostOffice_Counter) CORBA_ORB_string_to_object (global_orb, 
							  objref, ev);   
   ABORT_IF_EXCEPTION(ev);
   
   for (i=0; i<PO_MAX_SEND; ++i) send_letter (obj, &letter1, ev); 
   for (i=0; i<PO_MAX_SEND; ++i) send_letter (obj, &letter2, ev);
   for (i=0; i<PO_MAX_SEND; ++i) send_letter (obj, &letter3, ev);
   
   while (has_letter(obj, letter1.recipient, ev))
      fetch_letter (obj, letter1.recipient, ev);
   
   while (has_letter(obj, letter2.recipient, ev))
      fetch_letter (obj, letter2.recipient, ev);
   
   while (has_letter(obj, letter3.recipient, ev))
      fetch_letter (obj, letter3.recipient, ev);
   
   /* trigger exceptions */
   for (i=0; i<PO_MAX_SEND; ++i)
      fetch_letter (obj, "foo", ev);
   ABORT_IF_EXCEPTION(ev);  /* no exception should reach this point */

   CORBA_Object_release(obj, ev);
   ABORT_IF_EXCEPTION(ev);

   CORBA_ORB_destroy(global_orb, ev);
   ABORT_IF_EXCEPTION(ev);

   g_print ("\n");

   exit(0);
}

Now you can compile the the code and invoke server and client:

bash$ ./postoffice-server > objref.ior
bash$ ./postoffice-client < objref.ior

Bonobo Activation

FIXME, write this section