Prototype vs Parameter List

Procedure prototyping may offer advantages over a fixed parameter list, 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, the compile will fail.

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;       

There are several keywords that can lend to making the procedure call somewhat more flexible, and capable of managing mismatched parameter definitions. The example of the prototype below demonstrates the use of the constant keyword.

0063.00 D GetDHRec        PR                  EXTPGM('DPCASCRG')
0064.00 D  n_OPCM                        4a   CONST             
0065.00 D  n_OPOR#                       7P 0 CONST             
0066.00 D  n_OPORS#                      3P 0 CONST             
0067.00 D  n_OPRSQS                      3P 0                   
0068.00 D  n_OPS#                        3P 0                   
0069.00 D  n_found                        n     

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.

0026.00 D UTMSSGRU        PR                                  
0027.00 D  InString                    255                    
0028.00 D  InTitle                      27    options(*nopass)     

The call to this procedure may have two parameters, the text string and the title, string, or it may actual pass only the first parameter. This does mean the called application will have to check for the number of parameters it was actually handed. Usually, this is easily accomplished using the %PARMS built-in-function (BIF).

0068.00     IF %parms = 1;                  
0069.00         String = inString;          
0070.00     ENDIF;                          
0072.00     IF %parms = 2;                  
0073.00        String = inString;           
0074.00        TitleString = %trim(inTitle);
0075.00     ENDIF;                              

If any additional parameters are added to this prototype *NOPASS must be specified. Once the *NOPASS keyword has been used, any subsequent parameters must also be defined as *NOPASS.

It is very important that you never reference a parameter that wasn’t passed to the program, not because a pointer error will occur, but more importantly, because it may not!  Do not assume an error will be issued, automatically. (Which by-the-way will happen if you attempted to reference more parameters than were passed on a conventional program call.)

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);     

In this case the SHOWMSG procedure includes two parameters. The procedure may be called with two parameters, 1 parameter, in some cases no parameters.

0011.00       CALLP ShowMsg(errid);

0011.00       CALLP ShowMsg(*omit:msgfl);

0011.00       CALLP ShowMsg();    

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.

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)       

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.

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;                 

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.

0001.00 H DEBUG(*YES) dftactgrp(*no) actgrp('QILE') BNDDIR('DPORDP_BD':'QC2LE')
0003.00 D DspTxtMsg       PR                                                  
0004.00 D  InString                    255                                    
0005.00 D  InTitle                      27    options(*nopass)                
0007.00 D CmdString       S            256a   Inz('dspmsg qsysopr') Varying   
0008.00 D ErrorMsg        S            255a   Inz('Command failed')           
0010.00 D ExecuteCmd      Pr            10I 0 ExtProc('system')               
0011.00 D   CmdString                     *   Value Options(*String)   
0011.01 D CPFMessage      S              7A   Import('_EXCP_MSGID')      
0013.00  /FREE                                                                
0014.00     IF ExecuteCmd(CmdString) > 0                                     ;
0015.00     DspTxtMsg(ErrorMsg)                                              ;
0016.00     ENDIF                                                            ;
0017.00      *INLR = *ON                                                     ;
0018.00      RETURN                                                          ;
0019.00  /END-FREE                 

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 VARYING, use the %TRIM function when passing the parameter.