Bonobo Components: Architecture and Application

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

Abstract:

Bonobo is the component technology that is part of the GNOME desktop environment. This paper discusses the architecture of Bonobo, and the way it can be used to write software. Also, it takes a look at the current state of Bonobo, and some of the future developments.

Introduction

Free software has been quite successful in the server space: for example, the Apache web server and the Sendmail mail server have acquired very substantial shares of their markets. However, up until now, the desktop has proven to be a lot harder to conquer: most desktop users still use proprietary systems such as Microsoft's Windows or Apple's MacOS.

Partly this is caused by reasons such as user conservatism and the need for compatibility with other users. However, it cannot be ignored that most free software was not written with the non-technical end-user in mind, and can be hard to use, no matter how powerful. Also, free software desktops do (or did) not offer some of the powerful idioms that Windows and MacOS do, such as the ability to create compound documents (i.e., a spreadsheet inside a word-processor document), or the copying/pasting of complex data through a clipboard.

Next to end-user concerns, there are also developer concerns. Writing polished software takes a lot of effort. Ideally, developers would be able to compose large parts of their programs from existing (polished, mature) building blocks (software components), instead of constant reinventing them in some suboptimal way. However, reuse in the free software world is quite limited, due to the lack of standardized interfaces, the use different languages and probably the 'Not Invented Here'-syndrome.

The GNOME-project [8] is an effort to create a full-featured free software desktop, and as part of that project, the Bonobo component framework was designed and implemented. Bonobo is an effort to help developers to create and (re)use software components. Furthermore, the Bonobo building blocks enable creating the powerful features that end-users have come to expect of their desktop.

This paper discusses the Bonobo architecture from a technical viewpoint, and gives some examples of its use. For a more practical, how-to approach, please refer to [1] and [11].

Bonobo architecture

Overview

Software components are often compared to Lego building blocks: while various pieces may differ in function, color or shape, they can all interconnect. To ensure this, all pieces (Lego building blocks or software components) need to agree on some contract.

In the context of Bonobo, one could think of some program (the container) embedding an HTML-editing component. Now, if we press the 'Save'-button in the container, the component should write it's contents to disk: clearly, there should be some way for the container to tell the component. Note that the container and component may have been developed independently, so the contract should be sufficiently general to be usable in widely different scenarios.

Bonobo and OLE2

Bonobo specifies a number of such contracts, or interfaces, as they are usually called. The Bonobo interfaces were inspired by Microsoft's OLE2 [3]. Microsoft uses OLE2 for interconnecting components in the Windows-environment. Bonobo is not an implementation of OLE2, however. Many of the design decision that went into OLE2 were re-evaluated, and all Windows-specific parts had to be adapted for Unix and the X Window System. And importantly, while OLE2 is based on Microsoft's proprietary Component Object Model (COM) [2] as the underlying communications layer, Bonobo uses the Common Object Request Broker Architecture (CORBA) [9] instead.

Bonobo and CORBA

CORBA is an industry-standard architecture for writing software components, independent of location, operating system, or programming language. For example, you can access a Perl CORBA component running on a Windows-machine in Australia just as easy as you can use it implemented as a shared library on your local machine. CORBA is far too big to discuss here in any detail, suffice it to say that CORBA components expose themselves to the world through one or more interfaces, which are written in the Interface Definition Language (IDL). IDL looks a bit like a C++-header file, but without any implementation or anything non-public, for example:
interface Foo {
        long add (in long x, in long y);
        string echo (in string msg);
};
Programming languages can access components which implement or expose this interface through the facilities provided by the language-specific CORBA-binding.

While CORBA is quite powerful, it doesn't specify anything directly useful for writing desktop-specific components. This is where Bonobo comes in. Bonobo components are really just a special kind of CORBA components, and Bonobo "contracts" are really just CORBA interfaces.

Interfaces

Bonobo components can (and most of the time, will) expose multiple interfaces. However, by definition, a CORBA component is a Bonobo component if and only if it exposes the Unknown-interface: all Bonobo interfaces derive from Unknown. The IDL for Unknown looks like this (the full IDL can be found in Bonobo_Unknown.idl in the Bonobo source distribution):
interface Unknown {
        /* increment the reference count */
        void ref ();

