Options(*Varsize) vs. VARYING

Article ID: 19556

Q: I have a topic that's very confusing to me, and I bet it gives a lot of other programmers problems as well. The topic is the use of the VARYING and *VARSIZE keywords for subprocedures. When do I use one or the other, and what techniques are needed to make them work?

I've tried to use these keywords to make a wrapper for the QCMDCHK API, but nothing I've tried works. The following code should give you an idea of what I'm trying to accomplish:

     D ChkCmd          Pr              n
     D  CmdString                 32767a   Options(*VarSize)
     D  Prompt                        1a   Const Options(*Nopass)

     D sTestString     S           2000a

     D sDspReply       S              1a
     D sDspQueue       S             10a   Inz('*EXT')

      /Free
           // Command to prompt
           sTestString = 'sbmjob';

           // Execute subprocedure and show results
           If ChkCmd(sTestString:'Y');
              Dsply %SubSt(sTestString:1:50) sDspQueue sDspReply;
           Else;
              Dsply 'Command failed.' sDspQueue sDspReply;
           Endif;

          *InLr = *On;

      /End-Free

     P ChkCmd          B                   Export
     D ChkCmd          Pi              N
     D  CmdString                 32767a   Options(*VarSize)
     D  Prompt                        1a   Const Options(*NoPass)

     D QCmdChk         PR                  ExtPgm('QCMDCHK')
     D  CmdString                 32767a   Options(*VarSize)
     D  sCmdLen                      15  5 Const

     D sDftPrompt      s              1
     D sDftLength      s             10i 0
      /Free

             If %Parms >= 2 and
                Prompt <> *Blanks;
                sDftPrompt = Prompt;
             Else;
                sDftPrompt = 'N';
             EndIf;

             // THIS LINE DOES NOT WORK!  IT RETURNS
             //   32767 EVEN THOUGH THE INPUT IS 2000!
             sDftLength = %Len(CmdString);

             // THIS CODE CAN OVERWRITE MEMORY THAT DOESN'T
             //  BELONG TO CMDSTRING:
             If sDftPrompt = 'Y';
                CmdString  = '?' + %Subst(CmdString:1:sDftLength);
             Else;
                CmdString  = %Subst(CmdString:1:sDftLength);
             EndIf;

             // THE FOLLOWING CODE PRODUCES UNPREDICTABLE
             // RESULTS BECAUSE THE LENGTH THAT GETS PASSED
             // IS TOO LARGE!
             Monitor;
                QCmdChk(CmdString:sDftLength);
                Return *On;
             On-Error;
                Return *Off;
             Endmon;

      /End-Free
     P ChkCmd          E

A: Yes, I can see why you're confused. The terms "Varsize" and "Varying" are so similar that it's easy to confuse the two. Before I present a solution to errors in the subprocedure, I'll explain what each keyword does.

WHAT THE VARYING KEYWORD DOES

VARYING describes a type of variable -- or, at least, that's the way I've always thought of it. I find it counter-intuitive that IBM decided to make it a keyword instead of a data type! Just as "packed" and "zoned" are two different numeric data types, I consider "alphanumeric" and "varying" to be two different character data types.

Consider the following code:

     D myString        s            100A                                                                                        
     c                   eval      myString = 'Alpha Beta Gamma'

How many characters does this statement assign to myString? If you think that 16 characters are assigned, you're wrong. That statement sets 100 characters! The first 16 characters are the words "Alpha Beta Gamma," but there's also an additional 84 blanks that this statement implicitly sets. This is done because myString is a fixed-length string. It's always 100 characters long, no matter what you do to it!

The VARYING keyword tells the compiler that a variable doesn't have a fixed length. When you add the VARYING keyword, it changes the data type from standard alphanumeric to one that varies in length.

     D myString        s            100A   VARYING
     c                   eval      myString = 'Alpha Beta Gamma'

In this example, the variable is declared as varying. It's true, I still coded "100A", but now that number specifies the maximum length of the string. The actual length can vary. In this example, myString only gets assigned the 16 characters. No blanks are added to the end.

WHAT OPTIONS(*VARSIZE) DOES

On the other hand, *VARSIZE is an option that may be specified in the OPTIONS D-spec keyword. It has nothing to do with the data type of the variable. All it does is disable the compiler's validity checking of the length.

You see, normally when you pass a variable to a subprocedure, that variable must be the same length or larger than the one that's declared in the prototype. For example, the following code will not work:

     D myProto         pr
     D  Parm1                        19A

     D myVar           s             15A
      ** This will generate an error because myVar
      ** is shorter than Parm1!
     c                   callp     myProto(myVar)

