Writing Bonobo Components: the easy way

Dirk-Jan C. Binnema, http://www.djcbsoftware.nl
dirk-jan@djcbsoftware.nl

Abstract:

This article shows how creating a Bonobo components with both multiple and custom interfaces is not hard at all, using the gwizard emacs-lisp macros.

Introduction

There are some introductions to Bonobo that show some basic things. Up until now, there's not been a more advanced text (that I know of). I hope this article can help you to take the next step. If you don't know the basics of Bonobo, please read [1] (technical overview), [3] (how-to) or [7] first. Also, basic (working) knowledge of CORBA is assumed, and finally, you should know how to operate Emacs, because the gwizard tool I'll be using is Emacs based.

Big warning: A wizard may help you save some typing, but should never be a substitute for knowing what you are doing!

In this article I'll discuss the writing of a Bonobo component which has both

Actually, the same interfaces are both multiple and custom...

Creating Bonobo components is a lot easier with the right tools. Well, to save you from a lot of boring boilerplate code and RSI, I have written gwizard, a set of Emacs elisp-macros [2].

Components have feelings too

Let's introduce our example component: MoodyComponent. MoodyComponent is a, well, moody component: sometimes, it's in a good mood, and sometimes, it's in a bad mood.

We can model the moods of MoodyComponent as separate interfaces: one interface for the good mood, and one interface for the bad mood. Figure 1 gives an artist's impression of this.

Figure 1: Component exposing multiple interfaces
\begin{figure}\centerline{%%
\epsfxsize=80mm
\epsffile{moody-component.eps}} {}
\end{figure}

Writing the IDL

CORBA IDL is the language we use for the definition of the interfaces. We won't discuss CORBA IDL here any further; there are many places you can learn about that, for example [5], and of course there's always the official specification from the OMG [8].

Note that I put the interfaces in the Bonobo::Sample:: namespace. Also note that we're writing Bonobo interfaces, so by definition they derive (directly or indirectly, is this case directly) from Bonobo::Unknown. Figure 2 gives a UML-like class diagram.

Figure 2: MoodyComponent class diagram
\begin{figure}\centerline{%%
\epsfxsize=80mm
\epsffile{moods-uml.eps}} {}
\end{figure}