        /* decrement the reference count */
        void unref ();

        /* return a CORBA object with interface repoid, 
         * or CORBA_NIL */
        Unknown queryInterface (in string repoid);
};
Unknown does two things: it maintains a reference count mechanism (ref and unref), and, more interesting, it exposes the queryInterface-method, which enables us to switch to other interfaces this component exposes. Together with the fact that all Bonobo interfaces derive from Unknown, this means that if we have any interface to some Bonobo component, we can reach its other interfaces through queryInterface, as shown in figure [*].

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

Note that MyComponent is never visible to the client: it is only accessible through the interfaces. And using queryInterface, you can "hop" from one interface to the other (in pseudo-code):

If1 my_if1;
If2 my_if2;
If3 my_if3;

my_if1 = /* get a reference to the if1 interface */

my_if2 = my_if1.queryInterface ("if2")
my_if3 = my_if2.queryInterface ("if3")

my_if2.doSomeIf2Method (2.71828)
my_if3.doSomeIf3Method ("foo")

The ORBit/GTK+ implementation

The Bonobo CORBA-interfaces are quite versatile and powerful; on the other hand, they do require some deep understanding of CORBA. Especially the CORBA's C language binding is not for the faint-hearted. Furthermore, the same interfaces must be implemented over and over again. Clearly, this would not benefit developer enthusiasm for Bonobo.

Fortunately, Bonobo comes with a default implementation for all Bonobo interfaces, and with a support library; in many cases, there's hardly any CORBA showing through. Still, it's there when you need it.

Bonobo's default implementation is based on the GTK+-toolkit [14] and the ORBit CORBA implementation.

Note that the default implementation does not in any way tie the core Bonobo to GTK+ or even the X Window System: it would be perfectly possible to write an alternative implementation for Java, as has been done [6], or for KDE/Qt, the other Unix/Linux desktop. For the GNOME2-platform, Bonobo has been split in GUI and a non-GUI parts, enabling alternative implementations to reuse parts of the existing Bonobo.

OAF: Finding your object

There is one important piece of the Bonobo-architecture left, that we have not discussed yet: activation. To use a Bonobo component, one first needs to activate it, i.e., start the component. To ease the activation of CORBA-components (not just Bonobo components), the Object Activation Framework (OAF)[10] was created.

Components register themselves in OAF by installing an XML description file (.oaf) with their location information (i.e., where to find the executable or shared library), and arbitrary other information. Clients can query OAF for some object.

For example, suppose we want to activate some component which exposes both the Bonobo Control and the Nautilus ContentView interfaces:

CORBA_Object obj = 
        oaf_activate ("repo_ids.has_all(['IDL:Bonobo/Control:1.0',"
                      "'IDL:Nautilus/ContentView:1.0']");
It's also possible to do command-line queries, using oaf-run-query:
% oaf-run-query "bonobo:supported_mime_types.has('text/html')"
number of results: 3
OAFIID:GNOME_GtkHTML_EBrowser
OAFIID:GNOME_GtkHTML_Editor
OAFIID:nautilus_mozilla_content_view:1ee70717-57bf-4079-aae5-922abdd576b1
Here, we use the standardized bonobo:supported_mime_types-attribute, for querying Bonobo components that support some MIME-type.

OAF is neither dependent on Bonobo nor on the X Window System. The OAF-architecture allows for activating remote components (components running on other machines) as well. However, the current OAF-implementation does not support that. For the GNOME2 platform, OAF has been renamed bonobo-activation, but without adding any Bonobo-dependency.

Using Bonobo

Now, we will discuss the use of Bonobo technology. For a more practical example of writing actual Bonobo code, please refer to the tutorial and example code that's available online [1,11].

Bonobo Controls

One important application area for Bonobo are Bonobo Controls: Bonobo controls are widgets (GUI elements) which can be used like conventional widgets, but use Bonobo to communicate with containers instead of the 'normal' mechanisms. They provide a number of advantages over normal widgets:

