Skip to main content

Introduction to Bonobo: Implementing a new component

It's easier than you might think

Michael Meeks (michael@ximian.com), Component software engineer, Ximian Inc.

Michael is a Christian and enthusiastic believer in Free software. He very much enjoys working for Ximian Inc. where he gets paid to develop various free GNOME components, particularly ORBit2, Bonobo and GNOME 2 infrastructure. Prior to this he worked for Quantel gaining expertise in real time AV editing and playback achieved with high performance focused hardware/software solutions. You can reach him at michael@ximian.com.

Summary:  In this article we'll go through the basics of exposing an application as a Bonobo control and consider the surrounding issues. A familiarity with the material covered in the first two articles in this series is assumed.

Date:  01 Aug 2001
Level:  Introductory
Activity:  437 views

Oaf and activation

When someone wishes to activate a Bonobo component, they go and query the Object Activation Framework (Oaf). Consequently the first thing we must do is make sure that Oaf knows enough about the control we are about to implement.

To do this, we need to install a small XML file in a path where Oaf expects to find it. This might be in /usr/share/oaf, depending on your prefix, and GNOME_PATH. A cut-down Oaf file might look like this:



A sample Oaf file


<oaf_info>

  <oaf_server iid="OAFIID:Bonobo_Sample_ControlFactory" type="exe"
     location="bonobo-sample-controls">
        ...
  </oaf_server>

  <oaf_server iid="OAFIID:Bonobo_Sample_Clock" type="factory"
     location="OAFIID:Bonobo_Sample_ControlFactory">

    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Bonobo/Unknown:1.0"/>
      <item value="IDL:Bonobo/Control:1.0"/>
    </oaf_attribute>

    <oaf_attribute name="name" type="string" value="Clock control"/>
    <oaf_attribute name="description" type="string" value="A sample 
Bonobo control which displays a clock."/>

  </oaf_server>

</oaf_info>


Bonobo components are created by factories; consequently we have two server definitions. The factory, which is of type "exe" and located in the executable bonobo-sample-controls, is called Bonobo_Sample_ControlFactory. The object itself, named Bonobo_Sample_Clock, is created by a factory, of name Bonobo_Sample_ControlFactory.

In addition, it's important to notice that the interfaces supported by the component are declared against the control. In this case, only two interfaces are declared: Bonobo/Unknown and Bonobo/Control. And finally, the component is described to allow GUI builders such as Glade to display information about the control.

This example is taken from bonobo/samples/controls.


Interface definition

In many situations, it should not be necessary to implement new interfaces. Instead, generic Bonobo interfaces such as the PropertyBag and EventSource/Listener interfaces should be used. However, in some situations these will not suffice, and a new interface must be implemented.

First, the interface must be declared in Interface Definition Language (IDL), (see bonobo/idl for some examples). For instance, GNOME_Foo.idl.


Declaring the interface


#include <Bonobo.idl>

module GNOME {
  interface Foo : Bonobo::Unknown {
    double getTimeOfEvent (in string eventName, in long idx);
    long   addEventByName (in string eventName);
  };
};


Note that the Java naming convention is used in method naming. Also note that namespace allocation and naming is important, see bonobo/doc/FAQ.

Having implemented this we must compile it:


Compiling the interface


        $ orbit-idl `gnome-config --cflags idl` GNOME_Foo.idl


This will build GNOME_Foo-stubs.c, GNOME_Foo-skels.c, and GNOME_Foo-common.c, which provide stub (client access code), skels (server implementation code) and common code for the CORBA transport. These must be linked into your application.


Creating a simple control

Since the creation of controls is an operation that is very commonly required, Bonobo has some helpful implementation details that make it possible to do this easily, in fact, in a single line.

Assuming your control is already a GtkWidget, one can simply make a control out of it thus (showing the full control factory function):


Making your GtkWidget into a control