Sometimes, in order to make routines as generic as possible, you want to accept any size variable, no matter how large or small. The options(*VARSIZE) keyword disables the compiler's internal validity checking, so that the following code will compile:

     D myProto         pr
     D  Parm1                        19A   options(*varsize)

     D myVar           s             15A
      ** This will NOT generate an error. 
      ** Warning: Do not use positions 16-19 of PARM1 in the
      ** subprocedure!
     c                   callp     myProto(myVar)

The tricky thing about options(*varsize) is that you've disabled a safeguard that was there for a reason! The declaration of myVar causes the system to reserve 15 bytes of memory to store the variable's data into. When you call the subprocedure in the example above, it will view that same spot in memory as a 19A variable! If you mistakenly write anything to positions 16-19 of that variable, you'll overwrite memory that may be used for another purpose. Sometimes, it'll cause the program to crash. Other times, it'll overwrite memory used by other variables, which may be in the same program or may be in another program. You might even overwrite memory that's not in use, so you might not even notice the problem!

When you use options(*varsize), you must be very careful to only touch the bytes that were passed. It's up to you to determine how many bytes were passed and to make sure that the remaining bytes do not get touched.

CONTRASTING THE TWO

As you can see, VARYING and *VARSIZE are two very different things! VARYING is a data type where the data in a string varies, and options(*VARSIZE) is a keyword that disables a safeguard.

When you specify VARYING on a prototype, you're not telling the system that the length of the parameter can vary. You're telling it the data type of that parameter.

To summarize:

      *
      * Pass a 32k parameter to SomeProcedure. The problem here is
      * that it's too rigid!  The length always has to be 32767!
      *
     D SomeProcedure   PR
     D   parm1                    32767A

      *
      * Pass a 32k parameter to SomeProcedure. This is every bit as
      * rigid as the previous example. The only difference is the
      * parameter has to be a VARYING variable.
      *
     D SomeProcedure   PR
     D   parm1                    32767A   varying

      *
      * This example is not so rigid. The data can be any length,
      * but now I have to worry about overflowing the space that's
      * been reserved!
       *
     D SomeProcedure   PR
     D   parm1                    32767A   options(*varsize)

What you may be thinking of when you suggest using these options to count the length of a string for you is that RPG will automatically convert from one data type to another when the CONST keyword is specified. This can be used to calculate the length of a fixed-length string because when RPG converts from fixed-length to VARYING, it sets the length of the data in the VARYING variable to the length of the fixed-length field.

For example, the following will return 15:

     H DFTACTGRP(*NO)
     D CalcSize        PR            10I 0
     D   myParm                    1000A   const varying

     D myVar           s             15A
     D size            s             10I 0

      /free

         size = CalcSize(myVar);
         *inlr = *on;

      /end-free
     P CalcSize        B
     D CalcSize        PI            10I 0
     D   myParm                    1000A   const varying
      /free
         return %len(myParm);
      /end-free
     P                 E

This type of logic is not helpful in the QCMDCHK example, however, since QCMDCHK is not looking for a VARYING field, and you can't use CONST because the API needs to be able to change the data in the string.

FIXING THE QCMDCHK EXAMPLE

The easiest way to make QCMDCHK work is to eliminate the subprocedure completely and use the %SIZE BIF to calculate the length. That way, you don't need to write a special subprocedure. Instead, code it as follows:

     D QCmdChk         PR                  ExtPgm('QCMDCHK')
     D   CmdString                32767A   options(*varsize)
     D   sCmdLen                     15  5 const

     D sTestString     s           2000A

     D sDspReply       s              1A
     D sDspQueue       s             10A   inz('*EXT')

      /free

          sTestString = '?sbmjob';

          callp(e) QCmdChk(sTestString: %size(sTestString));
          if not %error;
             Dsply %subst(sTestString:1:50) sDspQueue sDspReply;
          else;
             Dsply 'Command failed.' sDspQueue sDspReply;
          endif;

          *inlr = *on;

      /end-free

If you do want to put this into a subprocedure, then you have to consider how to pass the variable-length parameter. VARYING does not make sense here, since QCmdChk doesn't understand that data type. Clearly, options(*VARSIZE) is what's needed.