The interface definition for Bonobo controls can be found in Bonobo_Control.idl in the Bonobo source distribution, and is a bit too long to reproduce here. Fortunately, the Bonobo GTK+ implementation wraps the interface quite nicely, and often we can avoid using the interfaces directly.

Creating a Bonobo-control is very easy. Suppose we have created a GTK+-widget of type MyWidget, and want to create a Bonobo-controls which offers its features to the outside world. Now, what we will usually do is write a object factory, ie. an object that produces other objects. In this case, it produces MyWidget-Bonobo-controls. In the C language, this could look like:

BonoboControl* control;
GtkWidget*     my_widget =  my_gtk_widget_new (); 
        
gtk_widget_show (my_widget);

control = bonobo_control_new (my_widget);
return control;
Note the absence of any CORBA-related code; the Bonobo/GTK+ implementation takes care of that. An easy way for component and container to communicate is using property bags. A property bag is a list of typed values, exposed by the component, and accessible by both component and container as shown in figure [*]. Callback functions can be registered for changes to values in the property bag.

Figure: A component with a property bag
\begin{figure}\centerline{%%
\epsfxsize=80mm
\epsffile{property_bag.eps}} {}
\end{figure}

Using property bags often avoids the necessity to add any custom methods to the control, thus avoiding the need to write a custom component implementing the Control-interface. For more information, see [1].

If you do need to write you own custom Bonobo components however, Bonobo provides the BonoboObject wrapper and some useful macros to make this is easy as possible. In that case, knowledge of the CORBA C-binding is required though.

Before we can use MyWidget in a container-application, we must register it with OAF by writing an .oaf-file. Check [10] for details.

Using MyWidget from a Bonobo container is also quite easy, at least if you are familiar with GTK+-programming:

   GtkWidget *bonobo_win, *control;
   BonoboUIContainer *uic;

   bonobo_win = bonobo_window_new ("test", "a small test");
   
    /* connect a ui container to the application */
   uic = bonobo_ui_container_new ();
   bonobo_ui_container_set_win (uic, BONOBO_WINDOW(bonobo_win));
   
   /* get a widget, containing the control */
   control = bonobo_widget_new_control (MYWIDGET_OAFIID, 
                                        BONOBO_OBJREF (uic));

   /* lights, camera, action */
   gtk_widget_show_all (GTK_WIDGET(bonobo_win));
Again, we won't discuss any details here, please refer to [1].

Compound documents

Nowadays, applications such as word processors not only process words, but also have to deal with pictures, graphs, spreadsheets, videos, sounds, ... It would be very hard for an application to handle all these media types by itself, not to mention all the media types that weren't even conceived when the application was written. Still, end-users want such heterogenous documents.

The solution to this problem is the use of compound documents. A compound document is formed by a hierarchy of documents (media types) which can be compound documents themselves, quite similar to a directory tree. For compound documents the "files" are called streams and the "directories" are called storages. Figure [*] gives a schematic view of an example compound document.

Figure: A compound document
\begin{figure}\centerline{%%
\epsfxsize=50mm
\epsffile{compound_doc.eps}} {}
\end{figure}

The trick in supporting arbitrary media types in a document, is to expose all these media types as Bonobo components, and let them deal with all their complexity themselves. Now, as long as the application knows how to handle components with the Embeddable interface (and probably a couple of others), it can handle them all, even if they weren't even available when the application was written.

In a compound document, the various subparts are responsible for their own display, and read/write operations, and subparts recursively tell their subparts to do the same. For read/write operations (persistence), Bonobo offers compound files which mirror the document hierarchy, and can be thought of as 'file system in a file'. Thus, compound documents are stored in compound files.

Compound documents expose the Embeddable-interface (and possibly the Control-interface), and probably the Stream/Storage-interfaces for file operations.

An example of an application supporting compound documents is the Gnumeric spreadsheet application, in which you embed various component types. This support is still in the experimental stages though.

Monikers

A special kind of Bonobo objects are monikers [3,5]. Monikers are pointer components, that is, components pointing to other components. While at first, this may not sound very interesting, monikers allow for some very powerful applications. Monikers expose the Moniker-interface (which can be found in Bonobo_Moniker.idl).

