Passing Variable-Length Parameters to Subprocedures

Article ID: 58811

Passing Variable-Length Parameters to Subprocedures

In RPG, there is a subtle but important difference between the VARYING keyword that is used to assign a variable-length attribute to a character field and the OPTIONS(*VARSIZE) keyword that is used to permit a variable of any length to be passed as a parameter to a subprocedure or program. Often these two different features are referred to as variable-length field or variable-length parameter. So confusing is this that many people refer to variable-length fields as varying fields to more closely associate them with their keyword attribute.

This week I explain the benefits of and differences between using these keywords with parameters. We'll look at the following combinations:

  1. VARYING
  2. VARYING with CONST
  3. OPTIONS(*VARSIZE)
  4. OPTIONS(*VARSIZE) with VARYING

Before we begin, let me set a baseline for the restrictions of the various types of character parameters:

Normal character parameters, no keywords Accepts parameter values that have a length attribute as long or longer than the parameter definition length.
VARYING character parameters. Accepts parameters of the same length as the parameter definition and must also include the VARYING keyword.
VARYING and CONST Accepts parameters VARYING or fixed-length values (literals or fields) that have any length (longer, short or the same size as the parameter definition).
OPTIONS(*VARSIZE) character parameters. Accepts parameter values that have any fixed-length definition (longer, short, or the same size as the parameter definition).
Normal character parameters with CONST Accepts parameter values (literals or fields) of fixed-length or VARYING length that have any length (longer, shorter, or the same as the parameter definition).

VARYING - Varying Length Parameters

The VARYING keyword allows a parameter to be defined with the same attributes as a VARYING standalone field; nothing special here. But an advantage in using VARYING on a parameter definition is that the length of the value passed to that parameter can be retrieved using %LEN, thereby allowing you to avoid passing an extra length parameter. Here is an example:

     P getCustName     B             
     D getCustName     PI             
     D custNo                         7P 0 Const 
(1)  D rtnName                      200A   VARYING
      /free
(2)        chain (custNo) customer;
(3)        if %found();
(4)           rtnName = cm.custName;
(5)        else;
(6)           rtnName = '';
(7)        endif;
      /end-free
     P getCustName     E           

A VARYING parameter definition on line 1 allows a 200-byte VARYING field to be passed to the GETCUSTNAME subprocedure. Since only VARYING is specified, the parameter may be modified (lines 4 and 6) to return data to the caller. The restriction on using only VARYING is that the variable passed to GETCUSTNAME must match the RTNNAME parameter definition. That is, it must be a 200-byte VARYING field. This restriction can be removed by adding the CONST keyword to the parameter definition, but doing so prevents the subprocedure from being able to modify the content of that parameter.

     D getCustName     PI             
     D custNo                         7P 0 Const 
     D inName                       200A   VARYING CONST

Defining VARYING CONST parameters is a great way to allow any type of character variable (fixed-length or VARYING) to be passed on the parameter. Since CONST converts data to the same attributes as the parameter definition (within the same data-type) specifying CONST VARYING for a parameter is an effective way to allow both fixed-length and VARYING fields of any length to be passed to a subprocedure.

One more advantage to using VARYING (with or without CONST) is that the %LEN built-in function may be used within the subprocedure to retrieve the current length of the input parameter. When a VARYING field is passed to the subprocedure, the current length of that variable is returned. When a fixed-length field is passed, the field's declared length is returned by %LEN.

OPTIONS(*VARSIZE) Keyword--Allow A Different Size

The OPTIONS(*VARSIZE) keyword allows the caller of the subprocedure to pass a field whose length does not match that of the subprocedure. When a character parameter is defined without any additional keywords, it accepts fields that are at least as long as the parameter definition.

For example, look at the QCMDEXC prototype below:

     D QCMDEXC         PR                  extPgm('QCMDEXC')
     D  cmdString                 32702A   Const OPTIONS(*VARSIZE)
     D  nCmdLen                      15P 5 Const
     D  dbcsFlag                      3A   Const OPTIONS(*NOPASS)