We can now express these interfaces in CORBA IDL. To prevent the polution of the IDL namespace, we place our Moody interfaces in the Bonobo::Sample namespace. Likewise, for the .idl-file, we choose the name Bonobo_Sample_Moody.idl (it would be stylistically ugly to call it Bonobo_Sample_Moody_Component.idl, because we'd like to maintain the separation of interface and implementation).

The actual IDL is very simple, and looks like this:

/*
 *  Bonobo_Sample_Moody.idl
 */

#include <Bonobo.idl>

module Bonobo {
        module Sample {
                interface GoodMood : Bonobo::Unknown {
                        string say_hello ();        
                };
        
                interface BadMood : Bonobo::Unknown {
                        string say_hi ();
                };
        };
};
If you wonder why GoodMood has say_hello and BadMood has say_hi, that's because I'd like to show they are really different; if they had the same name, some people may make the wrong assumptions.

In the example, we derive our interfaces directly from Bonobo::Unknown, because that's all we need. However, if we wanted to build a control we could derive for Bonobo::Control, if we wanted to support structured storage we could derive from Bonobo::Storage etc.

Implementation using Bonobo

Background: interfaces and implementation

CORBA IDL-interfaces are really all about interface inheritance: they specify what methods an interface support, but leave the implementation of those methods up to you. This holds also true for the Bonobo IDL, as they are CORBA IDL interfaces like any other.

However, together with the interfaces hierarchy, Bonobo comes with an implementation hierarchy that provides default implementations for the methods in the Bonobo interfaces. In the Bonobo source distribution, you'll find the IDL-interfaces in the idl/ directory, while the default implementations are in bonobo/

The mapping of IDL-interfaces upon implementation is mostly quite straightforward, for example the Bonobo::Print interface has a default implementation in BonoboPrint and Bonobo::Stream finds its counterpart in BonoboStream. The notable exception here is Bonobo::Unknown, which is implemented by BonoboXObject.

Also note that the implementations are done using the Gtk+ object system: they're GtkObjects in Gtk+/Glib/Gnome 1.x, but will be GObject's in Gnome2. However, we're discussing only the former here.

Generating the code

After we have specified the interfaces, we can implement them. This is quite easy, especially with gwizard; after installation we can do (in Emacs):
M-x gwizard-new-bonobo1-interface[RET]  
Interface: Bonobo::Sample::GoodMood[RET]
Parent: Bonobo::Unknown[RET]
Long names (y/n): n
License (1=GPL, 2=LGPL, 3=none): 1
So what are we doing here? Well:

That leaves us with the boilerplate implementation code for the GoodMood interface in good-mood.h and good-mood.c. I urge you to study the generated code; it's more-or-less a normal GtkObject, as described in [9], so it shouldn't be too hard.

Filling out the details

gwizard has done most of the boring work for use, but there are a couple of things we must do to finish the GoodMood-implementation:
  1. good-mood.h: Add the include file that CORBA created from the Bonobo_Sample_MoodyComponent.idl;
  2. good-mood.[ch]: Add the declarations and implementations for the say_hello function in the GoodMood interface;
  3. good-mood.c: connect our object implementation to our entry point vector; i.e. tell CORBA where it can find our implementation.

Let's do these things:

First, we add the include file with all the CORBA definitions. Remember our .idl is called Bonobo_Sample_Moody.idl; well, after IDL-compilation (which we haven't done yet), this will produce a file called Bonobo_Sample_Moody.h, and that's the one we must include here. So we add to good-mood.h, right under #include that's already there:

#include "Bonobo_Sample_Moody.h"

Ok, now for the say_hello implementation, in good-mood.h we add:

CORBA_char*  good_mood_say_hello (PortableServer_Servant servant,
                                  CORBA_Environment * ev);
(The signature can be deduced from the IDL$\to$C-mapping, or by grepping the generated Bonobo_Sample_Moody.h file for say_hello (you can compile it by hand with orbit-idl). The implementation in good-mood.c is also very simple; add the end of this file add:
static CORBA_char*
good_mood_say_hello (PortableServer_Servant servant,
                     CORBA_Environment *ev)
{
   return CORBA_string_dup ("Hi! How are you?");
}

Now, the last thing we need to do is hooking uo our implementation up with the so-called entry point vector (epv), in the good_mood_class_init:

    epv->say_hello = good_mood_say_hello;
And that's it! The final step tells CORBA where to find our implementation for the say_hello function.

What about the Bad Mood?

Not surprisingly, the implementation for BadMood is very similar:
M-x gwizard-new-bonobo1-interface[RET]  
Interface: Bonobo::Sample::GoodMood[RET]
Parent: Bonobo::Unknown[RET]
Long names (y/n): n
License (1=GPL, 2=LGPL, 3=none): 1

I guess it's a nice exercise for the reader to implement the BadMood-interface without any help... Oh, remember you must create a bad_mood_say_hi-method:

CORBA_char*     
bad_mood_say_hi  (PortableServer_Servant _servant,
                  CORBA_Environment * ev)
{
        CORBA_string_dup ("Grmpff... stop bothering me!");
}
Well, if you succeeded, we have now implemented the two interfaces. Hurray!

Writing the factory

Now we have implemented the two interfaces. To turn them into a component, we need to have a 'factory', a piece of software that produces components. Writing factories is very easy using the gwizard-new-bonobo1-factory macro in the gwizard- package:
M-x gwizard-new-bonobo1-factory[RET] 
Name: Bonobo::Sample::MoodyComponent[RET]
License (1=GPL, 2=LGPL, 3=none): 1
This generates bonobo-sample-moody-component.c and Bonobo_Sample_MoodyComponent.oaf.

Finishing bonobo-sample-moody-component.c is quite easy: add #include's for good-mood.h and bad-mood.h, and tie them together in the factory method:

static BonoboObject*
bonobo_sample_moody_component_factory (BonoboGenericFactory* factory,
                                       void* data)
{
        GoodMood *good_mood = good_mood_new ();
        BadMood  *bad_mood  = bad_mood_new ();
        
        bonobo_object_add_interface
                (BONOBO_OBJECT(good_mood), BONOBO_OBJECT(bad_mood));
       
        return BONOBO_OBJECT (good_mood);
}
You must select one ``primary'' interface (good_mood) in this case, to which you can add other interfaces. It doesn't matter which one you choose; just choose one.

Finishing the .oaf

Now, we've written all the code for the component, all that's left is registering it with OAF. The generated .oaf should mostly work, but what you still need to do is add the interfaces you implement:
<oaf_attribute name="repo_ids" type="stringv">
         <item value="IDL:Bonobo/Sample/GoodMood:1.0"/>
         <item value="IDL:Bonobo/Sample/BadMood:1.0"/>
</oaf_attribute>
There are some comments in the .oaf that indicate where to put this.

You should also check the location attribute of the Factory; make sure it's either somewhere in your PATH or fill in the path to the component executable.

Place the .oaf in your oaf-directory, which may be /usr/share/oaf, or anywhere in your OAF_PATH. Check the OAF-documentation [6] for details.

Compiling

Of course, we'd like to turn our work into some executable code. We write a little Makefile (this is not generated, but old-fashioned hand work):
#
# Makefile for bonobo-sample-moody-component
#

CORBA_GENERATED = \
        Bonobo_Sample_Moody-common.c \
        Bonobo_Sample_Moody-skels.c \
        Bonobo_Sample_Moody.h

OBJECTS = \
        Bonobo_Sample_Moody-common.o \
        Bonobo_Sample_Moody-skels.o \
        good-mood.o \
        bad-mood.o \
        bonobo-sample-moody-component.o 


bonobo-sample-moody-component: ${CORBA_GENERATED} ${OBJECTS}
        gcc -o $@ ${OBJECTS} `orbit-config --libs server`\ 
                      `gnome-config --libs bonobo gnomeui`

.c.o:   
        gcc -c $< `orbit-config --cflags server` `gnome-config --cflags bonobo` 

$(CORBA_GENERATED):
        orbit-idl --nostubs Bonobo_Sample_Moody.idl \ 
                           `gnome-config --cflags idl`

clean:
        rm -f *~ ${OBJECTS} ${CORBA_GENERATED} \
                 bonobo-sample-moody-component

Writing a client

We can't do anything with our newly created component if we don't have some client. However, I won't discuss the writing of a client here; but - fear not - a test client is included with gwizard, and looks a bit like this:
/*
 *  bonobo-sample-moody-client.c
 */
#include <gnome.h>
#include <liboaf/liboaf.h>
#include <bonobo.h>
#include "Bonobo_Sample_Moody.h"

int
main (int argc, char *argv[])
{
        CORBA_ORB orb;
        CORBA_Environment ev;

        BonoboObjectClient *server;

        Bonobo_Unknown moody_object;
        Bonobo_Sample_GoodMood good_mood;
        Bonobo_Sample_BadMood  bad_mood;
        
        gnome_init_with_popt_table ("bonobo-sample-moody-client", 
                                    "0.0.0", argc, argv,
                                    oaf_popt_options, 0, NULL);
        
        if ((orb = oaf_init (argc, argv)) == CORBA_OBJECT_NIL)
                g_error ("could not init orb\n");
        
        if (!bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL))
                g_error ("could not initialize bonobo\n");

        bonobo_activate ();
        
        if (!(server = bonobo_object_activate (
                      "OAFIID:Bonobo_Sample_MoodyComponent", 0)))
                g_error ("failed to create a moody component\n");
        
        CORBA_exception_init (&ev);
        moody_object = BONOBO_OBJREF (server);

        /*
         *  good mood
         */
        good_mood = bonobo_object_client_query_interface 
                (server, "IDL:Bonobo/Sample/GoodMood:1.0", &ev);
        if (BONOBO_EX(&ev))
                g_error ("error querying interface\n");
        else {
                char *msg = NULL;

                g_print ("Q: hey good mood component, how are you?\n");
                msg = Bonobo_Sample_GoodMood_say_hello (good_mood, &ev);
                if (BONOBO_EX(&ev)) 
                        g_warning ("error in say_hello\n");
                else
                        g_print ("A: %s\n", msg);
                
                CORBA_exception_free (&ev);
                CORBA_free (msg);
                
                bonobo_object_release_unref (good_mood, NULL); 
        }

        /* 
         * bad mood 
         */
        bad_mood = bonobo_object_client_query_interface 
                (server, "IDL:Bonobo/Sample/BadMood:1.0", &ev);
        if (BONOBO_EX(&ev))
                g_error ("error querying interface\n");
        else {
                char *msg = NULL;

                g_print ("Q: hey bad mood component, how are you?\n");
                msg = Bonobo_Sample_BadMood_say_hi (bad_mood, &ev);
                if (BONOBO_EX(&ev)) 
                        g_warning ("error in say_hello\n");
                else
                        g_print ("A: %s\n", msg);
                
                CORBA_exception_free (&ev);
                CORBA_free (msg);
                
                bonobo_object_release_unref (bad_mood, NULL);       
        }
        
                        
        CORBA_exception_free (&ev);
        bonobo_object_unref (BONOBO_OBJECT (server));
        
        return 0;
The corresponding Makefile can be found in the gwizard distribution.

I gladly admit the client looks quite complex for the little we do. This is partly because small programs have a big ratio of boilerplate code; another reason is that C-language binding is inherently rather low-level. You might want to consider the Bonobo Python binding instead [4]...

The impressive results

Now, let's compile the thing and we're ready for testing: we have already built our server (right?), and now we have a client too!
% ./bonobo-sample-moody-client 
Q: hey good mood component, how are you?
A: Hi, how are you!
Q: hey bad mood component, how are you?
A: Grmpff... stop bothering me!
Yes: it worked! You've reached the next level. Sit back and relax. And enjoy a short Zen moment of Bonobo enlightenment.

Conclusions

Hopefully, this article has shown that writing Bonobo components is not hard at all (I hope). Also, I hope it will encourage, you, the reader, to create some really cool Bonobo components - a little imagination, a little transpiration and a little elisp can do wonders...

gwizard is quite handy; however I need to stress again that a code generator can never be a substitute for understanding. Study the generated code. Understand it.

Future work on gwizard

gwizard is currenly alpha level software and probably still contains a number of bugs. It seems very useful though.

Anyway, apart from fixing bugs, there are some features I may or may not add.

Bibliography

1
Dirk-Jan Binnema.
Bonobo components: Architecture and application.
In Proceedings of the NLUUG Autumn Conference. NLUUG, November 2001.

2
Dirk-Jan Binnema.
Gwizard, an GNU/Emacs-based tool for GTK+/GNOME/Bonobo programming.
online, http://www.djcbsoftware.nl/projecten/emacs/gwizard-0.0.0.tar.gz, 2001.

3
Dirk-Jan Binnema.
On the Art of Writing Bonobo Controls.
available online, http://www.djcbsoftware.nl/projecten/bonobo-controls, 2001.

4
Johan Dahlin.
Bonobo-Python.
available online, http://bonobo-python.lajnux.nu/index.php, 2000.

5
M. Henning and S. Vinoski.
Advanced CORBA Programming with C++.
Addison-Wesley, 1999.

6
Mathieu Lacage.
Liboaf Library.
available online, http://developer.gnome.org, 2000.

7
Michael Meeks.
Introduction to Bonobo: Bonobo & ORBit.
available online, http://www-106.ibm.com/developerworks/components/library/co-bnbo1.html, 2001.

8
The Object Management Group (OMG).
OMG Hompage.
online, http://www.omg.org, 2001.

9
Havoc Pennington.
GTK+/GNOME Application Development.
New Riders, 1999.

About this document ...

Writing Bonobo Components: the easy way

This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.50)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html bonobo-gwizard.tex

The translation was initiated by Dirk-Jan C. Binnema on 2001-12-06


Dirk-Jan C. Binnema
2001-12-06