Go to the next section.
(with contributions from Doug Cutting, Frank Halasz, Dan Larner, and Denis Seversen)
(typeset 17 July 1996)
Copyright (C) 1993--1995 Xerox Corporation
All Rights Reserved.
This document describes version 2.0alpha8 of the Inter-Language Unification (ILU) system.
We gratefully acknowledge the contributions of many people, including our reviewers, alpha and beta testers, and regular users. The list includes (but is not limited to): Shridhar Acharya, Maria Perez Ayo, Mike Beasley, Erik Bennett, Dan Brotsky, David Brownell, Bruce Cameron, George Carrette, Philip Chou, Daniel W. Connolly, Antony Courtney, Doug Cutting, Mark Davidson, Larry Edelstein, Paul Everitt, Josef Fink, James Flagg, Steve Freeman, Mark Friedman, Jim Gettys, Gabriel Sanchez Gutierrez, Jun Hamano, Bruno Haible, Scott W. Hassan, Carl Hauser, Rob Head, Andrew Herbert, Angie Hinrichs, Ben Hurwitz, Roberto Invernici, Christian Jacobi, Swen Johnson, Gabor Karsai, Nobu Katayama, Sangkyun Kim, Ted Kim, Don Kimber, Steve Kirsch, Dan Larner, Carsten Malischewski, Larry Masinter, Fernando D. Mato Mira, Fazal Majid, Steven D. Majewski, Scott Minneman, Masashige Mizuyama, Curtis McKelvey, Chet Murthy, Farshad Nayeri, Les Niles, T. Owen O'Malley, Annrai O'Toole, Andreas Paepcke, Jan Pedersen, Karin Petersen, Steve Putz, George Robertson, Joerg Schreck, Ian Smith, Bridget Spitznagel, Peter Swain, Marvin Theimer, Lindsay Todd, P. B. Tune, Bill Tutt, Kevin Tyson, Bill van Melle, Guido van Rossum, Brent Welch, Jody Winston, Rick Yardumian.
ILU is primarily about interfaces between units of program structure; we call these units modules. The notion is that each module enscapsulates some logical part of a program, that has high `cohesiveness' internally, and low `coupling' to other parts of the program. ILU provides you with a way of writing down an object-oriented interface to the module; that is, a set of object types and other types, constants, and exceptions that another module would use to communicate with it. This interface can then be processed by various ILU tools to implement that communication.
ILU allows many different binding relationships between modules. The modules can be parts of one program instance, all written in the same language; they can be parts written in different languages, sharing runtime support in one memory image; they can be parts running in different program instances on different machines (on different sides of the planet). A module could even be a distributed system implemented by many program instances on many machines. A particular module might be part of several different program instances at the same time. ILU does all the translating and communicating necessary to use all these kinds of modules in a single program. It optimizes calls across module interfaces to involve only as much mechanism as necessary for the calling and called modules to interact. In particular, when the two modules are in the same memory image and use the same data representations, the calls are direct local procedure calls -- no stubs or other RPC mechanisms are involved. The notion of a `module' should not be confused with the independent concept of a program instance; by which we mean the combination of code and data running in one memory image. A UNIX process is (modulo the possibilities introduced by the ability, in some UNIX sytems, to share memory between processes) an example of a program instance.
Because ILU standardizes many of the issues involved in providing proper inter-module independence, such as memory management and error detection and recovery strategies, it can be used to build language-independent class libraries, collections of re-usable object definitions and implementations. Because one of the design goals of ILU was to use existing standards for various pieces, rather than inventing anything new, ILU can be used to implement ONC RPC or Xerox Courier services, or clients for existing ONC RPC or Xerox Courier services. ILU also includes an implementation of the Object Management Group's CORBA Internet Inter-Orb Protocol ( IIOP), and can be used to write CORBA services or clients, as well.
The approach used by ILU is one common to standard RPC systems such as Sun's ONC RPC, Xerox's Courier, and most implementations of OMG's CORBA. An interface is described once in some `language-neutral' interface specification language. Types and exceptions are described; exported functionality is specified by defining methods on object types. Tools are then run against the interface description to produce stubs for particular programming languages; these stubs can bind to, call, and be called from stubs generated from the same interface description for a different programming language. The stub code is then linked with the application code, some language-specific code containing any necessary ILU support for that programming language, and the ILU kernel library, which is code written in ANSI C. The following diagram illustrates the process:
Several modules may be linked together, for a standalone use. ILU stubs are generated in such a way that applications which link a caller and callee written in the same language directly together suffer no calling overhead. This makes ILU useful for defining interfaces between modules even in programs that do not use RPC.
Different modules of the program may be written in different programming languages. These can either be linked together in the same address space, if the runtimes of the different languages allow that, or they can be used to make separate network servers and clients. In the case of a network service, the memory layout for the program would be something like
ILU is object-oriented. By this, we mean that object types serve as the primary encapsulation mechanism in ILU. All functionality is exported from a module as methods that can be invoked on an instance of some object type, rather than as simple procedures. The object instance provides the context within which methods are executed. The object type system provides subtyping (`inheritance' of interfaces (ILU does not address object implementation)), to aid in structuring of interfaces.
With respect to a particular ILU object instance, a module is called the server if it implements the methods of that object, or a client if it calls, but does not implement, the methods of that object. One module can thus be a client of one object, and the server of another. An ILU object can be passed as a parameter to or result of a method call, and can be (in) the parameter to an exception. An object may be passed from its server to a client, from a client to its server, or between two clients, in any of the above three kinds of position. Unlike some RPC systems, there can be multiple ILU objects of the same type, even on one machine, even within one program instance.
For a given ILU object, there will, in general, be multiple language-specific objects; each is an "object" in one of the programming languages used in the system. One language-specific object, designated the true object, actually provides the implementation of the ILU object; it is thus part of the server module. The true object's methods are written by the programmer, not generated by ILU. The other language-specific objects are surrogate objects; their methods are actually RPC stubs (generated by ILU) that call on the true object. A surrogate object is used by a client module when the server module is in a different program instance or uses different data representations.
The object model specified here provides for multiple interface inheritance. It is intended that the subtype provide all the methods described by its supertypes, plus possibly other methods described directly in the subtype description. It is expected that in languages which support multiple-inheritance object models, that an ILU inheritance tree will be reflected in the language-specific inheritance tree. In a single-inheritance language, an ILU-specific multiple-(interface-)inheritance object system must be embedded.
In the ILU type system, the only subtyping questions that arise are between two object types. This is because ILU employs only those OOP features common to all languages supported.
Subtyping in ILU is based on structure and name; we include the names in the structure, and thus need only talk about structure. An object type declaration of the form defined later constructs a structure of the form
(OBJTYPE SINGLETON: singleton-protocol-info OPTIONAL:Boolean
COLLECTIBLE:Boolean
AUTHENTICATION: authentication-type SUPERTYPES: supertype-structure, ... METHODS: method-structure, ... LEVEL-BRANDS: (interface-name, interface-brand, type-name, type-brand))
Structure A is a subtype of structure B iff either (1) A and B are equal structures, or (2) one member of A's supertype-structures is a subtype of B.
Note that the level-brands include the interface name and (optional) brand, as well as the name and (optional) brand of the type being declared. Thus, two declarations of subtypes of the same type normally create distinct subtypes, because they would declare types of different names, or in interfaces with different names. When the interface name and the type name are the same, this does not cause a distinction, although other structural differences might. If the programmer wants to indicate that there's a semantic distinction, even though it doesn't otherwise show up in the structure, s/he can use different interface brands and/or different type brands. These distinctions can be made between declarations in different files, or between successive versions of a declaration in a file that's been edited.
Many existing RPC protocols and servers do not have the notion of multiple instances of a type co-existing at the same server, so cannot use the instance discrimination information passed in ILU procedure calls. To support the use of these protocols and servers, we introduce the notion of a singleton object type, of which there is only one instance (of each singleton type) at a kernel server. Note that because a single address space may support multiple kernel servers, this means that in a single address space, there may be multiple instances of the same singleton type. When a method is being called on an instance of a singleton type, no instance discrimination information is passed. Singleton types may not be subclassed.
To use (e.g., call the methods of) an ILU object, a client must first obtain a language-specific object for that ILU object. This can be done in one of two ways: (1) the client can call on a language-specific object of a different ILU object to return the object in question (or receive the object in a call made on the client, or in the parameter of an exception caught and handled by the client); or (2) certain standard facilities can be used to acquire a language-specific object given either addressing or naming information about the ILU object. The addressing information is called a string binding handle (SBH), and the ILU runtime library includes a procedure to acquire a language-specific object given a string binding handle for an ILU object (in strongly-typed languages, this procedure is typed to return an object of the base type common to all ILU objects in that language).
Every creation of a surrogate instance implies communication with the server module, and binding of the surrogate instance to the true instance. ILU may attempt to perform this communication when it is actually necessary, rather than immediately on surrogate instance creation.
The process of creating an instance may bootstrapped via a name
service, such as the PARC Name-and-Maintenance-Server
(NMS
), which allows servers to register instances on a
net-wide basis. A server registers a mapping from naming information to
a string binding handle. The client-side stubs for an interface include
a procedure that takes naming information, looks up the corresponding
string binding handle in the name service, and calls the above-mentioned
library routine to map the SBH to a language-specific object.
Alternatively, a client can do those steps itself, using an ILU
runtime library procedure to acquire a language-specific object for the
name service.
In ILU, there is a string-based representation for a reference to an object. That representation consists of a single string, called a string binding handle. ILU uses string binding handles when marshalling object references for RPC. ILU also allows applications to interconvert between objects and string binding handles. This is necessary when dealing with name services, and useful in other circumstances.
A string binding handle contains several different pieces of information:
The server ID, instance handle, and MSTID may each contain any ASCII character other than NUL. They are composed into the string binding handle according the the IETF rules for URLs, but the precise form of the URL is not specified here. (In versions of ILU before 2.0, string binding handles had a completely different syntax.)
The pair (server ID, instance handle)
are also
known as the
object ID (or
OID) of the object, because together they form
a universally unique ID for the object.
The contact info part contains one or more contact info sequences, each describing one particular way of communicating with the object's kernel server. Each contact info sequence consists of a series of fields. The first field is known as the protocol info, and names a particular RPC protocol, and any parameters that might influence the way in which this protocol would be used. Each of the succeeding fields specifies transport info, which defines a way of transforming or communicating data, and any parameters which might influence that transport method. There may be many sequences of contact info in any one string binding handle (but ILU currently ignores all but the first).
Some ILU object instances may have implementation
dependencies on private communication with other instances. For
example, imagine an object type time-share-system
, which
provides the method ListUsers()
, which returns a list of
"user" instances. Imagine that time-share-system
also
provides the method SetUserPriority(u : user, priority : integer)
.
We would like to be able to provide some assurance that
the user instance used as a parameter to SetUserPriority
is an
instance returned from a call to ListUsers
on the same instance
of a time-share-system
, because the way in which
SetUserPriority
is implemented relies on the user being a user
of that particular time-share-system
.
The ILU model provides the notion of a sibling object. Two instances are siblings if their methods are handled by the same kernel server. Instances that are non-discriminator parameters to methods may be specified in ISL as having to be siblings of the discriminator.
A simple form of garbage collection is defined for ILU objects. If an object type is tagged as being collectible, a server that implements objects of that type expects clients holding surrogate instances to register with it, passing an instance of a callback object. When a client finishes with the surrogate, the client unregisters itself. Thus the server may maintain a list of clients that hold surrogate instances. If no client is registered for an object, and the object has been dormant (had no methods called on it) for a period of time T1, the server may feel free to garbage collect the instance. T1 is determined by human concerns, not network performance: T1 is set long enough to allow useful debugging of a client.
To deal with possible failure of a client process, we introduce another time-out parameter. If an instance with registered clients has been dormant for a period of time T2, the server uses the callback instance associated with each client to see if the client still exists. If the client cannot be contacted for the callback, the server may remove it from the list of registered clients for that instance.
If a client calls a method on a surrogate instance of a true instance which
has been garbage-collected (typically because of partitioning), it will
receive the ilu.ProtocolError
exception, with detail code
ilu.NoSuchInstanceAtServer
.
ILU, when used to construct distributed systems, has no notion of "connections". That is, the called module has no pointer back to the caller, and no notion of how to do anything with the caller aside from returning a result message. Credentials passed in the request message can identify the caller, but not necessarily the location the call is made from. Protocols that need such information should pass it explicitly as an argument (an instance of a object type with methods defined on it) to the method.
This release of ILU includes an experimental simple binding/naming facility. It allows a module to publish an object, so that another module can import that object knowing only its object ID (as defined in section ILU Concepts). The interface to this facility is deliberately quite simple; one reason is to allow various implementations.
The interface consists of three operations:
Publish,
Withdraw, and
Lookup. Publish
takes one argument, an ILU object. Publish
returns a string that is needed to successfully invoke Withdraw
. Withdraw
undoes the effects of Publish
, and takes two arguments: (1) the object in question, and (2) the string returned from Publish
. In some langauge mappings, the string is not explicitly passed, but conveyed in the language mapping's representation of ILU objects. Lookup
takes two arguments: an object ID and a type the identified object should have. If the object with that ID is currently being published, and has the given type (among others), Lookup
returns that object.
The implementation shipped with this release of ILU can use either an ILU service, or a shared filesystem directory, to store information on the currently published objects. This choice must be specified at system configuration time. If the shared filesystem approach is used, this directory must be available by the same name, on all machines which wish to interoperate. The way in which clients interact with binding is the same, regardless of which approach is selected.
The simple program ilusbls
will list the currently registered objects. It may be invoked with an argument, in which case only those objects with string binding handles containing the argument as a substring will be listed.
If simple binding is to be done with shared files, a default directory is compiled into the ILU library. This directory may be explicitly specified at system configuration time with the --with-binding-dir=DIRECTORY
switch to configure
. (The compiled-in setting may also be overridden at run time, by setting the environment variable ILU_BINDING_DIRECTORY
to a different directory.) ILU creates a file in this directory for each published object. The name of the file is an 8-digit hex string, formed by taking the CRC-32 hash of the server ID and instance handle of the object. The file contains the string binding handle of the object and a random string, which serves as the `proof' that has to be provided when withdrawing a registration. Note that when using the shared file approach, the protection state of the directory must be such that programs calling Publish
can remove files and create new files, and programs calling Lookup
must be able to read files in the directory.
If an ILU service is used, the situation is a bit more flexible. The idea is that a program called ilusb
is run on some machine, and exports the binding service via a specified port. All clients have compiled-in knowledge as to which machine and port the binding service is running on, and they contact the service to perform Publish
, Withdraw
, and Lookup
calls. Each binding service is given a name, called a
binding realm, which is the name of the `space' for which it provides simple binding services. There may therefore be many instances of the ilusb
server running, even on a single machine, each one serving a different binding realm. It is often useful to establish multiple binding realms for different purposes. For instance, one might be used for everyday registration of services, another for testing, still another for experimenting.
To start the binding service, run the program ILUHOME/bin/ilusb
. It takes the following options:
-r REALM-NAME
-- this allows specification of the REALM-NAME which the server will serve. The default is the compiled-in realm name.
-h IP-ADDRESS
-- this allows overriding the compiled in IP address for the machine. This switch is mainly for multi-ported machines (machines which have two or more different IP addresses).
-p PORT
-- this allows overriding the compiled in PORT specification. This is the port on which the server listens for connections.
-f FILENAME
-- this allows you to specify the name of a file in which the server will store a backup of the various registrations currently active. If the server is restarted, and this file already exists, the server will read this file, and use the registrations in it as the current set of registrations. This file should be in a directory which can be read and written by the user identity under which the ilusb
program is running. The default is a file called `/tmp/ILUSimpleBindingService.REALM-NAME'.
-s
-- this option enables `protected' operation. This prevents active registrations from being overridden; Withdraw
must be called first, to remove the current registration, before a new registration for the same object can be made. This is useful in an environment which only wants to permit certain users to change certain registrations. However, without secure communications, this operation is not truly secure, and enabling it is often clumsy for casual use of the simple binding service. The default is unprotected operation.
By default, ILU programs use the compiled-in binding realm, host, and port. However, they can be directed to use a different combination of these three, by defining the environment variable ILU_BINDING_SERVICE
to a string of the form "REALM:HOST:PORT"
, before running the program. If you want only to override one or two of the compiled-in defaults, use a string with empty fields for the other parts. For example, if you just wanted to redirect to a particular host, you could use a string of the form ":foo.bar.company.com:"
, with empty strings for REALM and PORT.
The program ilusbls
will list all the currently registered objects. It takes an optional string argument. If the argument is specified, only objects which have that string in their string binding handles will be listed.
ILU uses the notion of an exception to signal errors between modules. An exception is a way of passing control outside the normal flow of control. It is typically used for handling of errors. The routine which detects the error signals an exception, which is caught by some error-handling mechanism. The exception type supported in ILU is a termination-model exception, in which the calling stack is unrolled back to the frame which defined the exception handler. Exceptions are signalled and caught using the native exception mechanisms for the servers and clients. A raised exception may carry a single parameter value, which is typed.
The type and exception model used by ILU is quite similar to that used by the Object Management Group's Common Object Request Broker Architecture (CORBA). We have in fact changed ILU in some ways to more closely match CORBA. Our tools will optionally parse the OMG's Interface Definition Language (OMG IDL) as well as ILU's ISL.
ILU also attempts to address issues that are already upon us, but are not addressed in CORBA 2.0: 64-bit architectures, UNICODE characters, a uniform way of indicating optional values, and garbage collection.
ILU provides two different interface definition languages, OMG IDL and ILU ISL to enhance portability of ILU modules. The OMG IDL subset understood by ILU is a strict subset of OMG IDL; this means that any ILU modules developed using OMG IDL interfaces should be interoperable with any other CORBA system. Any non-CORBA extensions may only be expressed in ILU ISL, so that any modules which use these extensions must use ILU ISL to express their interfaces, thereby underlining the fact that these modules are not CORBA-compliant. We feel that this dual-interface-language approach will tend to enhance both portability and CORBA-compliance of ILU modules.
ILU does not yet provide some of the features required by a full CORBA implementation. Notably it does not provide a Dynamic Invocation Interface, or implementations of either Interface Repository or Implementation Repository. It does not provide the Basic Object Adapter interface, either, but does provide an object adapter with most of the BOA's capabilities, except for those connected with the Interface Repository and/or Implementation Repository.
A number of concepts in CORBA that seem to require further
thought are not yet directly supported in ILU: the use of
#include
(ILU uses a more limited notion of "import");
the notion of using an IDL "interface" as both an object
type and a name space (this seems to be a "tramp idea" from the
language C++; in ILU the "interface" defines a
name space, and the object type defines a type); the notion that all BOA
objects are persistent (in ILU, the question of whether an
object is persistent is left up to that object's implementation); the
notion that type definitions can exist outside the scope of any module
or namespace (in ILU, all definitions occur in some interface).
Currently, there is no support in ILU for CORBA
context
s, the OMG IDL type any
, or the
OMG IDL type Object
. We feel that all three of these
notions tend to weaken interface descriptions.
Go to the next section.