Go to the previous, next section.

Using ILU with C++

Introduction

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.

Mapping ILU ISL to C++

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.

Names

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.

Types

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.

Sequence types

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.

Object types

Exceptions

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 _G::RaiseException ( RETAIN(Status *) status, GLOBAL(ilu_Exception) code, ...)

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

Constants are implemented with C++ #define statements.

Examples

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)

Using an ILU module from C++

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 *)} iluObject::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.

Implementing an ILU Module in C++

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.

Servers

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.

Event dispatching

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.

Publishing

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.

ILU API for C++

Generating ILU stubs for C++

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):

Typically, clients of a module never have a need for the `interface-name-server-stubs.cc' file.

% 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.

Tailoring C++ Names

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++-name
where 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.

Threading

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.

Other ILU Considerations For C++

Libraries and Linking

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.

Initialization order

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.

Makefiles

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.