In this article, the term "Bonobo" is used to specify both the Bonobo infrastructure, and -- often the underlying CORBA transport. This is to reduce confusion where making a distinction between the two is not helpful.
Similarly, I do not refer to the various language bindings for Bonobo (particularly good are Perl and Python -- see Resources), but rather stick with the native C bindings.
The first thing to understand about the way Bonobo interfaces behave is that it is not exactly like the way an Object Oriented (OO) language behaves with a class. Whilst an OO language's class type data, (by type data we mean the abstract information describing the class or interface) contains information about the class's methods. It also (typically) contains data and scope type information. What this means is that in an OO language there is often a protected-private-public distinction for methods. In addition, there is often data layout information -- that is, a "Point" object might have a data member of name "foo" of type "double." This implementation detail can thus be confused with the interface that the object exports.
In CORBA, the only things that are exposed to the remote world are methods (even attributes are implemented as get/set methods). Thus an interface is precisely that -- a pure method-based WYSIWYG interface. Hence whilst a Bonobo interface can inherit from another Bonobo interface, there is no reason the implementation needs to.
Secondly, Bonobo does not use multiple inheritance (MI). Instead, it uses single inheritance in combination with interface aggregation in the same way as COM, UNO and XPCOM.
Most interfaces inherit solely from the Bonobo::Unknown interface, which provides a nexus for retrieving (and querying) other interfaces. Remember that an interface contains no data: It simply provides a way to talk to the actual data on the object that lives behind it. So it should be reasonable that several different interfaces could be used to access that data. Thus a Control, for example, might have a property-access interface and a visual-embedding interface.
To get an interface from an object reference, one would use the queryInterface (QI) method in Interface Definition Language (IDL):
The queryInterface method
module Bonobo {
interface Unknown {
...
Unknown queryInterface (Unknown obj, in string repoid);
};
};
|
Whilst this is an important concept, in many cases the bindings will return interfaces of the type you expect, thus it is frequently not neccessary to use QI.
Bonobo splits into several parts. In the client section, the parts that we are interested in are the client sugarwrappers, and using the CORBA interfaces directly ourselves.
The above IDL queryInterface method, when compiled to
a C stub, would have a signature thus:
The queryInterface method compiled
Bonobo_Unknown
Bonobo_Unknown_queryInterface (
Bonobo_Unknown obj,
const CORBA_char *repoid,
CORBA_Environment *ev);
|
and be used thus:
The queryInterface method in use
CORBA_Environment ev;
CORBA_exception_init (&ev)
control = Bonobo_Unknown_queryInterface (
object, "IDL:Bonobo/Control:1.0", &ev);
|
This looks intuitive enough: We pass a C string, and we get an Unknown back. But there are several things worth noticing:
- The namespace and interface name are mangled into the method name using underscores, ie. since the interface is in the Bonobo::
namespace it has a
Bonobo_prefix, and since the method is in the Unknown interface, there is then theUnknown. This distinction is an important visual indication as to whether a method is a CORBA method (it will have a mixed case signature likeBonobo_Control_activate) as opposed to a wrapper function ( which will be all lower case:bonobo_control_new). - Remote handles have an associated interface, for instance,
Bonobo_Unknowndenotes a CORBA pointer to a remote object implementing the Unknown interface. - Each time a CORBA method is invoked it is possible that an exception will be flagged in 'ev'. This can be easily acertained by using
the if (BONOBO_EX (&ev)) idiom, the environment needs to be initialized before the CORBA call by the
CORBA_exception_init.
To ease the hassles of using the CORBA methods in a fine-grained fashion and being concerned about exception environments, there are client wrappers that make use of Bonobo -- and its integration with existing Gtk+ programs -- far easier.
Bonobo provides a Control interface that allows the construction of rich embedded GUI widgets. It also provides a PropertyBag interface to allow any properties to be set on the widget, and often an EventSource interface to allow connection to any signals the control may emit. Thus Bonobo provides capabilities similar to those of an ActiveX control or a JavaBean.
Thus to create a sample calculator widget and insert it into (perhaps an educational application to teach math) you might do:
A calculator widget
GtkWidget *control;
control = bonobo_widget_new_control (
"OAFIID:Bonobo_Sample_Calculator", NULL);
gtk_widget_show (control);
|
This widget would then behave just as any other widget would, and would allow insertion into any Gtk container in the normal way.
To set the displayed value of the calculator widget, we might want to set the "value" property. There are several ways to do this, first the simple, but non-type-safe fashion:
Setting the "value" property
CORBA_double i;
bonobo_widget_get_property (control, "value", &i, NULL);
i+= 0.37;
bonobo_widget_set_property (control, "value", i, NULL);
|
In this example, we grab the value, add 0.37 to it and set it back again. The more type-safe -- but longer -- method is perhaps more
instructive, after obtaining the BonoboControlFrame from the widget with bonobo_widget_get_control_frame we would do:
Setting the "value" property correctly
Bonobo_PropertyBag pb;
CORBA_Environment ev;
/* allocate the exception environment */
CORBA_exception_init (&ev);
pb = bonobo_control_frame_get_control_property_bag (
control_frame, &ev);
/* check for an exception */
if (BONOBO_EX (&ev))
g_warning ("Error getting property bag from control");
else {
/* use the property bag client helper function to set a
double value, this constructs an 'Any' from the double, and
calls Bonobo_Property_setValue on the remote property bag */
bonobo_property_bag_client_set_value_gdouble (
pb, "value", 0.12345, NULL);
/* release the Bonobo reference to the property bag
that was retrieved from the control_frame */
bonobo_object_release_unref (pb, NULL);
/* free the exception environment */
CORBA_exception_free (&ev);
|
The property and control interfaces are slightly unusual in that one interfaces with the Gtk+ widget system in an intimate way, and the
other manipulates CORBA_anys which are slightly ugly in C. Thus, having the bonobo_widget_ and bonobo_property_bag_client wrappers makes life far easier for
the C programmer.
Most new interfaces, however, will be used directly. For example, to write data to a stream, one would need to use the method (IDL):
A CORBA interface to write data to a stream
void write (in iobuf buffer)
raises (NoPermission, IOError);
which would be used thus:
CORBA_Environment ev;
Bonobo_Stream_iobuf buf;
buf._buffer = <a pointer to my byte data>;
buf._length = <length of the data in bytes>;
CORBA_exception_init (&ev);
Bonobo_Stream_write (stream, &buf, &ev);
if (BONOBO_EX (&ev))
g_warning ("Fatal stream write error '%s'",
bonobo_exception_get_text (&ev));
CORBA_exception_free (&ev);
|
Thus the CORBA interface can be used directly, although there is also a bonobo_stream_client_write helper method too. Notice here also the bonobo_exception_get_text method is used to return a translated, user-readable description of the exception that occurred (although this is leaked above for clarity).
The last important thing to take care of in client code is reference counting. Bonobo's lifecycle management is that of reference counting. This means that if you want to ensure that an object is not going to die a sudden, shocking death, you keep a reference to it. The object keeps track of how many references have been taken to it and, when this count reaches zero, it destroys the object.
Thus to ensure that objects are correctly terminated when they are no longer needed, and to ensure that they are not terminated prematurely, it is important to reference count correctly.
Client code should always use the bonobo_object_dup_ref and bonobo_object_release_unref methods. These tolerate NIL references silently, and allow exceptions to be ignored.
By convention, when Bonobo methods return a reference, you need to release the reference when you are finished with it (as above).
Finally, the bonobo_widget sugar wrapper also wraps the moniker infrastructure, this means that a much richer object namespace is available to the programmer. The following example:
Sugar wrapping an image reference
control = bonobo_widget_new_control (
"file:/demo/a.png", NULL);
|
will produce a control (perhaps implemented by Eye Of Gnome) that will render the image.
Below are some key points to take from this second installment of a three-part introduction to Bonobo:
- It is easy to use Bonobo components.
- Use method capitalization to distinguish between direct CORBA invocations, and local helper wrappers.
- Treat references carefully.
- See the samples in bonobo/samples/controls/ for some working example code.
- Monikers provide a powerful, abstract object namespace, see bonobo/doc/Monikers for more information.
In the next article, we'll discuss the process of creating your own component and exposing it to the world.
- Bonobo & ORBit (Part 1 of this series)
- Implementing a new component (Part 3 of this
series)
- You can download Bonobo as it is
published under the GNU GPL (for links to GNU see the Resources in Part 1 of this series).
- ORBit: http://www.labs.redhat.com/orbit/.
- The Perl and Python language bindings for Bonobo are quite good. For more on ORBit-perl see: http://people.redhat.com/otaylor/corba/orbit.html.
- For ORBit-Python see: ihttp://sourceforge.net/projects/orbit-python/.
- See the samples in bonobo/samples/controls/ for some working example code. You can find this code in the Bonobo CVS repository.
- Monikers provide a powerful, abstract object namespace, see bonobo/doc/Monikers in the Bonobo CVS for more information.
- Full Bonobo API Reference Manual http://developer.gnome.org/doc/API/bonobo/book1.html.
- The full GNOME 1.0 API documentation is here: http://developer.gnome.org/doc/API/.
- Bonobo is named for the Bonobo monkey, the last Great Ape -- and an endangered species.
- Michael Meeks, the author, is a software engineer at Ximian, Inc.
- If you liked this article, you may also be interested in the developerWorks Linux zone.
- And the developerWorks Open source zone.
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.