BonoboObject *
create_foo (BonoboGenericFactory *Factory, void *closure)
{
        BonoboControl *control;
        GtkWidget     *my_widget;

        my_widget = create_my_widget ();
        gtk_widget_show (my_widget);

        control = bonobo_control_new (my_widget);

        return BONOBO_OBJECT (control);
}


It is important to show the widget before returning it as a control. Many people use gtk_widget_show_all to reveal their entire widget hierarchy. This, however, is bad practice as it will not propagate to a sub-control, since a control might (legitimately) want to conceal parts of its contents.

Thus it is extremely easy to create and expose a GtkWidget as a control. A great example of setting up a simple control, and adding some Bonobo properties to it, can be found in bonobo/samples/controls/bonobo-clock-control.


Implementing an interface

Now that we have declared this abstract interface, we want to implement the interface inside our clock control. To do this, we use BonoboXObject, which will seem familiar to anyone confident with the Gtk+ object system upon which it is based. To people used to languages like C++ with built-in OO support, this will seem pedestrian; in such languages there is no need for this boilerplate code.


Implementing the interface with BonoboXObject


(foo-impl.h):

#include "GNOME_Foo.h"

#define FOO_TYPE        (foo_get_type ())
#define FOO(o)          (GTK_CHECK_CAST ((o), FOO_TYPE, Foo))

typedef struct {
        BonoboXObject parent;

        GSList       *events;
} Foo;

typedef struct {
        BonoboXObjectClass parent_class;

        POA_GNOME_Foo__epv epv;
} FooClass;

GtkType foo_get_type (void);


Note that the Gtk+ object system implements single "structure" inheritance by having the first element of the derived structure be that of the parent type. Secondly note that the epv element is constructed by prepending POA_ and appending __epv to the full name of the interface, for example GNOME_Foo.

The epv is the "Entry Point Vector," this is used by the ORB to locate the implementations of the methods. The epv contains a set of function pointers to our implementation, which we will fill in later.


Implementing the interface (continued)



(foo-impl.c):

#include <bonobo.h>
#include "foo-impl.h"

static CORBA_double
impl_getTimeOfEvent (PortableServer_Servant servant,
                     const CORBA_char      *eventName,
                     const CORBA_long       idx,
                     CORBA_Environment     *ev)
{
        Foo *foo = FOO (bonobo_object_from_servant (servant));

        g_warning ("Get Time of Event '%s':0x%x", eventName, idx);

        return 2.5;
}

static CORBA_long
impl_addEventByName (PortableServer_Servant servant,
                     const CORBA_char      *eventName,
                     CORBA_Environment     *ev)
{
        Foo *foo = FOO (bonobo_object_from_servant (servant));

        g_warning ("Add EventByName '%s'", eventName);

        return 1;
}

static void
foo_class_init (FooClass *klass)
{
        POA_GNOME_Foo__epv *epv = &klass->epv; 

        epv->getTimeOfEvent = impl_getTimeOfEvent;
        epv->addEventByName = impl_addEventByName;
}

GtkType
foo_get_type (void)
{
        static GtkType type = 0;

        if (!type) {
                GtkTypeInfo info = {
                        "Foo",
                        sizeof (Foo),
                        sizeof (FooClass),
                        (GtkClassInitFunc) foo_class_init,
                        NULL, NULL, NULL, NULL
                };

                type = bonobo_x_type_unique (
                        BONOBO_X_OBJECT_TYPE,
                        POA_GNOME_Foo__init, NULL,
                        GTK_STRUCT_OFFSET (FooClass, epv),
                        &info);
        }

        return type;
}


This sets up all the necessary interface class information, with some dummy method implementations.

Then -- if only this interface was required -- one might register a boilerplate factory function, thus:


Registering your component


BonoboObject *
create_foo (BonoboGenericFactory *Factory, void *closure)
{
        return gtk_type_new (foo_get_type ());
}

