Invocation Semantics as First Class Abstractions

The invocation semantics of common object oriented environments are fixed. Either the programmer is forced to use the only available semantics, or he can choose between a small fixed-sized set of semantics. Our invocation abstractions offer an open way to create arbitrary many new semantics. The library consists of two parts. Both of them are presented in this section. The first part offers a meta-programming interface. It allows to browse class interfaces, as well as to view and to change their invocation properties. The second part allows the construction of new invocation abstractions either by writing new ones, or by combining existing abstractions.

The first part offers a view on the capabilities of classes relevant for the invocation semantics (see reduced interface below). The procedure GetClass returns a Class object for the passed instance. This object contains all information on all methods (including inherited ones) with all information relevant in order to change the invocation semantics. One may now scan this information or change it according to the current necessities.

  • Set a parameter to shallow or deep copy mode
    This setting influences the stub and skeleton code generation. It defines how the parameter is marshalled (this setting only affects parameter of a pointer type). In deep copy mode the parameter is copied (possibly recursively) onto the marshalled stream. The amount of marshalled data depends on the size of the actual passed data structure. In shallow copy mode, an object identifier (OID) is written to the marshalled stream. The invocation semantics do not generate OIDs. They have to be supplied by the current application.
  • Set the return value to shallow or deep copy mode
    This setting defines the handling of the return value. The semantics are the same as described above for parameters, i.e. one can choose, whether the return value is deep or shallow copied. This mainly is of importance when using the invocation semantics in a distributed environment. Either the returned object is copied to the calling host, or the calling host receives a stub object and later method invocations are forwarded to the actual object.
  • Change the invocation semantics
    Finally, with this setting one can set the invocation semantics individually for every method (see section 3.2). The module Invocations actually implements no concrete invocation semantics. It just offers the basic methodology to define invocation semantics for individual objects.

Building and Using Invocation Abstractions

Building new invocation semantics or combining existing ones is quite simple using the abstractions of Invocations. The library itself implements no concrete implementation, but only the abstract base classes. Invocation is the base class of all invocation abstractions.

	Invocation = POINTER TO InvocationDesc;
	InvocationDesc = RECORD
		PROCEDURE (inv: Invocation) Invoke (obj: SYSTEM.PTR; id: LONGINT; s: Linearizers.Stream): Linearizers.Stream;
	END;

It defines only one method. This method Invoke has to be implemented by all concrete sub-classes. Whenever a method is invoked, which uses this invocation abstraction, the method Invoke is called either directly by the stub code, or indirectly by another abstraction. When called, Invoke receives three parameters. obj is the receiver, id identifies the called method, and s is a stream containing the marshalled parameters. An actual implementation on this level is not possible. Whatever the idea of a concrete subclass is, after the actual method has been invoked, the skeleton code returns the marshalled output (return value and output parameters) in another stream. This stream has to be returned to the caller.

	PROCEDURE (inv: MyInvocation) Invoke (obj: SYSTEM.PTR; id: LONGINT; s: Linearizers.Stream): Linearizers.Stream;
	BEGIN
		DoSomething(obj, id, s);
		result := InvokeMethod(obj, id, s);
		DoSomething(obj, id, s, result);
		RETURN result
	END Invoke;

One extension of Invocation is defined. InvocationFilter allows decorating other invocation abstraction using the decorator pattern.

Just-in-Time stub generation

With the library describedabove, one can put together the desired semantics and parameter passing modes. Tu put it to work one has to generate code that intercepts ordinary method invocations. This is done by cloning the actual object. Methods invoked on the actual object are still handled as before. Methods invoked on the clone object (stub object) are handled, as specified in the meta-class-information, by the generated stub code. It has the same interface as the actual method and replaces it transparently. It marshalls the parameter and activates the appropriate invocation semantic. Simultaneously, the skeleton code is generated. The skeleton is the counterpart to the stub. It unmarshalls the parameters and calls the actual method. These code pieces are generated with help of the module Objects. It offers the necessary facilities to generate stub and skeleton code as well as the abstraction DirectInvocation, which invokes a method as done when no interception is used.

One of the main goals was - from the beginning - that the delay, introduced by the increased flexibility of arbitrary invocation abstractions, is kept as small as possible. That means, that the delay is constant regarding to the number of managed objects and the number of different semantics. This is achieved by introducing an array for the active invocation semantics. At run-time, each newly defined semantic is assigned a slot within this array, i.e. it receives a unique number. As the stub code is generated but afterwards, its possible to use this knowledge and access the correct semantics directly through an arrays access. As this value - at compilation time - is a constant its even possible for the compiler to calculate the offset. This reduces the actual overhead on the client side to a neglectable size.

Transport layer

What is missing in our scheme so far is the transport layer. It's the means used to transport the information from the client to the server. This part is replaceable and depends only on the chosen semantics. In a distributed environment the transport layer would actually send the data to another host. If only local decoration is used its the code piece which connects the stub object with the actual object. Whenever this layer transported another invocation to the server it has to look for the correct server-side semantics. There are two possibilities on how this lookup could be done.

The first possibility uses a more or less advanced data structure to lookup the necessary semantics. The invocation supplies the receiver object, as well as a method identifier. With the help of the meta-class information one can decided on the semantics. The complexity of this data structure decides on the amount of time spent for the lookup. The prototype implementation uses this approach. It uses simple linear lists, which imposes a linear dependency of the lookup time to the number of classes and the complexity of the called class.

A second possibility would be to supply the client with information about the location of the server semantics. The stub code would have to be modified to know both the client-side and server-side semantic locations. The server-side location would have to be forwarded to the transport layer. This layer would need no lookup for the desired semantic anymore, which makes this alternative's overhead constant. Unfortunately, this approach has two drawbacks. First, each invocation abstraction needs an additional parameter. Second, each method invocation's data increases by two bytes, which have to be transported from the client to the server.

Further Information