The cmdString parameter includes the OPTIONS(*VARSIZE) keyword. This allows a value to be passed to QCMDEXC that is not 32702 bytes long (the parameter's length). A value of any length up to the system limitation may be specified. The Sytem i CL command string length maximum was around 6000 characters, but today I don't know if there is a limitation. I do know, however, that 32702 is the correct length for QCMDEXC.

Here's another example that shows how adding OPTIONS(*VARSIZE) to a subprocedure prototype allows you to pass in a field of any length:

     D getCustName     PR                  
     D  custNo                        7P 0 Const
     D  custName                    200A   OPTIONS(*VARSIZE)
     D  length                       10i 0 Const

In this example, the GETCUSTNAME subprocedure accepts the following parameters:

  1. CUSTNO--A valid customer number
  2. CUSTNAME--A return variable that receives the customer's name.
  3. LENGTH--An extra parameter that indicates the length of the return value specified on the CUSTNAME parameter.

To call GETCUSTNAME, specify all 3 parameters as follows:

     D name            S             50A 
      /free
           getCustName(1234 : name : %size(name) );
      /end-free

Even though the CUSTNAME parameter is defined as a 200-position parameter, the OPTIONS(*VARSIZE) keyword allows the 50-position NAME variable without a compile-time error. To avoid a runtime error in GETCUSTNAME, an extra parameter named LENGTH is passed with the declared length of the NAME variable. It's up to the programmer of the subprocedure GETCUSTNAME to read that parameter and use %SUBST or similar to avoid accessing data outside the scope of the variable being passed. The following illustrates one way to do this:

     P getCustName2    B             
     D getCustName2    PI             
     D custNo                         7P 0 Const 
     D rtnName                      200A   OPTIONS(*VARSIZE)
     D length                        10I 0 Const 
      /free
(1)        chain (custNo) customer;
(2)        if %found();
(3)           %subst(rtnName:1:length) = cm.custName;
(4)        else;
(5)           %subst(rtnName:1:length) = '';
(6)        endif;
      /end-free
     P getCustName2    E             

The CHAIN opcode (line 1) retrieves the record from the CUSTOMER. If a customer is successfully retrieved, the CUSTNAME field is copied to the RTNNAME parameter using %SUBST (line 3). This avoids access data outside the scope of the variable that was passed. One problem with this is, of course, that if you pass in a longer value, say... 300 bytes long, you'll get a runtime error due to it being longer than the parameter definition. This occurs even though OPTIONS(*VARSIZE) is specified. To avoid this kind of runtime error, an external API or function such as memcpy or CPYBYTES will need to be used to copy the data.

What about CONST and OPTIONS(*VARSIZE)?

When a parameter will not be modified by the called subprocedure or program, the CONST keyword provides all the capabilities of the OPTIONS(*VARSIZE) keyword. So why specify both (as was the case on the QCMDEXC prototype)?

When OPTIONS(*VARSIZE) is specified with CONST, the compiler will avoid making a copy of the data of the variable being passed to the subprocedure, when it is of the same VARYING or non-VARYING attribute as the parameter definition. So when you see OPTIONS(*VARSIZE) CONST for a parameter, it's more of an optimization setting by a programmer who knows what they're doing.

VARYING and OPTIONS(*VARSIZE)

When the parameter needs to be a VARYING field, and it must receive a value from the called subprocedure, you have a choice of restricting the input parameter to the same length as the parameter definition (as in our first example) or adding OPTIONS(*VARSIZE) to open it up. By adding OPTIONS(*VARSIZE) to a VARYING parameter, the parameter is no longer restricted to the length of the parameter definition. A VARYING field with a declared length different from that of the parameter definition may be passed.

Unlike normal non-VARYING parameters, a VARYING parameter can use the %LEN built-in function to retrieve the current length of the input variable. Certainly this is not a complete solution, as the current length could be zero or some length that is too short to receive the results. So setting the current length before calling the subprocedure is appropriate in this situation.

     D getCustName3    PI             
     D custNo                         7P 0 Const 
      D rtnName                      200A   VARYING OPTIONS(*VARSIZE)

Now the body or subprocedure implementation can modify the input parameter.

     P getCustName3    B             
     D getCustName3    PI             
     D custNo                         7P 0 Const 
     D rtnName                      200A   VARYING OPTIONS(*VARSIZE)
      /free
(1)        chain (custNo) customer;
(2)        if %found();
(3)           %subst(rtnName:1:%LEN(rtnLen)) = cm.custName;
(4)        else;
(5)           %subst(rtnName:1:%LEN(rtnLen)) = '';
(6)        endif;
      /end-free
     P getCustName3    E           

Call the GETCUSTNAME3 subprocedure, as follows:

     D getCustName3    PR
     D custNo                         7P 0 Const 
     D rtnName                      200A   VARYING OPTIONS(*VARSIZE)
     D name            S             50A   VARYING
      /free
           %len(name) = %size(name)-2;  // Set it to max length.
           getCustname( name );
      /end-free

This is great if you want to modify only as many bytes as the current length of the input value. How often will you remember to modify the current length before calling a subprocedure? You can unsafely modify more than %LEN bytes of the input parameter within the subprocedure, but that is not recommended. For example, the following may or may not work:

        rtnName = cm.custName;

As long as the data being copied to RTNNAME is no longer than %LEN(RTNNAME) the above statement will work. However, the compiler restricts assignment statements not to the declared length of the variable passed on the parameter, but to the parameter's definition. So in our example, it may try to expand the length of RTNNAME to as many as 200 bytes. But our input variable (in this example) is just 50 bytes long. So we could have a learning experience.

To leverage the full capabilities that VARYING with OPTIONS(*VARSIZE) provides while avoiding corrupting memory or restricting things with the CONST keyword, a system provided built-in function can be used to retrieve the attributes of the input variable. This API, CEEGSI, retrieves the current and maximum lengths for the variable passed to the subprocedure.

CEEGSI Prototype

     D CEEGSI          PR                  extProc('CEEGSI')
     D  parmID                       10I 0 Const 
     D  dataType                     10I 0 
     D  curLen                       10I 0 
     D  maxLen                       10I 0 
     D  parmError                    12A   OPTIONS(*OMIT) 
 
  1. The first parameter is the parameter ordinal. This is tech speak for the number of the parameter whose information is being retrieved. In our example, we would pass the number 2 for the PARMID parameter. I've asked IBM to create a %PARMID or %PARMNUM built-in function so that we could specify %PARMID(RTNNAME) rather than 2.
  2. The second parameter is the data type of the variable passed to the subprocedure.
  3. The third parameter is the current length of the variable passed to the subprocedure. The same as using %LEN(parmVar).
  4. The fourth parameter is the maximum length of the variable passed to the subprocedure. This is equivalent to %SIZE(parmVar)-2.
  5. The fifth parameter is an error return code, which is normally specified as *OMIT.

Here's a simple example of using CEEGSI in our GETCUSTNAME subprocedure:

     P getCustName4    B                                            
     D getCustName4    PI                  OPDESC                   
     D custNo                         7P 0 Const                    
     D rtnName                      200A   VARYING OPTIONS(*VARSIZE)
     D  data           S            100A   Inz('Bob Cozzi')         
     D  len            S             10I 0                          
     D  maxlen         S             10I 0                          
     D  dType          S             10I 0                          
      /free                                                         
(1)        ceegsi( 2 :  dType : len : maxLen : *OMIT);              
(2)        %subst(rtnName:1:maxLen) = %trimR(data);              
      /end-free                                                     
     P getCustName4    E

Line 1 retrieves the LEN and MAXLEN values for the variable passed on the RTNNAME parameter, using CEEGSI. To make CEEGSI work, the so-called Operational Descriptor needs to be passed to the subprocedure. To pass the operational description, the OPDESC keyword must be specified on the Prototype and Procedure Interface statements

Okay, so we have everything we want now, right? Not really. There is still a possibility of a memory overflow or runtime RNX0100 (out of range) message.

To solve this problem, we have to retrieve the max length, compare it to the number of bytes we want to return, and adjust the return parameter's current length. The revised code is illustrated below:

     P getCustName5    B
     D getCustName5    PI                  OPDESC
     D custNo                         7P 0 Const 
     D rtnName                      200A   VARYING OPTIONS(*VARSIZE)
     D  data           S            100A   Inz('Bob Cozzi')
     D  len            S             10I 0
     D  maxlen         S             10I 0
     D  dType          S             10I 0
     D  bytes          S             10I 0
      /free
(1)        ceegsi( 2 :  dType : len : maxLen : *OMIT);
(2)        bytes = %len(%trimR(data));
(3)        if (maxLen < bytes);
(4)           bytes = maxLen;                                    
(5)        endif;                                                
(6)       %len(rtnName) = bytes;                                
(7)       %subst(rtnName:1:bytes) = %trimR(data);
     /end-free
     P getCustName5    E
  • Line 1 retrieves the maximum length of the input variable along with its other attributes.
  • Line 2 sets the value of BYTES to the length of the data to be copied to the return variable. In our original example this was the data from the customer master file. Here, we are using a dummy field for example purposes.
  • Line 3 compares the number of bytes to copy (i.e., the BYTES variable) to the maximum length retrieved by CEEGSI. If the number of bytes is greater than the maximum allowed, we adjust BYTES to MAXLEN (line 4).
  • Line 6 sets the current length of RTNNAME to the number of bytes being returned. This will expand or contract the current length of the VARYING length input variable so that it can hold the return data.
  • Line 7 copies the data to the return value. We use %SUBST here as overkill; since we've already set the length of RTNNAME to BYTES, this redundancy isn't strictly necessary.

This still has an issue if the input parameter is longer than 200 bytes. There are two ways to resolve this:

Increase the declared length for RTNNAME to some large value like 32k or even 64k.

Use one of the C runtime functions such as memcpy or CPYBYTES and the "address of" built-in function to copy the data, thereby avoiding the RPG compiler-imposed field boundaries.

(7)       memcpy(%addr(rtnName:*DATA) : %addr(data:*data) : bytes);

Isn't that fun!

This is part of my new book, Breaking Free: Bob Cozzi's Guide to The Modern RPG Language, coming to an Amazon.com near you.


Bob Cozzi has a new book coming out in the Spring of 2010. Breaking Free: Bob Cozzi's Guide to The Modern RPG Language.

RPGWorld.com is Bob's website where you can find all things Cozzi. Including: RPG Open, iWeekly, the most popular RPG IV discussion forum, the RPG World Conference, Subprocedure training DVDs, and more! Visit www.RPGWorld.com for details.

Bob has created an RPG World group on Facebook.com join it and join in the conversation.

Follow Bob Cozzi on Twitter at: Twitter.com/bobcozzi

Bob Cozzi is the author of RPGLIB the RPG IV add-on or extension library that adds nearly 200 prewritten functions to RPG IV--those that programmers want to use today. Download a free 30-day trial today at www.RPGLIB.com

ProVIP Sponsors

ProVIP Sponsors