Go to the previous, next section.
This document is for the C++ programmer who wishes to use ILU. The following sections will show how ILU is mapped into C++ constructs and how both C++ clients and servers are generated and built.
When functions are described in this section, they are sometimes accompanied by locking comments, which describe the locking invariants maintained by ILU on a threaded system. See the file `ILUSRC/runtime/kernel/iluxport.h' for more information on this locking scheme, and the types of locking comments used.
A number of macros are used in function descriptions, to indicated optional arguments, and ownership of potentially
malloc'ed objects.
The owner is responsible for freeing the object's storage at an appropriate time, and generally makes no interesting guarantees of when that will be. Some types of objects (generally (fixed- and variable-length) arrays, including strings) are presumed to be mutable by only their owners.
The macro OPTIONAL(type-name)
means that the value
is either of the type indicated by type-name, or the value NULL
. This macro
may only be used with pointer values. The macro RETAIN(type-name)
indicates, when used on a parameter type, that the caller retains ownership of the value,
and when used on a return type, that the called function retains ownership of the
value. The macro PASS(type-name)
indicates, when used on a parameter type,
that the caller is passing ownership of the storage to the called function, and when
used on a return type, that the called function is passing ownership of the called
value to the caller. The macro GLOBAL(type-name)
means that neither
the caller nor the calling function owns the storage.
Using ILU with C++ is intended to eventually be compatible with the OMG CORBA specification. That is, all of the naming and stub generation comply with the Common Object Request Broker Architecture specified mapping for C++, when that specification is available. The current mapping was designed to be usable with a large number of C++ compilers, by avoiding problematic constructs such as templates, exceptions, namespaces, and nested class definitions.
Note that ILU support for C++ does rely on having argument prototypes, all C++ library functions, and the capabilities of the C++ pre-processor.
In general, ILU constructs C++ names from ISL names by replacing hyphens with underscores. Type names are prepended with their interface name and the string "_T_". Enumeration value namess are formed by prepending the enumeration type name and "_" to the ISL enumeration value name. Exception names are prepended with their interface name and "_E_". Constant names are prepended with their interface name and "_C_".
Other naming conventions may be specified explicitly; see the following section on tailoring names for more information.
Records turn directly into C++ structs.
Unions consist of a struct with two fields: the type discriminator, a field called "discriminator",
and a union of the possible values, called "value".
Arrays map directly into C++ arrays. Sequences become a C++ class
with methods and representation analogous to the procedures and representation that appear in the C mapping. Objects become normal C++ classes
that are subclasses of the pre-defined class iluObject
.
For most sequences types, the generated C++ code follows a pattern analogous to that set in the C mapping and illustrated in the upcoming example.
Because of the scarcity of implementation of the C++ exception mechanism,
exceptions are passed by adding an additional argument to the beginning of each method,
which is a pointer to a status struct, which contains an exception code, and a union of
all the possible exception value types defined in the interface. Method implementations
set the exception code, and fill in the appropriate value of the union, to signal an
exception. Exception codes
are represented in C++ with values of the type ilu_Exception
.
In a true module, exceptions may be raised by using the function <interface>_G::RaiseException
.
C++: void
Causes an exception code and value for the exception specified by code to be bound
in status. Besides the two required arguments, the function may take another
argument, which should be a value of the type implied by the value of code;
that is, of the appropriate type to be a value of the exception being signalled.
Note that RaiseException
does not actually cause a transfer of control,
so that an explicit return statement must follow a call to RaiseException
.
Constants are implemented with C++ #define
statements.
Here's a sample ISL spec, and the resulting C++ mappings:
INTERFACE Foo; TYPE String = ilu.CString; TYPE UnsignedInt = CARDINAL; TYPE E1 = ENUMERATION val1, val2, val3=40 END; TYPE R1 = RECORD field1 : CARDINAL, field2 : E1 END; TYPE A1 = ARRAY OF 200 BYTE; TYPE A2 = ARRAY OF 41, 3 R1; TYPE S1 = SEQUENCE OF E1; TYPE U1 = UNION R1, A2 END; EXCEPTION Except1 : String; CONSTANT Zero : CARDINAL = 0; TYPE O1 = OBJECT METHODS M1 (arg1 : R1) : UnsignedInt RAISES Except1 END END;
The C++ mapping:
typedef ilu_CString Foo_T_String; typedef ilu_Cardinal Foo_T_UnsignedInt; typedef enum _Foo_T_E1_enum { Foo_T_E1_val1 = 1, Foo_T_E1_val2 = 2, Foo_T_E1_val3 = 40 } Foo_T_E1; typedef struct _Foo_T_R1_struct { ilu_Cardinal field1; Foo_T_E1 field2; } Foo_T_R1; typedef ilu_Byte Foo_T_A1[200]; typedef Foo_T_R1 Foo_T_A2[41][3]; class _Foo_T_S1_sequence { private: ilu_Cardinal _maximum; ilu_Cardinal _length; Foo_T_E1 *_buffer; public: _Foo_T_S1_sequence (); virtual ~_Foo_T_S1_sequence (); static class _Foo_T_S1_sequence *Create (ilu_Cardinal initial_size, Foo_T_E1 *initial_data); virtual ilu_Cardinal Length(); virtual void Append(Foo_T_E1); virtual Foo_T_E1 RemoveHead(); virtual Foo_T_E1 RemoveTail(); virtual ilu_Cardinal RemoveAll(ilu_Boolean (*matchproc)(Foo_T_E1)); virtual Foo_T_E1 * Array(); virtual Foo_T_E1 Nth(ilu_Cardinal index); }; typedef class _Foo_T_S1_sequence * Foo_T_S1; enum Foo_T_U1_allowableTypes { Foo_T_U1_R1, Foo_T_U1_A2 }; typedef struct _Foo_T_U1_union { enum Foo_T_U1_allowableTypes discriminator; union { Foo_T_R1 R1; Foo_T_A2 A2; } value; } Foo_T_U1; extern ilu_Exception Foo_E_Except1; /* exception code Except1 */ typedef struct _Foo_Status_struct { ilu_Exception returnCode; union { ilu_Cardinal anyvalue; Foo_T_String Except1; } values; } FooStatus; class Foo_T_O1 : public iluObject { public: Foo_T_O1(); // constructor virtual ~Foo_T_O1(); // destructor static class Foo_T_O1 * ILUCreateFromSBH(ilu_CString sbh); Foo_T_UnsignedInt M1 (FooStatus *_status, Foo_T_R1 *arg1); }; #define Foo_C_Zero ((ilu_Cardinal) 0)
A client module may obtain an instance of an ILU object in
three basic ways: 1) instantiating it directly from a string binding handle,
2) using the function iluObject::Lookup
to locate it via the
simple binding interface, and 3) receiving the instance directly as a return
value or out parameter from a method on a different object.
To instantiate from a string binding handle, a static member function is
generated for each subclass of class iluObject
declared in the C++ stubs:
C++: {OPTIONAL(class T *)} T::ILUCreateFromSBH (ilu_CString sbh)
To use the simple binding service to locate an object:
C++: static OPTIONAL(GLOBAL(void *)) iluObject::Lookup ( RETAIN(char *) sid, RETAIN(char *) ih, ilu_Class putative-class )
Locking: Main invariant holds.
Finds and returns the object specified by the given Server ID (sid) and server-relative Instance Handle (ih) by consulting the local domain registry of objects. putative-class is the type that the object is expected to be of, though the type of the actual object returned may be a subtype of putative-class, cast to the putative-class. The return value should be immediately cast to a value of the C++ mapping of putative-class.
For each ILU class interface.otype
,
ILU will define, in the file
`interface.cc',
a C++ class called interface_T_otype
.
To implement a true object for interface.otype
,
one should further subclass this C++ class,
and override all of its methods. In particular, do not let
any of the default methods for the class be called from your methods for it.
ILU supports,
in each address space, multiple instances of something called
a kernel server, each of which in turn
supports some set of object instances.
A kernel server exports its objects by making them available
to other modules. It may do so via one or more ports, which are
abstractly a tuple of (rpc protocol, transport type,
transport address). For example, a typical port might
provide access to a kernel server's objects
via (Sun RPC, TCP/IP, (host 13.24.52.9, UNIX port 2076))
. Another port on the
same kernel server might provide access to the objects via
(Xerox Courier, XNS SPP, XNS port 1394)
.
When creating an instance of a true object, a kernel server for it,
and an instance handle (the name by which the kernel server knows it) for
it must be determined. These may be specified explicitly by overriding
the default iluObject::ILUGetServer
and iluObject::ILUGetInstanceHandle
methods,
respectively. The iluObject
implementation of ILUGetServer
defers to ilu::GetDefaultServer
.
The iluObject
implementation of ILUGetInstanceHandle
generates a handle that's unique relative to the kernel server.
The kernel server is represented in C++ with the class iluServer
,
which has the following constructor:
C++: ?? iluServer::iluServer ( OPTIONAL(const char *) server-id, OPTIONAL(iluObjectTable *) object-table )
Constructs an instance of class iluObject
with the given server-id
and object-table.
Note that ILU object IDs, which consist of the kernel server ID, plus the instance
handle of the object on that server, must be unique "across space
and time", as the saying goes. If no kernel server id is specified,
ILU will generate one automatically, using an algorithm
that provides a high probability of uniqueness. If you explicitly
specify a kernel server ID, a good technique is to use a prefix or
suffix which uniquely identifies some domain in which you can assure
the uniqueness of the remaining part of the ID. For example, when
using ILU at some project called NIFTY at some internet
site in the IP domain department.company.com
, one might use
kernel server IDs with names like
something.NIFTY.department.company.com
.
Once the server is constructed, a port must be added:
C++: ilu_Boolean iluServer::AddPort (OPTIONAL(RETAIN(char *)) protocol-info, OPTIONAL(RETAIN(ilu_TransportInfo)) transport-info, ilu_Boolean be-default)
Adds a port through which the server can be contacted. The protocol-info and transport-info specify the RPC and transport protocols and their parameters. The transport-info has a layered structure, represented by the C type ilu_TransportInfo
, described in `ILUSRC/runtime/kernel/iluxport.h'. See chapter 1 for a catalogue of available RPC and transport layer specifications.
To export a module for use by other modules,
simply instantiate one or more instances
of your subtype of interface:otype
and (if single-threaded) call the ILU C++
event dispatching loop, iluServer::Run
.
Most non-threaded long-lived C and C++ programs
simulate threads with event dispatching, in which the program waits in some
piece of code called the main loop until an event such
as input arriving on a file descriptor or the expiration of an alarm
signal causes a callback routine to be invoked. The ILU C++
runtime, in single-threaded mode, supports this style of operation with various static member functions
of the class iluServer
.
C++: static ilu_Boolean iluServer::RegisterInputHandler ( int fd, void (*callbackRoutine)(int, void *), void * callbackArg)
Register the file descriptor fd with the ILU kernel so that when
ILU kernel event dispatching is active (that is, during the iluServer::Run
call),
the function callbackRoutine will be invoked with the arguments (fd, callbackArg)
whenever input is available on the file descriptor fd.
C++: static ilu_Boolean iluServer::UnregisterInputHandler ( int fd )
Removes any callback routine registered on file descriptor fd.
C++: static ilu_Boolean iluServer::Run ( void )
Invokes the ILU main loop and causes ILU kernel event dispatching to be active. This routine never returns.
Occasionally it is necessary to use a different event dispatching mechanism,
typically because some other work is done inside the main loop of the mechanism.
An alternate main loop can be registered for use with ILU by creating
a subtype of the class iluMainLoop
and registering it with the kernel
by calling the function iluServer::iluSetMainLoop
:
C++: static void iluServer::iluSetMainLoop ( RETAIN(iluMainLoop *) ml )
Registers the main loop object ml with the runtime kernel.
To enable users of your module find the exported objects, you may register the string binding handle of the object or objects, along with their type IDs, in any name service or registry that is convenient for you. In releases 1.6--2.0 of ILU, we support an experimental simple binding method that allows you to "publish" an object, which registers it in a domain-wide registry, and then to withdraw the object, if necessary. Potential clients can find the string binding handle of the object by calling a lookup function. Note that this interface and service is experimental, and may be supported differently in future releases of the ILU system.
C++: ilu_Boolean iluObject::ILUPublish ()
A method on instances of class iluObject
, it registers the instance with some
domain-wide registration service.
The object is known by its <Server-ID, Instance-Handle> pair. Clients may find
the object by passing this pair to the iluObject::Lookup
function.
Returns true if the object can be successfully published in the local registry.
C++: ilu_Boolean iluObject::ILUWithdraw ()
Returns true if the object's registration in the local registry can be successfully withdrawn, or does not exist.
C++: ilu_Boolean iluObject::ILUPing (ilu_Error* p_error)
Returns ilu_TRUE if the true object exists, and the process serving it can be contacted, otherwise ilu_FALSE. *p_error is set if some error occurred.
To generate C++ stubs from an ISL file, you use the
program c++-stubber.
Three files are generated from the
`.isl' file (the extension cpp
is used instead of cc
when running on Windows):
% c++-stubber foo.isl header file interface foo to ./foo.hh... code for interface foo to ./foo.cc... code for server stubs of interface foo to ./foo-server-stubs.cc... %
The option -renames renames-filename
may be used with c++-stubber
to specify particular C++ names for ISL types. See the following section
for more details.
It is sometimes necessary to have the C++ names of an ILU interface match some other naming scheme. A mechanism is provided to allow the programmer to specify the names of C++ language artifacts directly, and thus override the automatic ISL to C++ name mappings.
To do this, you place a set of synonyms for ISL names in a
renames-file, and invoke the c++-stubber
program with the switch -renames
,
specifying the name of the renames-file. The lines in the file are of the form
construct ISL-name C++-namewhere construct is one of
method
, exception
, type
, interface
,
or constant
; ISL-name is the name of the construct, expressed either
as the simple name, for interface names, the concatenation
interface-name.construct-name
for exceptions, types, and constants,
or interface-name.type-name.method-name
for methods;
and C++-name is the name the construct should have in the generated
C++ code. For example:
# change "Foo_T_R1" to plain "R1" type Foo.R1 R1 # change name of method "M1" to "Method1" method Foo.O1.M1 Method1
Lines beginning with the `hash' character `#' are treated as comment lines, and ignored, in the renames-file.
This feature of the c++-stubber
should be used as little and as carefully
as possible, as it can cause confusion for readers of the ISL interface,
in trying to follow the C++ code. It can also create name conflicts
between different modules, unless names are carefully chosen.
The ILU C++ runtime is prepared to be used in either a single-thread or a multi-threaded mode. Single-threaded is the default. To run multi-threaded, the application is responsible for picking a threading mechanism and making it accessible to ILU. Two calls must be made to make a threading mechanism available to ILU. One, iluServer::SetFork
, enables ILU to fork new threads. The other, ilu_SetLockTech
(from `ILUSRC/runtime/kernel/iluxport.h'), supplies various thread synchronization primitives. These calls must be done at startup time, before any ILU servers are created or surrogate objects imported.
For clients of an ILU module, it is only necessary to link with the `interface-name.o' file compiled from the `interface-name.cc' file generated for the interface or interfaces being used, and with the two libraries `ILUHOME/lib/libilu-c++.a' and `ILUHOME/lib/libilu.a' (in this order, as `libilu-c++.a' uses functions in `libilu.a').
For implementors of true classes, or servers, the code for the server-side stubs, in the file `interface-name-server-stubs.o', compiled from `interface-name-server-stubs.cc', should be included along with the other files and libraries.
ILU uses the static-object-with-constructor trick to effect per-compilation-unit startup code. In certain cases you'll want to ensure that a certain compilation unit's initialization is run before another's. While C++ defines no standard way to do this, most compilers work like this: compilation units are initialized (static object construtors run) in the order in which they are given to the link-editor. We want to hear about any exceptions to this rule.
ILU uses the imake
system from X11R? to produce
`Makefile's from `Imakefile's. For more details on this process,
section Using Imake with ILU.
Go to the previous, next section.