Variable Procedure Calls in Free-Format RPG
March 31, 2010 Jon Paris
Note: The code accompanying this article is available for download here.
In my previous tip, I detailed the prototyping technique required to enable program calls in free-format RPG to use a variable to control the target program. These days, most modern RPGers make extensive use of subprocedures, so can the same techniques be applied to procedure calls?
The simple answer is “yes,” but the technique is slightly more complex than that used for program calls. The reason for this is unlike program calls, procedure calls normally have to be resolved at program creation time, i.e., during the binding step. There are ways around this, one of which we will touch on later in this tip, but for now let’s work on the basics.
The “secret ingredient” involved in this process is a procedure pointer. Take a look at the prototype for MyProc at (A) in the code extract below.
// Prototype for dynamic procedure call (A) D MyProc Pr ExtProc(procPtr) // Prototypes for procedures to actually be called D Proc1 Pr D Proc2 Pr D procList DS (B) D ptrToProc1 * ProcPtr Inz(%PAddr(Proc1)) (C) D ptrToProc2 * ProcPtr Inz(%PAddr('PROC2')) D ptrToNothing * ProcPtr Inz // Redefine pointers as an array (D) D ptrArray * Overlay(procList) D ProcPtr Dim(3) D procPtr S * ProcPtr Inz D procNumber S 3I 0 D exit C 99 // Status codes for Monitor/On-Error tests D arrayIndexErr C 121 D callFail C 202 /Free // Ask for identifier of first procedure to call Dsply ('Enter procedure to call (1 - 3 or 99 to exit)') ' ' procNumber; DoU (procNumber = exit); Monitor; (E) procPtr = ptrArray(procNumber); MyProc(); On-Error arrayIndexErr; Dsply ('Value of ' + %Char(procNumber) + ' Not in range 1 to ' + %Char(%Elem(ptrArray))); On-Error callFail; Dsply ('Error detected when calling procedure ' + %Char(procNumber)); EndMon;
Notice that, as with the prototype for the dynamic program calls, the parameter to the keyword does not have quotes around it. As a result the compiler will treat the name as identifying a procedure pointer and will use the content of pointer to supply the call target.
Simply defining the procedure pointer, of course, is not enough. We need to be able to load it with a value that points to the specific procedure that we want to call. RPG’s Built-in-function (BIF) %PADDR is used to obtain a pointer to a specified procedure. It can either reference a prototype name as at (B), or directly specify the procedure name as at (C). Note that when specifying the actual procedure name it is vital that you use the correct case for the name. Using ‘Proc2’ would not have worked. Using this approach, all of the procedure pointers must be able to be resolved at program creation time just as if we were directly invoking them via their individual prototypes.
In my example I have defined the required procedure pointers in the data structure procList and then remapped them as array ptrArray (D). This allowed me to test the process by simply entering a number from 1 to 3. Notice that no procedure address was assigned to the third pointer so that I could test for a call failure.
Before the call can be made, the required procedure pointer must be loaded. This takes place at (E), and is immediately followed by the procedure call itself. In this example there are two possible error conditions that could occur within the MONITOR block. The array index entered could be out of range, or we may have used an invalid procedure pointer–in my case the third entry in the array.
In my example program I defined the subprocedures within the program itself, but the technique would be no different if they were within a service program or a separate module object.
My example procedures do not use parameters, but it is important to understand that using this technique will normally rely on all the procedures involved having a common parameter structure. There are techniques that can avoid this requirement, but it is certainly a good idea when designing such a dynamic system to start from that assumption.
Can we make this technique even more flexible? Yes we can. The approach that I am about to describe uses a regular program (*PGM) to provide the functionality of a service program (*SRVPGM), but with one important exception. By incorporating the technique described in the previous tip, we can dynamically call this “service program” and therefore, by changing the name of the program we call, change the set of procedures that we activate.
There are a number of potential uses for such techniques, for example I have used it as part of a dynamically managed authority-based menu system. For now though I’ll simply focus on the technique and leave it to your imagination as to the uses you might put it to.
Please note that the sample programs I have supplied to demonstrate this technique do not have a direct practical application. They are simply intended as a “geek’s playground” for those of you who like to explore ways of extending your programming toolkit. That said, let’s start by looking at some code extracts from the “service program” itself. Much of this should look familiar, as it is derived from the earlier example.
At (F) you can see that the program’s procedure interface defines a parameter that is specified as being a clone of the procList data structure. The actual list of initialized procedure pointers can be seen at (G). Basically, it is the same code as we saw in the previous example.
(F) D ServProcs PI D procsAvail LikeDS(procList) // Prototypes for procedures D Proc1 Pr D Proc2 Pr (G) D procList DS D ptrToProc1 * ProcPtr Inz(%PAddr(Proc1)) D ptrToProc2 * ProcPtr Inz(%PAddr('PROC2')) D ptrToNothing * ProcPtr Inz /Free // Set list of available procedures and return them to caller (H) procsAvail = procList; Return;
The entire main line logic of the program consists of just the two lines beginning at (H). It copies the program’s list of procedure pointers to the caller’s parameter and then returns to the caller. Notice that we do not want to set on LR; we want the program to remain active. If this were a real working program, I would probably have included a count of the number of active entries in the list in the data structure.
In many cases it is also desirable to include some form of ID for each of the procedures so that the caller does not have to be aware of the sequence in which the procedures are defined but can seek out the procedure it needs by using its ID. I have used this approach in programs designed to make calculations based on formulas in text strings.
Having looked at the “service program,” let’s now take a quick look at how it is initially invoked by the program that wants to use its services. As soon as this initial program call has been made, the procedure pointers are available so that procedure calls based on those pointers can now be made. Of course these pointers can be passed from program to program so that the procedures can be called from anywhere within the activation group.
At (I) you can see the definition for the “service program” prototype. In real life this would be incorporated via a /COPY. Notice that the procList data structure incorporates the array definition for ptrArray. The data structure, and therefore the array of procedure pointers, is populated by the call at (J).
// Prototype for dynamic call to "Service Program" (I) D ServProgram Pr ExtPgm(servProg) D procList LikeDS(procList) ... D procList DS D ptrToProc1 * ProcPtr D ptrToProc2 * ProcPtr D ptrToNothing * ProcPtr // Redefine pointers as an array D ptrArray * Overlay(procList) D ProcPtr Dim(3) ... // Call "Service Program" to obtain list of procedures (J) ServProgram(procList); ...
The balance of the code is the same as in my first example so I will not comment further on it here.
The full source code used in these examples is available here. I encourage you to play with them. If you have any questions about the techniques discussed here please email me through Ted Holt via the IT Jungle Contact page.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summit conference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.