A lot of RPG programmers are under the misconception that the CALLP operation means Call Procedure. That is because...
most of them come across it when they start using subprocedures. But CALLP means Call a Prototyped Procedure or Program, and it can be used in place of the CALL operation, as well as CALLB.
I would like to discuss the benefits of prototyping dynamic calls. No mention of subprocedures or ILE.
What's wrong with CALL and PARM?
In RPG all parameters are passed by reference. That means a pointer to the parameter is passed, not the actual value of the parameter. That, in turn, means both the passed and receiving parameter fields share the same memory location, and that is where the potential problem lies.
- Send an e-mail to an admin when a job gets a status of *MSGW
- Don't proliferate old, bad code
Figure 1 shows the code of a calling program. It calls PGMB passing Parm1, a 10 character field, as a parameter.
D TestParm DS D Parm1 10 Inz('XXXXXXXXXX') D Parm2 10 Inz('YYYYYYYYYY') C Call 'PGMB' C Parm Parm1
Figure 1: A program call with a parameter field in a data structure
Figure 2 shows the code of the called program. It has an incorrect length of 15 for Parm1.
Will the compiler tell us that it is invalid? No.
Will the program fail at run time? No.
What will happen? When control returns to the calling program, Parm1 will have a value of 'ZZZZZZZZZZ' and Parm2 will have a value of 'ZZZZZYYYYY'. Just imagine the fun if Parm2 was a packed numeric field.
D Parm1 S 15 C *Entry PList C Parm Parm1 C Eval Parm1 = *All'Z' C Return
Figure 2: The called program with an invalid length for a parameter.
You really want the compiler to validate the parameter list for you, which is one of the reasons for using prototypes.
When the compiler sees a CALLP operation, it will validate to ensure that all the parameters are correct. But how does it know that the parameters are correct? You provide a prototype. A prototype is the format, or the template, or the rules, for the call operation. It is not a parameter list.
Prototypes are defined on the D specifications, as shown in Figure 3. The format of a prototype is very similar to a data structure, except that the type is PR as opposed to DS. You can provide your own name for the CALLP (PromptProduct). The EXTPGM keyword indicates that this is the equivalent of a CALL operation, and it identifies the name of the called program (PRP01R). The names of the subfields in the prototype are irrelevant, what are important are the number of subfields (i.e. parameters) and the definition of each. In the example in Figure 3 the compiler will ensure that two parameters are passed, that Parm1 is a 30 character field and that Parm2 is a 1 character field.
D PromptProduct PR ExtPgm('PRP001R') D FirstParm 30 D SecondParm 1 C CallP PromptProduct(Parm1:Parm2)
Figure 3: A prototyped call to a program.
I quite like the ability to use a meaningful name on the call.
The Procedure Interface
What about the called program? Just as the compiler will validate the parameters on the call, you also want it to validate the parameters in the called program. You achieve that by replacing the *ENTRY PLIST with a Procedure Interface, as shown in Figure 4. The compiler will, again, require a prototype to enable it to validate the parameters.
D PromptProduct PR ExtPgm('PRP001R') D FirstParm 30 D SecondParm 1 D PromptProduct PI D GetCode 30 D Description 1
Figure 4: A Procedure Interface.
Since the prototype is required in at least two programs, it makes sense to put it in a copy member and include it using the /COPY compiler directive.
And it makes even more sense to put all prototypes in a single copy member and include it in all programs using a /COPY compiler directive. Think of this as the source member containing the rules for all calls within your application.
The use of keywords in the prototype can ease some of your coding. The example in Figure 5 shows a work field (Parm2) being used to pass an action code (or some such).
C Eval Parm2 = 'A' C Call 'PGMB' C Parm Parm1 C Parm Parm2
Figure 5: Using a work field on a parameter list
When using a prototyped call, you can use the CONST keyword to indicate that a constant value can be passed for the parameter, as shown in Figure 6.
D PromptProduct PR ExtPgm('PRP001R') D FirstParm 30 D SecondParm 1 Const C CallP PromptProduct(Parm1:'A')
Figure 6: Passing a constant value as a parameter.
Of course, you must make sure that the called program does not change the second parameter. If you are using a procedure interface, in the called program, the compiler will give you an error if you try to change a parameter that has a CONST keyword.
Another useful keyword is OPTION with a value of *OMIT or *NOPASS. Figure 8 shows an example of calling a program to set a customer name and address. The name is optional, as are the second and third address lines. OPTION(*OMIT) indicates that a value of *OMIT can be used in place of a parameter. OPTION(*NOPASS) indicates that the parameter is optional. Once a parameter is defined with OPTION(*NOPASS), all following parameters must be defined with OPTION(*NOPASS). All three calls are valid.
D SetCustomer PR ExtPgm('SPR001R') D Customer 10 D Name 30 Const Options(*OMIT) D AddressLine1 30 Const D AddressLine2 30 Options(*NOPASS) D AddressLine3 30 Options(*NOPASS) C CallP SetCustomer(Code:CusName: Add1:Add2:Add3) C CallP SetCustomer(Code:*OMIT:Add1) C CallP SetCustomer(Code:'Paul': C Add1:Add2)
Figure 7: A prototypes call using *OMIT and *NOPASS.
Figure 8 shows a snippet of the called program. The name and address lines are set to their "default" values. The %ADDR BIF is used to check whether or not *OMIT was specified for the name; the address will be null if *OMIT was specified. The %PARMS BIF is used to check the number of parameters passed.
D SetCustomer PI D Customer 10 D NameIn 30 Const Options(*OMIT) D Addr1In 30 Const D Addr2In 30 Options(*NOPASS) D Addr3In 30 Options(*NOPASS) C Eval CustomerName = 'No Name' C Eval AddrLine2 = Addr1In C Eval AddrLine2 = *All'*' C Eval AddrLine3 = *All'*' C If %Addr(NameIn) <> *Null C Eval CustomerName = NameIn C EndIf C If %Parms() > 3 C Eval AddrLine2 = Addr2In C EndIf C If %Parms() > 4 C Eval AddrLine3 = Addr3In C EndIf
Figure 8: Checking for *OMIT and *NOPASS in a called program.
Remember, prototyped calls are not just for subprocedures. They are a means by which the compiler can validate parameters, and they allow you flexibility in how you pass parameters.
Also, if at a later stage, you decide to change any of these programs to subprocedures, all you need to do is change the EXTPGM keyword to an EXTPROC keyword and compile accordingly.
About the author: Paul Tuohy is CEO of ComCon, an iSeries consulting company. He is the author of Re-Engineering RPG Legacy Applications and is one of the quoted industry experts in the IBM Redbook "Who Knew You Could Do That With RPG IV?"