Dirk-Jan C. Binnema,
http://www.djcbsoftware.nl
dirk-jan@djcbsoftware.nl
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
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].
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.
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.
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.
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 GtkObject
s in Gtk+/Glib/Gnome 1.x, but will be
GObject
's in Gnome2. However, we're discussing only the former
here.
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): 1So what are we doing here? Well:
Bonobo::Unknown
, so that's what we enter;
bonobo-sample-good-mood.c
or just good-mood.c
? If you
want the latter, say 'n' here;
Finally, gwizard
can automatically insert the GPL or
LGPL license header if you want.
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.
GoodMood
-implementation:
good-mood.h
: Add the include file that CORBA created from the
Bonobo_Sample_MoodyComponent.idl
;
good-mood.[ch]
: Add the declarations and
implementations for the say_hello
function in the
GoodMood
interface;
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 IDLC-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.
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!
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): 1This 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.
.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.
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
/* * 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]...
% ./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.
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.
Anyway, apart from fixing bugs, there are some features I may or may not add.
Makefile
/Makefile.am
;
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