The Ivy C library guideFrancois-RégisColinfcolin@cena.frStéphaneChattychatty@cena.frYannickJestinjestin@cena.frDecember 11, 20021998-2006Centre d'Études de la Navigation Aérienne
This document is a programmer's guide that describes how to use the Ivy C
library to connect applications to an Ivy bus. This guide describes version 3.8
of the library.
Foreword
This document was written in SGML according to the DocBook DTD, so as to be able to
generate PDF and html output. However, the authors have not yet mastered the
intricacies of SGML, the DocBook DTD, the DocBook Stylesheets and the related
tools, which have achieved the glorious feat of being far more complex than
LaTeX and Microsoft Word combined together. This explains why this document, in addition
to being incomplete, is so ugly. We'll try and improve it.
What is Ivy?
Ivy is a software bus designed at CENA (France). A software bus is a system
that allows software applications to exchange information with the illusion of
broadcasting that information, selection being performed by the receiving
applications. Using a software bus is very similar to dealing with events in a
graphical toolkit: on one side, messages are emitted without caring about who
will handle them, and on the other side, one decide to handle the messages that
have a certain type or follow a certain pattern. Software buses are mainly aimed
at facilitating the rapid development of new agents, and at managing a dynamic
collection of agents on the bus: agents show up, emit messages and receive some,
then leave the bus without blocking the others.
Ivy is implemented as a collection of libaries for several languages and
platforms. If you want to read more about the principles Ivy before reading this guide of the C
library, please refer to The Ivy sofware bus: a white
paper. If you want more details about the internals of Ivy, have a
look at The Ivy architecture and protocol. And finally,
if you are more interested in other languages, refer to other guides such as
The Ivy Java library guide. All those documents should be
available from the Ivy Web site at http://www.tls.cena.fr/products/ivy/.
The Ivy C libraryWhat is it?
The Ivy C library (aka Ivy-C or ivy-c) is a C library that allows you to connect
applications to an Ivy bus. You can use it to write applications in C or any
other language that supports C extensions. You can also use it to integrate an
application that already has a main loop (such as a GUI application) within an
Ivy bus. This guide is here to help you do that.
The Ivy C library is known to compile and work in Win32, Linux, and MacOSX
environments. It should be easy to use on most Posix environments.
The Ivy C library was originally developed by François-Régis Colin at CENA. It
is maintained by a group at CENA (Toulouse, France)
Getting and installing the Ivy C library
You can get the latest versions of the Ivy C library from CENA
(http://www.tls.cena.fr/products/ivy/). Depending on whether you
use a supported binary distribution, you can retrieve binary RPM or
Debian packages for Linux. Do not forget to get the development package as well as the
run-time package, or retrieve the source files and compile them.
Your first Ivy application
We are going to write a "Hello world translater" for an Ivy bus. The application
will subscribe to all messages starting with "Hello", and re-emit them after
translating "Hello" into "Bonjour". In addition, the application will quit when
it receives any message containing exactly "Bye".
The code
Here is the code of "hellotranslater.c":
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <Ivy/ivy.h>
#include <Ivy/ivyloop.h>
/* callback associated to "Hello" messages */
void HelloCallback (IvyClientPtr app, void *data, int argc, char **argv)
{
const char* arg = (argc < 1) ? "" : argv[0];
IvySendMsg ("Bonjour%s", arg);
}
/* callback associated to "Bye" messages */
void ByeCallback (IvyClientPtr app, void *data, int argc, char **argv)
{
IvyStop ();
}
main (int argc, char**argv)
{
/* handling of -b option */
const char* bus = 0;
char c;
while (c = getopt (argc, argv, "b:") != EOF) {
switch (c) {
case 'b':
bus = optarg;
break;
}
}
/* handling of environment variable */
if (!bus)
bus = getenv ("IVYBUS");
/* initializations */
IvyInit ("IvyTranslater", "Hello le monde", 0, 0, 0, 0);
IvyStart (bus);
/* binding of HelloCallback to messages starting with 'Hello' */
IvyBindMsg (HelloCallback, 0, "^Hello(.*)");
/* binding of ByeCallback to 'Bye' */
IvyBindMsg (ByeCallback, 0, "^Bye$");
/* main loop */
IvyMainLoop (0,0);
}
Compiling it
On a Unix computer, you should be able to compile the application with the
following command:
$ cc -o ivytranslater ivytranslater.c -livy
$
Testing
We are going to test our application with ivyprobe. In a
terminal window, launch ivytranslater.
$ ivytranslater
Then in another
terminal window, launch ivyprobe '(.*)'. You are then ready
to start. Type "Hello Paul", and you should get "Bonjour Paul". Type "Bye", and
your application should quit:
$ ivyprobe '(.*)'
IvyTranslater connected from localhost
IvyTranslater subscribes to 'Hello (.*)'
IvyTranslater subscribes to 'Bye'
Hello Paul
IvyTranslater sent 'Bonjour Paul'
Bye
IvyTranslater disconnected from localhost
<Ctrl-D>
$
Basic functionsInitialization and main loop
Initializing an Ivy agent with the Ivy C library is a two step process. First of
all, you should initialize the library by calling function IvyInit. Once
the library is initialized you can create timers and add subscriptions, but your
agent is still not connected to any bus. In order to connect, you should call
function IvyStart. In theory, initialization is then over. However in
practice, as for any asynchronous communication or interaction library, nothing
happens until your application has reached the main loop.
The Ivy C library provides its own main loop: IvyMainLoop. You should use
it unless you already use a toolkit that provides its own main loop and you want
to use that one. If it is the case, please refer to section XX. Otherwise, just
call IvyMainLoop. From within the main loop, you can call IvyStop to
exit the loop.
Here are more details on those functions:
void IvyInit (const char* agentname,
const char* ready_msg,
IvyApplicationCallback app_cb,
void *app_data,
IvyDieCallback die_cb,
void *die_data);
initializes the library.
agentname is the name of your application on the Ivy
bus. It will be transmitted to other applications and possibly used by them, as
does ivyprobe.
ready_msg is the first message that is going to be sent
to peer applications, bypassing the normal broadcasting scheme of Ivy (see
The Ivy architecture and protocol for more details). If a zero
value is passed, no message will be sent.
app_cb is a callback that will be called every time a new
peer is detected. If a zero value is passed, no callback is called.
app_data is a pointer that will be passed to the
application-connection callback.
die_cb is a callback that will be called every time a
peer disconnects. If a zero value is passed, no callback is called.
die_data is a pointer that will be passed to the
application-disconnection callback.
void IvyStart (const char* bus);
connects your application to the bus specified in
bus. The string provided should follow the convention
described in section XX. Example:
"10.192.33,10.192.34:2345". If a null value is passed,
the library will use the value of the environment variable
IVYBUS, which should have the same syntax. If the
environment variable is not defined, the default value
"127:2010" is used.
void IvyMainLoop (void (*beforehook) (void),void (*afterhook) (void));
makes your application enter the main loop in which it will handle asynchronous
communications and signals. This is the default Ivy main loop, based on the
select POSIX system call.
If non-null, beforehook is called every time the main loop is
about to enter select, and can be used (with care!) to
extend the main loop. afterhook is called each time the
main loop exits select.
void IvyStop ();
makes your application exit the main loop.
Emitting messages
Emitting a message on an Ivy bus is much like printing a message on the standard
output. However, do not forget that your message will not be emitted if Ivy has
not been properly initialized and if you do not have a main loop of some sort
running. To emit a message, use IvySendMsg, which works like printf:
void IvySendMsg (const char* format, ...);
sends a message on the bus. This function has exactly the same behaviour as
printf, sprintf or fprintf.
Subscribing to messages
Subscribing to messages consists in binding a callback function to a message
pattern. Patterns are described by regular expressions with captures. Since
the 3.6 library, we use Perl Compatible Regular
Expression. When a
message matching the regular expression is detected on the bus, the callback
function is called. The captures (ie the bits of the message that match the
parts of regular expression delimited by brackets) are passed to the callback
function much like options are passed to main. Use function IvyBindMsg
to bind a callback to a pattern, and function IvyUnbindMsg to delete the
binding.
MsgRcvPtr IvyBindMsg (MsgCallback cb,
void* data,
const char* regex_format, ...);
binds callback function cb to the regular expression specified by
regex_format and the optional following arguments. regex_format and
the following arguments are handled as in printf. The return
value is an identifier that can be used later for cancelling the subscription.
void IvyUnbindMsg (MsgRcvPtr id);
deletes the binding specified by id.
In what precedes, MsgRcvPtr is an opaque type used to identify bindings,
data is a user pointer passed to the callback whenever it is called, and
MsgCallback is defined as follows:
typedef void (*MsgCallback)(IvyClientPtr app, void *data, int argc, char **argv);
Advanced functionsUtilities
[to be written]
Direct messages
There are cases when we don't want to send messages to every other subscribee
on the bus. Direct messages allow peer to peer communication, and thus kind of
break the software bus metaphor. We strongly discourage you to use them, and
thus won't provide handy documentation.
Managing timers and other channels
In your applications, you may need to manage other input/output channels than an
Ivy bus: a serial driver, the channels defined by a graphical toolkit, or simply
stdin and stdout. The same applies for timers. You can either manage those
channels or timers from the Ivy main loop, or instead use the main loop provided by
another library.
Channels
You can get a channel to be managed from the Ivy main loop by using functions
IvyChannelSetUp and IvyChannelClose.
Channel IvyChannelSetUp (HANDLE fd,
void* data,
ChannelHandleDelete handle_delete,
ChannelHandleRead handle_read);
ensures that function handle_read is called whenever data is read on file
descriptor fd, and function handle_delete whenever fd is
closed, and
void IvyChannelClose (Channel ch);
terminates the management of channel ch.
In what precedes, Channel is an opaque type defined by the Ivy C
library, data is a pointer that will be passed to
functions handle_read and
handle_delete. It can be defined at will by users. The
types HANDLE, ChannelHandleDelete and
ChannelHandleRead are as follows:
typedef int HANDLE; (for Unix)
typedef SOCKET HANDLE; (for Windows)
typedef void (*ChannelHandleDelete)(void *data);
typedef void (*ChannelHandleRead)(Channel ch, HANDLE fd, void* data);
Adding timers
You can get a function to be repeatedly called by using function
TimerRepeatAfter:
TimerId TimerRepeatAfter (int nbticks, long delay, TimerCb handle_timer, void* data);
ensures that function handle_timer is called nbticks times at
intervals of delay seconds, thus creating a timer.
void TimerModify (TimerId id, long delay);
changes the delay used for timer id.
void TimerRemove (TimerId id);
deletes timer id, thus stopping it.
In what precedes, data is passed to handle_timer every time it is
called. delay is expressed in milliseconds.
If nbticks is set to TIMER_LOOP, then handle_timer will
be called forever. TimerCb is as follows:
typedef void (*TimerCb)(TimerId id, void *data, unsigned long delta);
Conventions for writing applications
In addition to the Ivy protocol, Ivy applications
should respect two conventions when used in a Posix environment:
They should accept the option or
to specify the Ivy bus on which they will connect. The Ivy
C library provides no support for that.
They should refer to the environment variable
IVYBUS when the above option is not used. With the Ivy C
library, this is obtained by passing a null value to IvyStartUsing Ivy with another main loop
The ivyprobe source code holds examples of use of Ivy within other main loops,
namely Xt and Gtk.
Using Ivy with the X ToolkitThe basics for using the Ivy withing the XtAppMainLoop() are the
following ones:
include the ivy.h and ivyxtloop.hlink with libxtivy.o ( add the ld flag and NOT the )create the ivy busIvyXtChannelAppContect(app_context) with an existing Xt
contextYou can add channels to be handled by Ivy, for instance,
stdin, with the IvyXtChannelSetUp function
IvyInit(char *name,char *readyMessage,IvyApplicationCallback
cb,void *cbUserData,IvyDieCallback dieCb,void *dieCbUserdata)IvyBindMsg() for the behaviorIvyStart(char *domain)run the Xt main loop with XtAppMainLoop(app_context)Here is an example, motifButtonIvy.c. You can compile it with the
following command line:
cc -o motifButtonIvy motifButtonIvy.c -lxtivy
The result is a simple single-buttoned application emitting a message on the
bus. The message defaults to "foo", but can be updated via an Ivy Button
text=bar message.
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <Xm/PushB.h>
#include <ivy.h>
#include <ivyxtloop.h>
void myMotifCallback(Widget w,XtPointer client_d,XtPointer call_d){
IvySendMsg (*((char**)client_d));
}
void textCallback(IvyClientPtr app, void *user_data, int argc, char *argv[]){
*((char **)user_data)=argv[0];
}
void DieCallback (IvyClientPtr app, void *data, int id){
exit(0);
}
int main(int argc,char *argv[]){
Widget toplevel,pushb;
XtAppContext app_context;
Arg myargs[10];
char *bus=getenv("IVYBUS");
char *tosend="foo";
toplevel=XtAppInitialize(&app_context,"Ivy Button",NULL,0,&argc,argv,NULL,myargs,0);
pushb=XmCreatePushButton(toplevel,"send message",myargs,1);
XtManageChild(pushb);
XtAddCallback(pushb,XmNactivateCallback,myMotifCallback,&tosend);
XtRealizeWidget(toplevel);
IvyXtChannelAppContext(app_context);
IvyInit("IvyMotif","IvyMotif connected",NULL,NULL,DieCallback,NULL);
IvyBindMsg(textCallback,&tosend,"^Ivy Button text=(.*)");
IvyStart(bus);
XtAppMainLoop(app_context);
}
Using Ivy with Tcl/Tk
A full example in Tcl/Tk is provided here:
#!/usr/bin/wish
load libtclivy.so.3
proc connect {args} { }
proc send { } {
global tosend
Ivy::send $tosend
}
proc dotext {text} {
global tosend
set tosend $text
}
Ivy::init "IvyTCLTK" "IvyTCLTK READY" connect echo
Ivy::start 127.255.255.255:2010
Ivy::bind "^Ivy Button text=(.*)" dotext
set tosend foo
button .send -command send -text "send msg"
pack .send
Using Ivy with Gtk/GdkThere is little to do to make your gtk applications Ivy aware: just add
the following lines into your code:
#include <Ivy/ivy.h>
#include <Ivy/ivyglibloop.h>
...
IvyInit ("IvyGtkButton", "IvyGtkButton READY",NULL,NULL,NULL,NULL);
IvyBindMsg(textCallback,&tosend,"^Ivy Button text=(.*)");
IvyStart (bus);
A full example: gtkIvyButton.c is provided below, compile it with the
appropriate flags for your gtk distribution ( `pkg-config --cflags --libs
gtk+-x11-2.0 `) and -lglibivy .
#include <gtk/gtk.h>
#include <Ivy/ivy.h>
#include <Ivy/ivyglibloop.h>
#include <stdio.h>
#include <stdlib.h>
void hello( GtkWidget *widget, gpointer data ) {
fprintf(stderr,"%s\n",*((char**)data));
IvySendMsg(*((char**)data));
}
void textCallback(IvyClientPtr app, void *user_data, int argc, char *argv[]){
*((char **)user_data)=argv[0];
}
int main( int argc, char *argv[] ) {
GtkWidget *window;
GtkWidget *button;
char *bus=getenv("IVYBUS");
char *tosend="foo";
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
button = gtk_button_new_with_label ("send message");
g_signal_connect (G_OBJECT(button),"clicked",G_CALLBACK(hello),&tosend);
gtk_container_add (GTK_CONTAINER(window),button);
gtk_widget_show (button);
gtk_widget_show (window);
IvyInit ("IvyGtkButton", "IvyGtkButton READY",NULL,NULL,NULL,NULL);
IvyBindMsg(textCallback,&tosend,"^Ivy Button text=(.*)");
IvyStart (bus);
gtk_main ();
return 0;
}
Adding Ivy to another main loopFunctions to be provided
You can decide to use the main loop from another toolkit than the X Toolkit
or the Tk toolkit. If you do that, you'll have to define three functions that
Ivy will use to get its own channels managed by the other toolkit. The three
following global variables should be defined:
ChannelInit channel_init;
ChannelSetUp channel_setup;
ChannelClose channel_close;
They should point to functions that respectively:
make the necessary global initializations before entering the main loop initialize a channel and ensure that it is managed by the main loop close a channel
The types ChannelInit, ChannelSetUp and ChannelClose are defined
as follows:
typedef void (*ChannelInit)(void);
typedef Channel (*ChannelSetUp)(
HANDLE fd,
void *data,
ChannelHandleDelete handle_delete,
ChannelHandleRead handle_read);
typedef void (*ChannelClose)( Channel channel );
Type to be defined
In order to implement the three previous functions, you will need to define the
hidden type struct _channel (the type Channel is
defined as struct _channel*). Use it to store the data provided by
the other toolkit.
Contacting the authors
The Ivy C library was mainly written by Francois-Régis Colin, with support
from Stéphane Chatty. For bug reports or comments on the library itself or
about this document, please send them an email: fcolin@cena.fr and
chatty@cena.fr. For comments and ideas about Ivy itself (protocol,
applications, etc), please use the Ivy mailing list: ivy@tls.cena.fr.