Monikers can be constructed from some stringified representation, and a Bonobo component can then be resolved from this moniker:

 
Bonobo::Unknown control;
moniker = bonobo_moniker_client_new_from_name 
                            (stringified_moniker);
control = moniker->resolve (interface);
For example, if we want to access a remote web document through the Control interface, and also through the Stream interface, we can do:
moniker = bonobo_moniker_client_new_from_name 
                          ("http://www.gnome.org");

control = moniker->resolve ("IDL:Bonobo/Control:1.0");
stream  = moniker->resolve ("IDL:Bonobo/Stream:1.0");

A very interesting property of monikers is the fact that they can be chained: each component of the chain is handled by a separate moniker, and each of these monikers returns a component to the moniker at its left side, for example:

file:myfile.gnumeric!SalesReport!totals
This stringified moniker resolves to the totals-cell in the SalesReport-workbook in the myfile.gnumeric spreadsheet file. The file, workbook and cell part of the moniker are each handled by separated monikers.

An increasing number of GNOME services are made accessible through monikers. Examples include:

Bonobo language bindings

While libraries, system-related and complex application software are still best written in C/C++, for a lot of other applications, languages such as Perl and Python are more suitable. The GTK+-toolkit underlying the GNOME-desktop has mature bindings for both Perl and Python, and so does ORBit. So it's only natural to use Bonobo with these languages as well.

Writing Bonobo controls and containers with, say, Python [4], is very easy. For example, let's write a control factory:

def control_factory (factory, object_id):
        # Create a GtkWidget and show it
        my_widget = gtk.GtkLabel ('Sample Control')
        my_widget.show()
            
        # Create a control from the widget and return it
        control = bonobo.BonoboControl (my_widget)
        return control
By using Bonobo, the painful and slow process of writing Perl/Python/Guile/... wrappers for libraries is not necessary anymore: when the language supports Bonobo, it can simply use Bonobo components written in other languages and vice versa.

Bonobo: present and future

The success of a component model like Bonobo is very much determined by the availability of components: no matter how powerful the component model, if there are no components available for reuse, it won't succeed. Bonobo has made a lot of progress in this area.

Bonobo Today

Already, a lot of programs are using Bonobo in some way.

The future

Bonobo has sparked a lot of very interesting developments. Let's take a look at some of the interesting developments that are currently underway, and will shape Bonobo's future:

Conclusion

Bonobo brings component technology to the Unix desktop, and has proven itself in some huge application such as Evolution and Nautilus. Bonobo is based on industry-standard CORBA, which allows for easy interaction with existing systems. More and more important pieces of free software will be accessible through Bonobo.

Bonobo certainly isn't perfect, and some problems remain:

Work is needed to fix these problems. Nevertheless, Bonobo looks very promising. In the future, more and more of the GNOME desktop will be based on Bonobo. This will bring benefits to both users and developers: users will get a free software with all bells and whistles, and developers get powerful tools which allow for more reuse and better software.

Bibliography

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

2
D. Box.
Essential COM.
Addison-Wesley, 1998.

3
K. Brockschmidt.
Inside OLE.
Microsoft Press, 1995.

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

5
Miguel De Icaza.
Monikers in the GNOME-system.
available online, http://primates.ximian.com/~miguel/monikers.html, 2000.

6
Gergo Erdi.
Monkeybeans.
available online, http://cvs.gnome.org/lxr/source/monkeybeans, 2000.

7
Gnome DB Project.
GnomeDB Homepage.
online, http://www.gnome-db.org, 2001.

8
The Gnome Project.
Gnome Hompage.
online, http://www.gnome.org, 2001.

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

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

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

12
Uche Ogbuji.
Bridging XPCOM/Bonobo - techniques.
available online, http://www-105.ibm.com/developerworks/education.nsf/linux-onlinecourse-bytitle/85AACA6C27A8EF0886256A42006109E0?OpenDocument, 2001.

13
Open Office Project.
Open Office Homepage.
online, http://www.openoffice.org, 2001.

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

About this document ...

Bonobo Components: Architecture and Application

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 nluug-bonobo.tex

The translation was initiated by Dirk-Jan C. Binnema on 2001-11-28


Dirk-Jan C. Binnema
2001-11-28