Procedure Prototyping
Prototyping replaced OPM parameter lists.
Procedure prototyping offers advantages over a the fixed parameter list of OPM code, even if the process is a dynamic call, instead of a bound procedure. Though the initial design and purpose of prototypes were to define interfaces for subprocedures (OS/400 V3R2), the designers quickly adapted the prototype functions to accommodate their use for subprogram calls.
One immediately notable difference between procedure prototypes and parameter lists is run-time, versus compile time signature resolution. Using a parameter list, the resolution of the subprogram signature is left to run-time routines. If there is a parameter mismatch, the compile program potentially will end in error because of the differences in the calling parameter list and the entry parameter list. However, the compiler is instructed to match procedure prototype entries with the called module. If there is a mismatch of signatures, then the compile will fail.
0021.00 C CALL 'AUTOEXMP' 0022.00 C PARM PRM1 7 0023.00 C PARM PRM2 3 0024.00 C PARM PRM3 4 0025.00 C PARM PRM4 5 0026.00 C PARM PRM5 5
In this example, if the program called AUTOEXMP expected six parameters instead of 5, or if PRM3 were actually ten bytes long,
the program would still compile. However, run-time errors are bound to occur. Using a procedure prototype allows the compiler to
compare the prototype, with the procedure interface. If there is a mismatch on any of the elements of the argument list, the compile will fail.
Cycle-Main programs don't necessarily make use of procedure prototypes. Generally, a prototyped procedure is associated with a bound process.
However, IBM as of 7.3 has added a new control entry keyword (REQPREXP(*NO | *WARN | *REQUIRE)) which allows prototypes to be optional or required for
main and/or exported procedures.
0104.00 D GetFunction PR 45A 0105.00 D pgmnam 10 0106.00 D pnlnam 10 0107.00 D keyDS 1 0108.00 D keyID 10 0109.00 D function 45 0110.00 D level 3 0103.00 P GetFunction B export 0104.00 D GetFunction PI 45A 0105.00 D pgmnam 10 0106.00 D pnlnam 10 0107.00 D keyDS 1 0108.00 D keyID 10 0109.00 D function 45 0110.00 D level 3
The names for the prototype entries do not have to match. In fact the name of the prototype parameter entry
is documentation only and may be omitted, though I would not recommend it. The sample below shows the call
to the subprocedure (CALLP is implied) if free-format RPG.
0408.00 MONITOR; 0409.00 GetFunction(fpgmid:fpnlid:fkeyds:fkeyid:fmacro:fkautl); 0410.00 ON-ERROR; 0411.00 msgid = 'MIS0003'; 0412.00 EXSR @GetMsg; 0413.00 ENDMON;
If the calling program has a 5-digit numeric field defined instead of a 7-digit field for the second parameter,
with the use of CONST, the compiler will disregard the mismatched parameter. It generates a temporary field of
the appropriate size and attribute for the second parameter, moves the value from program defined field to the
temporary field and uses the temporary field instead of the program defined field for the procedure interface.
Using CONST, the compiler accommodates differences in* Size, decimal places, and type (zoned, packed, integer, etc.) for numeric fields. It compensates for the date format for Date fields and length and type (fixed or varying) for character fields. The use of CONST will allow the programmer to use a literal, or even an expression to pass a value to the invoked procedure. There are some limitations, though. CONST may not be used where the value of the parameter/field may be changed by the called procedure.
An OPTIONS keyword is permissible which adds an even greater degree of flexibility to procedure prototyping. In the following example the prototype shows two parameters. The first parameter is mandatory, the second parameter is optional, and may or may not be passed by the calling program. Bound calls and subprocedures use a bound-call mechanism which is considerably different than procedures that are not prototyped. In many cases, it is possible that a valid pointer exists in the position where the passed parameter would have been. The contents of that location could literally contain anything. To re-iterate, it is important to use %PARMS to determine how many parameters have been established when using *NOPASS, not because an error could occur, but because an error might not occur, leading to spurious results.
dcl-pr utilityMessage ; inString char(255) ; inTitle char(27) options(*nopass) ; end-pr; . . . IF %parms = 1; String = inString; ENDIF; IF %parms = 2; String = inString; TitleString = %trim(inTitle); ENDIF;
Bound calls and subprocedures use a bound-call mechanism which is considerably different than procedures that are not prototyped.
In many cases, it is possible that a valid pointer exists in the position where the passed parameter would have been. The contents
of that location could literally contain anything. To re-iterate, it is important to use %PARMS to determine how many parameters
have been established when using *NOPASS, not because an error could occur, but because an error might not occur, leading to
spurious results. Another option on the procedure prototype is *OMIT. The *OMIT keyword will allow the keyword to be inserted in
the procedure call instead of a value. This might be used in cases where default values are contained within a procedure and it is
not always necessary to pass information from the calling program.
0003.00 D ShowMsg PR Extproc('SCF010RPV1') 0004.00 D errid 7 options(*omit:*nopass) 0005.00 D msgfl 10 options(*nopass) . . 0011.00 CALLP ShowMsg(errid:msgfl); . . 0011.00 CALLP ShowMsg(errid); . . 0011.00 CALLP ShowMsg(*omit:msgfl); . . 0011.00 CALLP ShowMsg();
In this case the SHOWMSG procedure includes two parameters. The procedure may be called with two parameters, 1 parameter, in some cases no parameters.
This does require some additional code in the procedure to handle the various signatures permissible with the procedure. The %PARMS BIF is not enough
to determine how to handle the parameter list. The first parameter may be present, but when *OMIT is defined as an option, the BIF %ADDR must be
used to determine if a value was assigned to the parameter. If the call to the procedure used *OMIT, then the test of the address should return a null value.
In the example below, if the first parameter was null, a literal value is assigned to the error ID to be used in the procedure.
0011.00 P ShowMsg B export 0012.00 D ShowMsg PI 0013.00 D p$err 7 options(*omit:*nopass) 0014.00 D p$msg 10 options(*nopass) . . . 0026.00 IF %addr(p$err) = *null; 0027.00 errid = 'MIS0013'; 0028.00 ELSE; 0029.00 errid = p$err; 0030.00 ENDIF; 0031.00 IF %parms < 2; 0032.00 msgfl = 'MISMSGF'; 0033.00 ELSE; 0034.00 msgfl = p$msg; 0035.00 ENDIF; 0036.00
Similar to the CONST keyword when used on a prototyped procedure, the VALUE keyword allows the passing of data that does
not exactly match the prototyped specification. To reiterate, the use of CONST on a prototyped parameter gives a programmer
the flexibility to use a literal, or use a packed decimal field when the actual specification is defined as zoned decimal.
In this event, the compiler produces an intermediate representation in the expected format and passes a pointer to a copy
of the data, rather than to the original field.
The VALUE parameter keyword is treated in a similar fashion by the complier, requiring the compiler to adjust the variable length and convert the information into the correct data format. However, passing parameters by value does not pass a pointer to the data, but actually passes a copy of the data.There are however, major differences between CONST and VALUE. CONST only produces a copy of the parameter variable when necessary, that is when a discrepancy is detected between the program variable and the procedure prototype. VALUE performs the copy for the parameter definition regardless whether the procedure prototype specification and the program variable agree, or not.
Another major difference is the fact that, since a copy of the data is being passed, the parameter value may be changed by the subprocedure. This can reduce the number of work fields, or temporary variables in your subprocedure. The subprocedure may change the value of the parameter without affecting the value, as seen by the requesting program. The value keyword does, however, have a limitation not imposed on the CONST keyword. VALUE may only appear in the prototype for bound procedures. The value keyword is not supported by dynamic calls. The following simple example demonstrates the use of the keyword VALUE in procedure prototyping.
ctl-Opt DEBUG(*YES) OPTION(*SRCSTMT : *NODEBUGIO) DFTACTGRP(*NO) ACTGRP('QILE') BNDDIR('DPORDP_BD':'QC2LE'); Dcl-pr DspTxtMsg ; inString char(255) ; inTitle char(27) options(*nopass) ; end-pr; Dcl-Pr exit extProc('exit') ; *n uns(3) value ; END-PR; Dcl-Pr ExecuteCmd int(10) extproc('system') ; cmdString pointer value options(*string) ; End-Pr Dcl-S CmdString varchar(256) Inz('dspmsg qsysopr') ; Dcl-S ErrorMsg char(255) Inz('Command failed') ; Dcl-S CPFMessage char(7) Import('_EXCP_MSGID') ; IF ExecuteCmd(CmdString) > 0 ; DspTxtMsg(ErrorMsg) ; ENDIF ; *INLR = *ON ; exit(0) ; RETURN ;
The short sample program above demonstrates the use of the value keyword in conjunction with the option *STRING.
Though short, the example contains some critical points within the code. Note first there are two bound procedures defined,
DspTxtMsg and ExecuteCmd. Note the use of the EXTPROC on the ExecuteCmd prototype. The compiler does not require EXTPROC,
in fact it is supplied as the default. However, the compiler will translate prototype names to uppercase. This is not critical
with RPG, since RPG is not case sensitive, however, this example is bound to use C functions. C is case sensitive, so the EXTPROC
is necessary. Enclosed within quotes, the function/procedure name will not be translated to uppercase.
The sample makes use of the C-system function. Essentially the C function serves the same purpose as the QCMDEXC API. One possible advantage of the C system function as opposed to the QCMDEXC API is the return variable on the C system function. Like the code above, you may simply use an IF statement to determine if the command executed successfully, or failed. Any return code greater than zero indicates an error. The actual CPF message may be trapped if you include a variable for the exception and import _EXCP_MSGID as illustrated above.
The procedure prototype specifies CmdString as being a pointer, passed by VALUE, using the option *STRING. The result is a character field, not a pointer, to the function called. This is necessary to avoid problems with the C language constructs. Unlike RPG, C does not define fixed character fields, but rather views character representations as null-terminated strings. Using a pointer, C expects a null-terminated string will be passed. To insure a peaceful coexistence between C and RPG, the option *STRING can be used to direct the compiler to copy the data to a temporary field and add the X’00’ (null) to the end of the data. If you use a fixed length variable instead of specifying VARCHAR (or VARYING in fixed-format), use the %TRIM function when passing the parameter.