Remember the caveat that I discussed above! You have to be very careful to only touch the length that was passed. You could do that by passing an additional parameter to indicate the length of the variable, as follows:

     H DFTACTGRP(*NO)

     D ChkCmd          PR              N
     D   CmdString                32767A   options(*varsize)
     D   sCmdLen                     10I 0 value
     D   Prompt                       1A   const options(*nopass)

     D QCmdChk         PR                  ExtPgm('QCMDCHK')
     D   CmdString                32767A   options(*varsize)
     D   sCmdLen                     15  5 const

     D sTestString     s           2000A

     D sDspReply       s              1A
     D sDspQueue       s             10A   inz('*EXT')

      /free

          sTestString = 'sbmjob';

          if ChkCmd(sTestString: %size(sTestString): 'Y');
             Dsply %subst(sTestString:1:50) sDspQueue sDspReply;
          else;
             Dsply 'Command failed.' sDspQueue sDspReply;
          endif;

          *inlr = *on;

      /end-free

     P ChkCmd          B
     D ChkCmd          PI              N
     D   CmdString                32767A   options(*varsize)
     D   sCmdLen                     10I 0 value
     D   Prompt                       1A   const options(*nopass)

     D sDftPrompt      s              1

      /free

          if %parms >= 2 and
             Prompt <> *blanks;
             sDftPrompt = Prompt;
          else;
             sDftPrompt = 'N';
          endif;

          if sDftPrompt = 'Y';
             %subst(CmdString:1:sCmdLen) =
                   '?' + %subst(CmdString:1:sCmdLen);
          endif;

          monitor;
             QCmdChk(CmdString: sCmdLen);
             return *on;
          on-error;
             return *off;
          endmon;

      /end-free
     P                 E

Note the use of the %SUBST BIF where it adds the prompt character. The following code will NOT work, because it overwrites the entire CmdString! If you did it this way, it'd set the first 2001 bytes of CmdString to the value passed in and set the remaining 30766 bytes to blanks! That would overwrite memory that you don't own and cause all sorts of headaches!

             // USE %SUBST, DON'T DO IT THIS WAY!
             If sDftPrompt = 'Y';
                CmdString  = '?' + %Subst(CmdString:1:sDftLength);
             EndIf;

Although the example that I posted above will work nicely, it doesn't do the job of calculating the size of the input variable for you. You have to pass it as a parameter. You could use RPG's support for operational descriptors to solve that problem. When you use operational descriptors, additional information about each parameter is made available through some APIs.

The following code demonstrates the use of operational descriptors to calculate the length of the variable for you:

     H DFTACTGRP(*NO)

     D ChkCmd          PR              N   opdesc
     D   CmdString                32767A   options(*varsize)
     D   Prompt                       1A   const options(*nopass)

     D QCmdChk         PR                  ExtPgm('QCMDCHK')
     D   CmdString                32767A   options(*varsize)
     D   sCmdLen                     15  5 const

     D sTestString     s           2000A

     D sDspReply       s              1A
     D sDspQueue       s             10A   inz('*EXT')

      /free

          sTestString = 'sbmjob';

          if ChkCmd(sTestString: 'Y');
             Dsply %subst(sTestString:1:50) sDspQueue sDspReply;
          else;
             Dsply 'Command failed.' sDspQueue sDspReply;
          endif;

          *inlr = *on;

      /end-free

     P ChkCmd          B
     D ChkCmd          PI              N   opdesc
     D   CmdString                32767A   options(*varsize)
     D   Prompt                       1A   const options(*nopass)

      *
      * CEEGSI(): Get String Information from the
      *           operational descriptor.
      *
     D CEEGSI          PR
     D   parmno                      10I 0 const
     D   datatype                    10I 0
     D   currlen                     10I 0
     D   maxlen                      10I 0

     D sDftPrompt      s              1
     D StringType      s             10I 0
     D CurrentLen      s             10I 0
     D MaximumLen      s             10I 0

      /free

          if %parms >= 2 and
             Prompt <> *blanks;
             sDftPrompt = Prompt;
          else;
             sDftPrompt = 'N';
          endif;

          CEEGSI( 1
                : StringType
                : CurrentLen
                : MaximumLen );

          if sDftPrompt = 'Y';
             %subst(CmdString:1:MaximumLen) =
                   '?' + %subst(CmdString:1:CurrentLen);
          endif;

          monitor;
             QCmdChk(CmdString: MaximumLen);
             return *on;
          on-error;
             return *off;
          endmon;

      /end-free
     P                 E

ProVIP Sponsors

ProVIP Sponsors