Developers typically want to modify, enhance or correct parts of high-availability applications without having to bring the entire system to a halt, re-build and re-boot. Using current operating systems, new code can be loaded on demand through dynamically loaded/linked libraries (DLLs) on Microsoft s Windows, or through shared libraries on Unix-based operating systems.
In practice, however, the protocol for invoking routines in a DLL or shared library is very low level and is therefore not ideal for mission-critical applications. In contrast, object-oriented (OO) programming languages offer a much more attractive approach to the problem by using the more robust, high-level protocol of dynamic dispatching to invoke operations in dynamically loaded plug-ins. Plug-ins contain instances of new subclasses in the inheritance tree, that is, instances of subclasses inherited from the superclasses known to the application. A plug-in is thus type-safe, robust and enriches the functionality of the original application without requiring execution to stop.
Ada, originally developed for embedded and real-time applications, was in fact the first internationally standardized OO language in 1995. Ada is widely used in high-integrity applications with very static application characteristics, but the language can also be used in the more dynamic context of high availability. Ada s main programs can use the DLL or shared object facility to load the plug-ins, and can then use dynamic dispatching at the application level to transparently invoke routines provided by the plug-ins.
A simplistic simulation of the instruments on an automotive dashboard illustrates how new functionality can be added at run-time without stopping the execution of the application. The system is developed in a way that makes it completely and conveniently portable to operating systems as different as Windows, Linux, Unix or VMS.
The instruments on the dashboard are coded as separate plug-ins, independent of the main program. They are loaded at run-time such that the simulation can take newly created instruments into account without having to be restarted. Although stand-alone, these plug-ins share objects and code in the original program. They become part of the original program through dynamic linking (Figure 1).
Abstract Base Class
The common code base is shared by the application. The plug-ins and all foundation classes are declared in this part of the application. The most important of these foundation classes is Instrument, which defines the abstract base class for the subclasses defined by the plug-ins (Figure 2). In Ada, a class is represented by the combination of individual building blocks rather than by the single class construct of some object-oriented languages. Specifically, a class is represented in Ada by a type, declared within a package, with subprograms that manipulate objects of that type. The package provides the encapsulation and the subprograms provide the operations. These building blocks work together as a whole, however. Only those subprograms that are declared in the package with the type and that manipulate objects of the type are inherited by any subclasses.
The package InDash illustrates these concepts. Without going into the full details, the package first exports a number of declarations to client code, including the type Instrument and operations that manipulate objects of that type via parameters. There follows a private part that encapsulates the type s representation; as a result client code can use the type to create instances but cannot access the internal implementation of those instances. This private part of the package in Code Segment 1 corresponds to the protected part of a C++ class construct.
Additionally, any instrument instance (including instances of subclasses, which are of course also instruments) can display its current state and can have that state updated. State updates are based upon the passage of some number of milliseconds.
For the sake of comparison, consider the roughly corresponding C++ class declaration in Code Segment 2.
We see that the Ada type corresponds partly to the C++ class construct and that the Ada package construct corresponds partly to a C++ namespace, except that the package also provides encapsulation of the type s implementation via the package private part. The procedures and functions that manipulate values of the type correspond to the C++ member functions. Ada routines manipulate these values via parameters, using either of two possible syntactic forms for the calls. For example, given an object named Display, of type Any_Instrument, we could call the Set_Name procedure using either the traditional functional notation:
Set_Name (Display, Tach ); or the popular distinguished receiver syntax:
Display.Set_Name ( Tach );
The InDash package would also have a textually separate package body that implements the operations. This package body corresponds to the private part of a C++ class. We omit the package body here for the sake of brevity.
Plug-in Subclass Instance Registry
The code base also includes the single object representing all the instruments currently on the dashboard. Essentially, this object is a registry designating the instrument objects created by the plug-ins during plug-in loading. For example, the following is a fragment of another package, named Dash_Board, that contains the declaration of this registry object: package Instruments is new Ada.Containers.Vectors (Positive, InDash.Any_Instrument);
Registry : Instruments.Vector;
Thus the registry is an object of the type (i.e., class) Vector provided by the instantiation of the standard generic package Ada.Containers.Vectors. A generic is simply a template for a program unit ”in this case a template for a package ”that can be tailored for specific types and other properties. In C++ generics are in fact referred to as templates, and the Ada Vectors generic is conceptually similar to a template provided by the C++ Standard Template Library (STL). In this case, the Vectors generic is tailored to contain access values (essentially pointers) that designate any kind of instrument. In effect this kind of vector contains pointer to base class values, to use C++ terminology, because such a pointer can designate any class in a set of classes related directly or indirectly by inheritance. This effect is achieved because the designated type is class-wide, as indicated by the syntax of the access type declaration within package InDash: type Any_Instrument is access all Instrument Class;
The designated type named Instrument Class represents this set of classes (types) related by inheritance, directly or indirectly, from type Instrument. The type Any_Instrument is therefore the pointer-to-base class type that can designate any subclass of Instrument. The registry contains these pointer values. Plug-ins call the Dash_Board.Register procedure for the objects they allocate locally within themselves, passing just such an access value as the parameter: procedure Register (Device : Any_Instrument) is begin Registry.Append (Device); end Register;
The instrument passed to procedure Register is simply appended to the Registry object. You should note that the single Registry object is thus shared between the main program and all the plug-ins (Figure 1).
The main program iteratively discovers and loads any available plug-ins, displays the current dashboard and updates the states of the instruments according to how much time has elapsed. Any new plug-ins are discovered and loaded on each iteration so the number of instruments appearing in the dashboard can increase dynamically. Our discovery approach is uncontrolled and is, therefore, not realistic, but suffices for this demonstration. Specifically the main program, via procedure Discover_Plugins, searches all the immediate subdirectories for DLLs and loads any new ones it finds. This behavior continues indefinitely, as the source code fragment in Code Segment 3 illustrates. The delay statement causes the main program to suspend ( sleep ) for the number of seconds corresponding to the number of milliseconds the user entered prior to the loop. We explain the other two routines calls next.
Dynamic Dispatching to Instances within Plug-ins
Procedures Dash_Board.Display and Dash_Board.Update, called by the main program, contain high-level dispatching calls from the main program to any plug-ins that have been discovered. Both procedures iterate over the internal Registry of objects maintained by package Dash_Board and make dispatching calls to the operations defined by the subclass instances declared within the plug-ins. The body of procedure Display follows in Code Segment 4.
The object C is of a type named Cursor (from package Instruments) that supports iteration over a composite data structure, in this case over the Instruments.Vector object named Registry. Initialization of the cursor sets it to indicate the first element contained within the Registry vector, if any such element is present. The loop will execute as long as elements remain in the Registry vector to be visited. The function Element returns the value at the specified cursor location, in this case a pointer value designating some kind of instrument object. The Display operation is applied to this designated object ”the dereference is implicit ”thereby dispatching to a Display routine specific to the type of designated instrument. These calls are dynamically dispatched at run-time because the specific types of designated objects are not known until the calls occur. This fact highlights the point that dynamic dispatching in Ada is a property of calls, not operations. That is why we do not need the C++ virtual reserved word preceding the Ada method declarations in the package. Any of those methods can be a dispatching target.
The call therefore dispatches from the main program to the operation defined within the plug-in, and it does so in a type-safe manner because the operation s signature (the parameter and result type profile) is specified by an interface common to both the plug-in and the main program. The fact that the object is of a type not necessarily in existence when the main program was compiled is completely transparent to both the programmer and the client main program. Indeed, transparent extension is the desired effect in this example. The Update procedure works in exactly the same manner but dispatches to the Update operation for each of the designated instrument instances.
Plug-ins are independent of the application in terms of dependencies and can be developed when the application is running. Each plug-in defines one or more subclasses of the base instrument class, allocates instances of those subclasses and registers them with the dashboard when the main program discovers and loads the corresponding DLL. The declaration of the speedometer plug-in is in Code Segment 5.
The type Digital_Speedometer is thus a subclass of Instrument, with a representation that is hidden from clients. This is a typical abstract data type decla
ration using the normal information hiding techniques of the language. All the attributes and operations of the base class Instrument are inherited but note that the Display and Update operations are overridden to change their behaviors. An additional hidden floating-point component named Value is added to the inherited component.
Running the Application
Now that the infrastructure is in place, the main program can be built and can be extended with individual plug-ins while it executes. Initially no plug-ins will exist but the application can be launched. No instruments will be displayed but the main program will iterate nonetheless. Plug-ins can be built separately in another window while the main program is running and they will be discovered on successive iterations as they are built. The main program will discover new plug-ins and dispatch to the corresponding Display and Update the routines for the individual objects within the plug-ins. Details for how the plug-ins are compiled and linked are ignored here but the source code provided includes the full mechanisms required. When all the plug-ins are present the display for one iteration looks like Code Segment 6. The instruments states are updated as time passes and the displayed values change accordingly.
As the example has shown, object-oriented programming languages offer developers a safe, robust method for extending the functionality of high-availability applications without first stopping execution. By making additional instances of new subclasses available, plug-ins allow dynamic dispatching to replace a lower-level, comparatively unsafe mechanism. Although this kind of run-time extension is not new, dynamic dispatching in terms of abstract base classes provides a much more robust mechanism. The reader may be surprised that Ada, known for applications with more static characteristics, can take advantage of this capability as well, especially the ability to dynamically link extensions.