Object oriented problems that cannot be solved by passive objects

 

What is the relationship between active objects and processes ? Do we need active objects if we have processes ? In the following, I will show that there are problems that are close to problems nicely solved by usual objects, but that cannot be solved without slightly extending the notion of object. Doing this, I will introduce our own idea of active objects.

A C++ object : ifstream fileDescr

The object fileDescr of class ifstream is quite a standard object, with methods such as open(...), getline(...), close(...), etc. It is usually used with the operator >>, but this is unimportant here. This object is passive, even if it is in some way connected to an active device (the disk).

The C++ cin object :

The cin object is similar to the previous one. However, it has a new characteristic : this object can postpone the execution of method getline for some time, until data are available. Note that the cout does not make problem, because it can (usually) be executed without delay. It would be nice to preserve the symmetry between input and output.

Graphical objects

A text field in a window on a screen can easily be created as an object. It would thus be very appealing to have the possibility to call a method getline() to retrieve the data typed in on the keyboard, like for cin, and like for an output onto the terminal window. Similarly, after a button has been instantiated, it would be nice to call something like Button.Pressed(), and have method Pressed executed only when the user has clicked the button icon. This method would not need any parameter, just the fact that it is executed is sufficient to signal its activation.

TCP sockets

I would also very much like to see TCP sockets defined as objects, with two ends, one in each interconnected computers, and to be capable of getting data like with the cin command.

CORBA objects

A CORBA object is accessed through a stub, which can be seen as a local replication of the remote object offering the same interface, but forwarding the parameters to the real object and reassembling the results the latter returns. It is often useful to let the client continue its local activity in parallel with the exchange of data between the stub and the remote object (deferred call). Again this is easily implemented if some activity can run inside the object, in parallel with the activity of the other objects and the client ( more details see). As the client is only interested in the interface, not in the internal protocol that handles the parameter exchange, it would be disturbing for him/her to see the replication of an object represented by a process !

A watch

A nice example of an active object is a watch. A watch can be defined as an object with some operations like readTime(...), setTime(...), etc. However, if it is defined as a usual (passive) object, the program must call it periodically to update it, much like those watches without motor that are offered to small children. As the system does not know the watch interface, this call cannot be realized automatically in a proper way. The watch could be created as a process, but the user is not concerned by the internal activity of the watch, it just wants to read and set time. The user should have the possibility to instantiate a watch like any object and call its methods like the ones of a usual object. Such an object can easily be done if we can start a method running on a thread attached to the object at the instantiation of the watch, and have this method loop on a delay and the statements updating time.

Event driven programming

The programs built around an event loop are not compatible with the simple approach advocated here. Actually we can even say that an event loop program is the inversion of an active object oriented program. The main event loop awaits and dispatches events issued from the peripheral devices (sockets, buttons, keyboard...) to functions containing the code of the program, whereas with our proposal the program calls the peripheral devices defined as objects. In order to use an event loop, one has to split a program into as many functions as there are event waiting locations. Although this approach is broadly used, it completely breaks up the structure of the programs.

Parallel waiting

It may happen that a user enters a text in a field, and then either hits the return key or a button to indicate the end of its entry, or that data arrive either from one of several TCP sockets or from a terminal window. This situation can easily be solved with a statement that can await either one of two or n calls in parallel, something like Ada’s select, but with the possibility to have calls as well as accept in the select.

As soon as one of the methods defined in the select that has been entered by the program is ready, it is executed. Obviously, the execution of the selected method should not begin before the called object has indicated that the method can be executed in one strike. The select statement must be suspended BEFORE the execution of any method.

JAVA’s active objects

The active objects looked for above can (almost) be implemented in Java. A Java active object can be instantiated (almost like a usual object ). It can run an internal method that carries out the various activities of the device. However, there is no select in Java, and it cannot easily be simulated with Java’s notify and wait statements. The fact that in Java, the waiting can only be done inside the object, a feature derived from the Hoare monitor, prevents the simultaneous waiting for several methods execution. On the other hand, the protection of the methods guarantied by the keyword synchronized is useful, because we may now have parallel threads that call the methods of the same object simultaneously, which may lead to reentrancy problems.

Ada objects

Ada’s processes are orthogonal to objects. A TCP socket, for example, must be a process. It cannot be handled as the C++ ifstream or cin object. Its select cannot directly be used to control object calls.

sC++

We have developed an extension to C++ that includes a concept of active object with the characteristics suitable for implementing the objects described above. These objects are declared, instantiated, called, deleted, referenced, and inherited exactly like the passive objects. They have a special method, called the body, defined much like a constructor/destructor that runs on a thread attach to the object. This body can decide when a method may be called (by means of an accept statement) by the main, processes or the other object bodies.

Modeling

The approach we have taken is supported by a solid theoretical background. The call of a method can be described with a CSP or CCS rendezvous. Thus our language has the same modeling properties as SDL, Promela or LOTOS, for example. We have developed a random walker that can be used to check big programs at random, without even recompiling it. We have started the development of a model checker that will be capable of verifying if a simple program has properties defined with temporal logic.

Availability

We have developed libraries that define the device objects defined above. In particular, we have encapsulated the Motif library, a well as the sockets, and realized a CORBA library.

This approach has allowed us to develope complex applications such as a debugger, an HTTP server, a distributed architecture editor. Developers outside our laboratory have developed modular and reliable distributed applications very quickly. They are convinced that they would have spent much more time to develop their application, wouldn’t they have sC++ at their hand.

A more complete description of our approach can be found in the research paper of the September issue of the IEEE Computer (pp 65-72). A package containing the compiler and the libraries is freely available on our web site. The package is also distributed by Redhat.