BONOBO_OAF_FACTORY ("OAFIID:GNOME_Foo_Factory", "a foo factory",
                    "1.0", create_foo, NULL);

        To compile the complete server use:

        $ gcc `gnome-config --cflags bonobo` -I. -Wall \
              `gnome-config --libs bonobo` \
              foo-impl.c GNOME_Foo-skels.c GNOME_Foo-common.c


After registering the GNOME_Foo_Factory with Oaf by adapting the first section, one could write a small client:


A very simple client


        GNOME_Foo corba_foo = bonobo_get_object (
                  "OAFIID:GNOME_Foo", "GNOME/Foo", NULL);

        GNOME_Foo_addEventByName (corba_foo, "MyEventName", &ev);


See also Basic Bonobo use for more information on the client code.

It's important to note that while on the server side it is possible to get a "Foo *" pointer to the implementation, on the client side you can only get a GNOME_Foo handle to the remote CORBA object.

bonobo/samples/bonobo-class is a good place to find a complete example of implementing an interface.


Constructing the aggregate object

In many cases, a component will support multiple interfaces. For example we may wish to add the "GNOME/Foo" functionality described above to the existing Bonobo_Sample_Clock component. To do this, we must aggregate the interfaces together: This allows either interface to be accessed from the remote client by use of the Query Interface (QI) mechanism (see also Basic Bonobo use).

So, for example:


Aggregating interfaces


        BonoboObject *clock;
        BonoboObject *foo;

        clock = bonobo_clock_control_new ();
        foo   = create_foo ();

        bonobo_object_add_interface (clock, foo);


The crucial invocation here is bonobo_object_add_interface. This fuses the two interfaces' objects together so that they represent a single object, allowing a remote QI to obtain a clock interface from a foo interface and vv.


Stream handling controls

One of the most useful roles of Bonobo controls is handling data of a certain MIME type. To do this, the control must implement either the Persist/Stream interface, or the Persist/File interface. Whilst retrofitting an existing application with the PersistFile interface is extremely easy, it is in many ways preferable to use the PersistStream interface.

In addition to implementing the Bonobo/PersistStream interface, it is necessary to register two things. First, register that the component implements the interface, and second, register the MIME types the component can handle. To do this, we would update the interfaces supported by the component in the Oaf XML file thus:


Registering the component's interfaces


    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Bonobo/Unknown:1.0"/>
      <item value="IDL:Bonobo/Control:1.0"/>
      <item value="IDL:Bonobo/PersistStream:1.0"/>
    </oaf_attribute>


Then we would add a section describing the supported MIME types thus:


Registering the component's supported MIME types


    <oaf_attribute name="bonobo:supported_mime_types" type="stringv">
      <item value="applicaton/x-msword"/>
      <item value="text/plain"/>
    </oaf_attribute>


This information enables the system to launch the new control in many situations. For instance, on resolving the moniker:

file:/demo/a.doc against 'Bonobo/Control'

the system would recognize that this component implements the necessary functionality. The system would then activate it and feed it the stream to allow display of the document.


Conclusions

And finally, here are some key points to take from this final installment of a three-part introduction to Bonobo:

  • Implementing Bonobo controls is only marginally more complex than using them.
  • Exposing your application as a Bonobo control allows its use across the system in many (sometimes unexpected) contexts.
  • Implementing your own interfaces is required infrequently.

Resources

About the author


Michael is a Christian and enthusiastic believer in Free software. He very much enjoys working for Ximian Inc. where he gets paid to develop various free GNOME components, particularly ORBit2, Bonobo and GNOME 2 infrastructure. Prior to this he worked for Quantel gaining expertise in real time AV editing and playback achieved with high performance focused hardware/software solutions. You can reach him at michael@ximian.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and Web services
ArticleID=86671
ArticleTitle=Introduction to Bonobo: Implementing a new component
publish-date=08012001
author1-email=michael@ximian.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers