C-Kernel - An Example
Here we provide a brief tutorial on how to write components for the C version of the RUNES middleware through a sample application. First we outline the process involved in developing a component, then we build on this to develop a (simple) multi-component application…
Developing a Component
In the C/Linux environment, components are represented as Linux .so files.
In the C source for the .so file, the developer implements the set of functions
that appear in all of the interfaces supported by the component. Each function
takes Component *
as its first argument - this refers to
the component instance being invoked. In each component, the developer
implements two standard C functions as follows:
int construct(Component *comp, int argc, char argv[]);
this is called by the runtime'sinstantiate()
API. It sets up the Receptacles and Interfaces of the new component instance. Theargc
andargv
arguments are used to pass initialisation state to the new component. If component instances need to employ persistent state, this is declared in a C struct and registered with the kernel. See example below.int destruct(Component *comp);
is called by the runtime'sdestroy()
API. It reclaims (usingfree()
any per-component instance state that was allocated by construct().
Adder
component that has
a single interface as follows:/* IDL */
Interface Adder {
int add(in int a, in int b);
int increment(void);
}
The header file for this component is as follows:
/* adder.h - Adder Component with
a single Interface */
#include “rcm.h”
/* Interface struct for the Adder
interface */
typedef struct adder {
int (*add)(Component *comp, int
x, int y);
int (*increment)(Component
*comp);
} Adder;
/* State struct for the 'adder'
component */
typedef struct as {
int current; /* for use by
increment() */
} Adder_State;
contruct()
function there are
5 steps outlined in the below. Actually, these are quite generic to
any contructor implementation:/* adder.c - adder component with
a single interface
of type Adder */
#include "rcm.h“
#include "adder.h“
int construct(Component *comp)
{
Adder *ai;
Adder_State *as;
/* STEP 1: Allocate memory for function pointer structs
(n.b. VEFIFY() is a simple
macro that makes its enclosing
function return the
macro’s second arg if the first arg
evaluates to 0 - in such cases
the list of statements
comprising the third arg is
executed before returning) */
VERIFY(ai = (Adder
*)malloc(sizeof(Adder)),
ERR, "adder.construct():
malloc",);
/* STEP 2: Allocate the memory for the state struct */
VERIFY(as = (Adder_State
*)malloc(sizeof(Adder_State)),
ERR, "adder.construct():
malloc", free((void *)ai));
/* STEP 3: Create and register
the receptacles. As there
are no receptacles in this
component we have nothing to
do here. If there were, we
would use rcm_regreceptacle()
in a similar way to that
illustrated below for
rcm_reginterface(). */
/* STEP 4: Create and register
the interfaces */
ai->add = add ; /*
assign function pointers... */
ai->increment = increment ;
VERIFY(rcm_reginterface(comp,
"Adder", ai, sizeof(Adder)),
ERR, "adder.construct():
rcm_reginterface",
free((void *)ai); free((void
*)as););
/* STEP 5: Create and register the state */
as->current = 0;
rcm_regstate(comp, (void *)as);
return OK;
}
int destruct(Component *comp)
{
/* free any non-top-level
state-related dynamic
data - none here */
return OK;
}
int add(Component *comp, int a,
int b)
{
/* carry out the application
specific job; in this case we
can ignore the comp argument
as the add operation doesn't
involve state */
return a + b;
}
int increment(Component *comp)
{
Adder_State *state;
/* call a utility function to
obtain the state for this
component */
rcm_getstate(comp, (void **)&state);
return ++(state->current);
}
Developing a Client for our Adder Component
Now, here is a simple client for the Adder component we have just developed:/* IDL */
Interface Client {
int run(void);
}
/* client.h */
#include "rcm.h“
typedef struct client {
int (*run) (Component *comp);
} Client;
/* client.c */
#include "client.h“
#include "adder.h“
int construct(Component *c); {...}
int destruct(Component *c); {...}
int run(Component *comp)
{
Receptacle *r;
int i, t;
VERIFY(rcm_getreceptacle(comp,
"Adder", &r),
ERR, "client.run():
get_receptacle",);
for (i=0; i<5; i++) {
t = INVOKE_RECEPTACLE(r, Adder,
add, i, i+1);
printf("Adder.add(%d, %d):
%d\n", i, i+1, t);
t = INVOKE_RECEPTACLE(r, Adder,
increment);
printf("Adder.increment():
%d\n", t);
}
return OK;
}
run()
calls rcm_getreceptacle()
to obtain the client component's
receptacle, and then uses the INVOKE_RECEPTACLE()
macro to perform an invocation on that
receptacle. The receptacle is assumed here to be already bound to the
Adder component's interface! How does this happen? To see how let's
now look at an application that puts it all together...
Putting it all Together
Here is amain()
function
that instantiates and binds instances of our Adder and Client components and
then sets things off by calling the Client's run()
operation. Note that the call of run()
is achieved using a macro called INVOKE_INTERFACE()
. This allows us to call an interface
directly - i.e. an interface that has not necessarily been bound to a
receptacle. This should only be used for bootstrapping purposes as in the case
here - somehow we need to get the 'first' component running given that a C main()
function is not itself a component.main()
{
ComponentType *adderCT,
*clientCT;
Component *adder, *client;
Receptacle *rAdder;
Interface *iAdder, *iClient;
Component *con;
printf("Starting...\n");
/* initialise */
rcm_init();
/* load the “adder”
and “client” ComponentTypes */
VERIFY(rcm_load("adder.so", &adderCT) != ERR,
ERR, "main: rcm_load
adder",);
VERIFY(rcm_load("client.so", &clientCT) != ERR,
ERR, "main: rcm_load
client",);
/* instantiate adder and client
components */
VERIFY(rcm_instantiate(adderCT,
&adder, 0,
(char **)0) != ERR, ERR,
"main: rcm_instantiate adder",);
VERIFY(rcm_instantiate(clientCT,
&client, 0,
(char **)0) != ERR, ERR,
"main: rcm_instantiate client",);
/* get the Adder Receptacle from
the client instance */
VERIFY(rcm_getreceptacle(client,
"Adder", &rAdder),
ERR, "main: get_receptacle
adder",);
/* get the Adder Interface from
the adder instance */
VERIFY(rcm_getinterface(adder,
"Adder", &iAdder),
ERR, "main: get_interface
adder",);
/* connect them using the default
Connector */
VERIFY(rcm_connect(rAdder, iAdder,
"default_connector.so", &con), ERR, "main:
rcm_connect",);
/* get the Client interface from
client and call
the “run” method
on it */
VERIFY(rcm_getinterface(client,
"Client", &iClient),
ERR, "main: get_interface
client",);
INVOKE_INTERFACE(iClient, Client,
run);
/* when we get back here, client
has done making
calls on adder; so clean
up... */
VERIFY(rcm_destroy(con) != ERR,
ERR, "main: rcm_destroy
dummy Conn",);
VERIFY(rcm_destroy(adder) != ERR,
ERR, "main: rcm_destroy
adder",);
VERIFY(rcm_destroy(client) !=
ERR,
ERR, "main: rcm_destroy
client",);
VERIFY(rcm_unload(adderCT) !=
ERR,
ERR, "main: rcm_unload
adderCT",);
VERIFY(rcm_unload(clientCT) !=
ERR,
ERR, "main: rcm_unload
clientCT",);
printf("Done\n");
}