DCL Call Forward and Callback
It is the rare DCL command procedure that is an island unto itself. Rather, it is far more common for DCL procedures to be parts of larger ensembles; collections of pre-existing and purpose-developed tools working in concert to achieve a common purpose. Calls to lower-level DCL procedures and callbacks to user-provided processing are integral to assembling these ensembles which by their very nature leverage development and maintenance resources.
Ensemble applications are far more efficient than writing each application independently. Reusing code is not merely a question of cost. Using pre-existing components also ensures that core processing is performed consistently. By the same reasoning, invoking an external procedure is far safer than including the procedure textually within each DCL file.
In yet other cases, a general processing framework must be extended for idiosyncratic requirements. Different extensions are often mutually incompatible. Callbacks efficiently allow basic frameworks to be extended in ways that are otherwise mutually incompatible; effectively implementing a structure not unlike a base class in an object oriented language. DCL can implement both conventional calls to utility routines; as well as callbacks, those user-provided supplements whose precise identity is not known in advance.
Implementing both of these techniques in DCL is straightforward. A set of small examples of how this can be accomplished is available for download at http://www.rlgsc.com/demonstrations/openvms-callback_v1.zip.
Calls to known routines would seem simple enough, but there are often many ways for problems to arise. DCL procedures often use a variety of techniques to locate other procedures, executables, and internal data files. Some of these approaches have negative side effects. For example, many procedures explicitly refer to their own directory (e.g., DISK$USERS:[TOOLS.FRED]). Relocating such a procedure to a different directory can be catastrophically disruptive, requiring large numbers of textual changes. However, DCL includes facilities that drastically reduce, if not totally eliminate, dangers of that type.
Another common technique is to reference the locations of DCL command files using logical names. While effective, it frequently requires a large, ever- expanding collection of logical names. This clutters logical name tables and complicates maintenance.
In many cases, it is more efficient to refer to the directory containing the file using the DCL lexical function F$ENVIRONMENT. F$ENVIRONMENT("PROCEDURE") retrieves the fully qualified filename of the currently executing DCL procedure. Referring to files contained in the same directory as the current procedure is straightforward, using a combination of F$ENVIRONMENT and F$PARSE in concert (F$PARSE was discussed in a previous installment, Filename Alchemy - F$PARSE Filename Defaulting). In the downloadable examples, OPENVMS-CALLBACK-EXAMPLE1.COM uses this technique to determine the location of OPENVMS-CALLBACK-EXAMPLE2.COM.
This technique is preferable to using a logical name to identify the location of an executing command file. F$PARSE can then be used to combine a known partial filename (e.g., SUBPROCEDURE) into a fully qualified filename. This is syntax with some power. Whether this power should be left to the user is a design and implementation judgment. By leaving out the file type (e.g., SUBPROCEDURE instead of SUBPROCEDURE.COM), we allow for the possibility of overriding the filename using a logical name. This possibility may be desirable for testing, yet may represent a hazard in production use. There is no definite right or wrong, but the difference should be understood. Thus, once the externally defined entry point has been accessed, references based on the name of the executing procedure are self-referential; there is no need to use a logical name to locate files stored with the command procedure. This has several advantages:
Callbacks are a complementary approach. Designing utilities to deal with all possible contingencies is extraordinarily difficult. It is difficult enough when solely considering the present; achieving foresight is far more difficult. When future possibilities are taken into account, the challenges become all but impossible.
However, there is an effective answer. Rather than design utilities for all possible applications, it is both far more effective and far less complex to provide a mechanism for extending functionality. Callbacks are a long- standing design pattern a technique precisely tailored to this problem: extending utilities beyond their basic capabilities. Default functionality may be enabled by using a null callback (e.g., a callback that always returns success or failure, as appropriate). More elaborate callbacks can provide any supplemental processing required, extending a utility's functionality almost without limit. Rather than solving a particular problem, a utility with provisions for callback becomes a framework for addressing entire classes of problems.
Callbacks are not limited to compiled or interpretive languages. Procedures written in DCL and other scripting languages can also implement callbacks, so long as there is a method for passing the name of a procedure to be invoked at an appropriate point in the future.
There are multiple methods of implementing DCL callbacks. The least flexible method is to use the OpenVMS logical name facility to provide a search path. This approach should be familiar to all OpenVMS system managers. It is used in OpenVMS as the basis for STARTUP processing. The SYS$STARTUP logical name is a search list that implements this hierarchy. Node specific versions of files are placed in the appropriate node-specific (actually, boot-root specific) directory, where they will be executed in preference to cluster common versions (those stored in the SYS$COMMON directory tree). In effect, the cluster common directory provides a default callback which can be overridden on a node-by-node basis.
While powerful, the names of these callbacks are fixed. This diminishes the power of the mechanism. Rather than the fixed structure of specific named functions, it is far more powerful to supply the name of a function callback for specific processing. This is a standard technique implemented in most programming languages. In the context of a DCL procedure, implementing this technique requires that the filename of the callback be supplied to the invoked utility.
OPENVMS-CALLBACK-EXAMPLE1.COM also illustrates passing a named callback into a procedure. Previously, I described how OPENVMS-CALLBACK-EXAMPLE1.COM invokes OPENVMS-CALLBACK-EXAMPLE2.COM from within its own residence directory. In addition, OPENVMS-CALLBACK-EXAMPLE1.COM also accepts a parameter, the name of a DCL procedure to be invoked during processing. By default, the location of this callback is within the present default directory. Since the implementation uses the F$PARSE lexical function, the location of the file can be overridden using normal OpenVMS conventions.
The example program has also been written to accept a null callback. At present, the example outputs an error message in this case; it could just as easily have skipped this particular callback.
From these admittedly simple examples, it is straightforward to see how better implementation of external DCL calls, whether calling into defined utility procedures or callbacks, implementing processing extensions is a powerful technique that can increase flexibility and decrease duplicative errors and repetitive maintenance.