Fixing the Implementation
To provide instance-specific state for each instance of the calculator type,
we can begin by observing that the calculator object is provided to each true
method as the self parameter.
A D V E R T I S E M E N T
We could thus perform the binding of
some local state to each instance with a hash table, which would map values of
Tutorial_Calculator to values of CORBA_double (or more
probably, to values of CORBA_double *), so that each calculator
object would be manipulating its own separate value.
A simpler way, however, is to use the user data field of each
ILU_C_Object, a slot of type void * which is reserved for
use by the true implementation of the object type. This field can be initialized
by the user with a value when a true instance of an object type is created.
We'll specify that the user data field of instances of the type
Tutorial_Calculator contain a pointer to a malloc'ed
CORBA_double value. We can help to enforce that constraint by adding a
utility function to our code, that creates an instance of
Tutorial_Calculator by malloc'ing a value of
CORBA_double, then calls the automatically generated function
Tutorial_Calculator__CreateTrue to actually create the instance, passing
the pointer to the malloc'ed value as an argument.
[ We define a function which creates a new instance of a Calculator object. ]
Tutorial_Calculator
Create_Tutorial_Calculator ()
{
CORBA_double *the_value = (CORBA_double *) malloc(sizeof(CORBA_double));
*the_value = 0.0; /* zero out our value */
[ The function Tutorial_Calculator__CreateTrue is automatically
generated into the file "Tutorial-true.c" by the c-stubber.
It takes three arguments, INSTANCE-HANDLE, SERVER, and
USER-DATA-FIELD, and returns a new instance of Tutorial_Calculator.
We don't care about what the INSTANCE-HANDLE and SERVER of
Calculator instances are, so we'll pass ILU_NIL (which is another name
for NULL) for the first two arguments, which will cause ILU
to choose reasonable default values for us. ]
return (Tutorial_Calculator__CreateTrue (ILU_NIL, ILU_NIL, the_value));
}
We also modify each of our six true methods to use the user data field, which
is available through the void * instanceData field of any
ILU_C_Object value. For example, the Add method now looks
like:
void
server_Tutorial_Calculator_Add (
Tutorial_Calculator self,
CORBA_double v,
CORBA_Environment *env)
{
[ The user data field is available as the field "void *instanceData" of
the object instance "self", so we'll just add "v" to it. ]
*((CORBA_double *) (self->instanceData)) += v;
}
Making a Library
Our implementation can now be combined with the code generated by the c-stubber
program to form a library. Assuming that our implementation resides in a file
called `Calculator-impl.c', we compile each of the C source files, and
combine them in a library (assuming that ILU has been installed under `/usr/local/ilu/'):
% make
/usr/local/ilu/bin/c-stubber Tutorial.isl
header file interface Tutorial to ./Tutorial.h...
common code for interface Tutorial to ./Tutorial-common.c...
code for surrogate stubs of interface Tutorial to ./Tutorial-surrogate.c...
code for true stubs of interface Tutorial to ./Tutorial-true.c...
rm -f Tutorial-common.o
cc -c -I. -I/usr/local/ilu/include Tutorial-common.c
rm -f Tutorial-surrogate.o
cc -c -I. -I/usr/local/ilu/include Tutorial-surrogate.c
rm -f Tutorial-true.o
cc -c -I. -I/usr/local/ilu/include Tutorial-true.c
rm -f Calculator-impl.o
cc -c -I. -I/usr/local/ilu/include Calculator-impl.c
rm -f libTutorial.a
ar clq libTutorial.a Tutorial-true.o \
Tutorial-surrogate.o \
Tutorial-common.o \
Calculator-impl.o
ranlib libTutorial.a
%
The exact paths used, and the exact form of the cc and ar
commands will differ from system to system. On some systems, the ranlib
command is not necessary.
Using the True Module as a Library
If an implementation of a module (sometimes called the true module) is
available as a library, how would it be used? Taking our 4-function calculator
as an example, suppose that a programmer wanted to write a C program which used
its functionality; how would that functionality be seen from that application?
It would appear as an object of the Calculator type; that is, a
value of the C type Tutorial_Calculator, which would be used as the
first argument to any of the six methods that the library exports. These methods
would be called by their generic function names, rather than their true
names, because this is a use of the module rather than an
implementation of the module.
Another question that immediately comes up is, "how do I get my hands on an
instance of the Calculator object to begin with?" Remember that we
added the function Create_Tutorial_Calculator, which will return an
instance of a calculator object.
So, a very simple program to use the Tutorial module might be
the following:
/* simple1.c */
[ A simple program that demonstrates the use of the
Tutorial true module as a library. ]
#include <stdio.h> /* for NULL */
#include <stdlib.h> /* for atof */
[ Include the header file for the Tutorial interface, so that our
types and methods will be defined. ]
#include <Tutorial.h>
[ We should also define a prototype for the Create function
exported from the implementation of the Tutorial module. ]
extern Tutorial_Calculator Create_Tutorial_Calculator(void);
[ A simple program:
1) make an instance of Tutorial.Calculator
2) add all the arguments by invoking the Add method
3) print the resultant value. ]
[ continued on following page ]
int main (int argc, char **argv)
{
Tutorial_Calculator c;
CORBA_double v;
char **arg;
CORBA_Environment env;
[ Initialize the Tutorial module. ]
Tutorial__InitializeServer();
[ Create an instance of a Tutorial.Calculator object. ]
if ((c = Create_Tutorial_Calculator()) == NULL)
{
fprintf (stderr, "Couldn't create calculator!\n");
exit(1);
}
[ Clear the calculator before using it. ]
Tutorial_Calculator_SetValue (c, 0.0, &env);
[ Now loop over the arguments, adding each in turn. ]
for (arg = ++argv; *arg != NULL; arg++)
{
v = atof (*arg);
Tutorial_Calculator_Add (c, v, &env);
}
[ And print the result. ]
v = Tutorial_Calculator_GetValue (c, &env);
printf ("the sum is %.5e\n", v);
exit (0);
}
This program would be compiled and run as follows:
% cc -o simple1 -I. -I/usr/local/ilu/include simple1.c libTutorial.a \
/usr/local/ilu/lib/{libilu-c,libilu}.a
% ./simple1 34.9 45.23111 12
the sum is 9.21311e+01
%
This is a completely self-contained use of the Tutorial
implementation; when a method is called, it is the true method that is invoked.
The use of ILU in this program adds some overhead in terms of included code, but
has almost the same performance as a version of this program that does not use
ILU.
Checking for Exceptions
Suppose, instead of the Add method, we'd called the Divide
method. In that case, we might have had to handle a DivideByZero
exception; that is, notice the exception and do something sensible. We do this
by testing the env parameter. We can test this parameter in a
number of ways; the simplest is to use the macro ILU_C_SUCCESSFUL,
which evaluates to 1 if no exception was raised, or 0 if some exception
occurred. We could then use the macro ILU_C_EXCEPTION_ID to
retrieve the name of the exception and print it. For example, here's a fragment
of C code that checks for the DivideByZero exception:
/* from simple2.c */
...
Tutorial_Calculator_Divide (c, v, &env);
if (! ILU_C_SUCCESSFUL(&env))
{
fprintf (stderr, "Divide signalled exception <%s>.\n",
ILU_C_EXCEPTION_ID(&env));
exit(1);
}
...
And here's an example of what we get when it runs:
% ./simple2 12345 6 7 8 9
the quotient is 4.08234e+00
% ./simple2 12345 6 0 8 9
Divide signalled exception <Tutorial: DivideByZero>.
%
Actually, every method may return an exception, as there are a number of
standard system exceptions which may be signalled even by methods which have no
declared exceptions. So we should check every method to see if it succeeded,
even simple ones like GetValue.
Providing the True Module as a Network Service
Now let's see what's involved in providing the calculator functionality as a
network service. Basically, there are three things to look at:
- providing a "factory" to build calculator objects;
- publishing the name of the factory; and
- writing a server program.
Using Factories to Build Objects
When one program uses code from another address space, it has to get its
hands on an instance of an ILU object, to be able to call methods. In our
library application, we simply made a call into the true module, to create an
instance of the calculator object. In the networked world, we need to do the
same kind of thing, but this time the call into the true module has to be a
method on an object type. In short, we need to have some object type which
exports a method something like
CreateCalculator () : Calculator
There are several ways to provide this. The standard way of doing it is to
add an object type to our Tutorial interface, which contains this
method. This kind of object type is sometimes called a factory, because
it exists only in order to build instances of other object types. We'll add the
following type definition to our `Tutorial.isl':
TYPE Factory = OBJECT
METHODS
CreateCalculator () : Calculator
END;
Then we need to provide an implementation of the Factory object
type, just as we did with the Calculator type:
/* Factory-impl.c */
[ Include the Tutorial header file, to get all the defined
types and function prototypes. ]
#include <Tutorial.h>
[ Code for the Factory object type. ]
extern Tutorial_Calculator Create_Tutorial_Calculator(void);
Tutorial_Calculator
server_Tutorial_Factory_CreateCalculator (
Tutorial_Factory self,
CORBA_Environment *env)
{
return (Create_Tutorial_Calculator());
}
Now, to provide other programs a way of creating calculator objects, we'll
just create just one instance of Tutorial.Factory, and let programs
call the CreateCalculator method on that at will, to obtain new
calculator objects.
Publishing a Well-Known Instance
The question then arises, how does a program that wants to use the
Factory object get its hands on that one well-known instance? The answer
is to use the simple binding system built into ILU. Simple binding allows
a program acting as a "server" to publish the location of a well-known
object, and allows programs acting as "clients" of that server to look up the
location, given the object's name.
The name of an ILU object instance has two parts, which are the instance
handle of the object, and the name of its kernel server, called the
server ID. (The kernel server is a data structure maintained by the ILU
kernel which takes care of all communication between different address spaces.)
These two combined must form a universally unique ID for the object. Usually you
can simply let the ILU system choose names for your objects automatically, in
which case it takes care to choose names which will never conflict with names in
use by others. However, for objects which we wish to publish, we need to specify
what the name of an object will be, so that users of the well-known object can
find it.
When working with the C programming language, this act of explicitly
specifying an object name is divided into two steps. First, we create a kernel
server with a specified server ID. Secondly, we create an instance of an object
on this new server, with a specified instance handle. Together, the server ID
and the instance handle form the name of the instance.
For instance, we might use a server ID of Tutorial.domain,
where domain is your Internet domain (typically something like
department.company.com, or department.univerity.edu).
This serves to distinguish your server from other servers on the net. Then we
can use a simple instance handle, like theFactory. The name, or
object ID, of this object would then be the pair (Tutorial.domain,
theFactory), where domain would vary from place to
place. Note that this implies that only one instance of this object is going to
exist in the whole domain. If you have many people using different versions of
this object in your domain, you should introduce more qualifiers in the server
ID so that your kernel server can be distinguished from that run by others.
The Server Program
Given this information, we can now write a complete program that will serve
as a provider of calculator objects to other programs. It will create a single
Factory instance with a well-known name, publish that instance,
then sit tight servicing methods invoked on its objects. Here's what it looks
like:
/* server.c */
#include <stdio.h> /* for stderr and NULL */
[ Include the Tutorial header file, to get all the defined
types and function prototypes. ]
#include <Tutorial.h>
int main (int ac, char **av)
{
Tutorial_Factory theFactory;
ilu_Server theServer;
int stop;
[ Continued on next page... ]
[ This program is to be called with one argument, the server ID
to use ("Tutorial.foo.something.com" or something like that.) ]
if (ac < 2)
{
fprintf (stderr, "Usage: server SERVER-ID\n");
exit(1);
}
[ In any server program, we have to initialize each interface
that we're providing types from, by calling the InitializeServer
method on that interface. In this case, it's just the
Tutorial interface. ]
Tutorial__InitializeServer();
[ We make a "kernel server", using the server ID that was
passed in on the command line, the default "object table",
the default protocol for data pickling and message packets,
the default transport system for getting data back and forth,
and we make this kernel server the default server for the
program. ]
theServer = ILU_C_InitializeServer (
av[1], /* the server ID */
NULL, /* use no object table */
NULL, /* use default protocol */
NULL, /* use default transport */
NULL, /* no passport here */
ilu_TRUE /* establish as default server */
);
[ Now that we have a server, we create an instance of the
Factory object type, with the instance handle of "Factory",
by calling the automatically generated procedure
"Tutorial_Factory__CreateTrue()". ]
theFactory = Tutorial_Factory__CreateTrue ("theFactory",
/* instance handle */
theServer,
/* server to use */
NULL
/* no user data */
);
[ Now make the Factory object "well-known" by publishing it.
PublishObject will return NULL if it can't publish the
object; otherwise it will return a pointer to a string,
which is the "publish proof". ]
if (ILU_C_PublishObject(theFactory) == NULL)
{
fprintf (stderr, "Can't publish theFactory object.\n");
exit(1);
}
else
{
[ Now we print the string binding handle (the object's name plus
its location) of the new instance. ]
printf ("Factory instance published.\n");
printf ("Its SBH is \"%s\".\n", ILU_C_SBHOfObject(theFactory));
[ ilu_RunMainLoop() is an event dispatching loop. It may
be exited by invoking ilu_ExitMainLoop() passing the same
"int *" value ilu_RunMainLoop was invoked with. ]
ilu_RunMainLoop (&stop);
}
}
When we run this program, we'll see something like:
% ./server Tutorial.dept.company.com
Factory instance published.
Its SBH is "ilu:Tutorial.dept.company.com/theFactory;@somegibberish".
%
This indicates that the object known as theFactory@Tutorial.dept.company.com
is being exported in a particular way, which is encoded in the
somegibberish part of the string binding handle. Your specific numbers may
vary, but it should look similar.
Using the Network Service
Given that someone has exported a module as a network service, by publishing the
location of a well-known instance of an object type, a potential client of that
module can then use the module by binding to that well-known instance. It does
this by calling the standard ILU routine ILU_C_LookupObject, which
takes the name and type of an instance, and attempts to find that instance on
the net.
So, in our first example, we could replace the call to
Create_Tutorial_Calculator with a routine that calls
ILU_C_LookupObject to find the factory, then creates an instance of a
Calculator. The full code of the revised example, `simple3.c',
is available as section
simple3.c, but here's what the new code for obtaining an instance of a
Calculator looks like:
...
static Tutorial_Calculator
Get_Tutorial_Calculator (char *factObjSID, char *factObjIH)
{
Tutorial_Factory f;
Tutorial_Calculator c;
CORBA_Environment env;
[ We have to call ILU_C_LookupObject() with the object ID of the factory
object (the SID and IH pair), and the "type" of the object we're looking
for, which is always available as "TYPENAME__MSType". ]
f = ILU_C_LookupObject (factObjSID, factObjIH, Tutorial_Factory__MSType);
if (f == NULL) {
fprintf (stderr, "Couldn't find Factory object <%s %s>.\n",
factObjSID, factObjIH);
return (NULL); };
[ Now call the CreateCalculator method on the factory, and check the result. ]
c = Tutorial_Factory_CreateCalculator (f, &env);
if (! ILU_C_SUCCESSFUL(&env)) {
fprintf (stderr,
"Call to CreateCalculator failed with exception <%s>.\n",
ILU_C_EXCEPTION_ID(&env));
return (NULL); };
[ Return the calculator object... ]
return (c);
}
We then link and use the simple3 program:
% cc -o simple3 simple3.o libTutorial.a ${ILUHOME}/lib/libilu-c.a \
${ILUHOME}/lib/libilu.a
% ./simple3 Tutorial.dept.company.com theFactory 1 2 3 4 5 6
the sum is 2.10000e+01
%
|