You can determine at run-time the size of a variable.
I recently conducted a workshop on best programming practices when working with system APIs. One of the practices I recommend is to avoid hard-coding values such as variable lengths (or anything else for that matter) throughout the program. Assume for instance that you're calling a retrieve type system API that has four parameters as shown here:
Required Parameter Group:
1 |
Receiver variable |
Output |
Char(*) |
2 |
Format of receiver variable |
Input |
Char(8) |
3 |
Length of receiver variable |
Input |
Binary(4) |
4 |
Error code |
I/O |
Char(*)
|
If your Receiver variable parameter, named RcvVar for discussion purposes, is defined as an 80-byte structure, you want to use functions such as ILE RPG's %size(RcvVar) built-in or ILE COBOL's Length of RcvVar support for the third parameter. To hard-code the third parameter as the constant value of 80 is just looking for trouble down the road when someone changes the length of RcvVar and forgets to update the call, copy/pastes the API call changing the RcvVar variable name, or a host of other possibilities. At this point in my workshop, a bright (not to imply that any of the attendees were not bright!) student said, "I program primarily in CL. How do I avoid the hard-coding of parameter lengths in my CL program?" We have now reached the purpose of this article.
CL does not provide, at least through V6R1, a built-in that returns the length of a declared variable. CL does, however, allow us to extend the language through the addition of new CL commands. In this article, we'll create the command Retrieve Variable Size (RTVVARSIZ).
Rather than hard-coding the command message prompts, we will first create a message file. This message file, OURMSGS, will contain the prompts for our new command and also a message that we will use in a test program to display the size of various variables. This is the CL to create the message file and associated messages:
Pgm
CrtMsgF MsgF(OurMsgs)
AddMsgD MsgID(Msg0101) MsgF(OurMsgs) Msg('Retrieve Variable Size')
AddMsgD MsgID(Msg0102) MsgF(OurMsgs) Msg('Variable name')
AddMsgD MsgID(Msg0103) MsgF(OurMsgs) Msg('Variable size')
AddMsgD MsgID(Msg0301) MsgF(OurMsgs) Msg('The size is &1.') +
Fmt((*UBin 4))
EndPgm
You can enter this source into QCLSRC member BLDOURMSGS, compile BLDOURMSGS using CRTBNDCL BLDOURMSGS, and then create the OURMSGS *MSGF with CALL BLDOURMSGS.
Here's the command source for RTVVARSIZ:
Cmd Prompt(Msg0101)
Parm Kwd(Var) Type(*Char) Len(1) RtnVal(*Yes) Min(1) +
Vary(*Yes *Int4) Prompt(Msg0102)
Parm Kwd(Size) Type(*Int4) RtnVal(*Yes) Min(1) Prompt(Msg0103)
This source can be entered into QCMDSRC member RTVVARSIZ and compiled using this command:
CrtCmd Cmd(RtvVarSiz) Pgm(RtvVarSiz) Allow(*IPgm *BPgm *IMod *BMod)
PmtFile(OurMsgs)
Before looking at the command processing program (CPP) for RTVVARSIZ, let's look at the definition for the RTVVARSIZ command.
The first parameter, VAR, represents the CL variable (&RcvVar to return to our earlier example) that we want to know the allocated size of, and the parameter is prompted by message Msg0102. VAR is defined as accepting character variables and, due to the Vary(*Yes *Int4) specification, the variable does not have to be of a fixed size. The *Int4 argument of the Vary keyword indicates that the actual length of the variable is to be passed to the CPP as a 4-byte signed integer that is prefixed to the actual value of the CL variable. The length of the CL variable must be a minimum of one byte--Len(1)--and has no fixed maximum length other than what your current release allows for the length of a CL character variable. A CL variable is required when using the RTVVARSIZ command--Min(1)--and only one variable can be passed due to a default value of Max(1). The key specification, though, is RtnVal(*Yes). When passing a varying-length parameter from a command to a CPP, the length prefix is normally set to the actual length of the provided character data. That is, the length represents the number of characters in the CL variable with any trailing blanks removed. When RtnVal(*Yes) is specified for a varying-length parameter, the length prefix discussed earlier represents the allocated size of the CL variable. This is done so that the CPP can determine how many bytes of data can be safely returned to the CL program variable. It is this RtnVal(*Yes) provided length that we will use to determine the allocated size of the CL variable. We won't actually be returning any data in the VAR variable but will certainly take advantage of this information the command processor provides us.
The second parameter, SIZE, represents the CL variable that is used to return the allocated size of VAR, and the parameter is prompted by message Msg0103. SIZE is defined as a required parameter that must be a 4-byte signed integer. RtnVal(*Yes) is once again specified, but this time we will be returning data to the CL variable--namely, the allocated size of the VAR variable.
With that overview of the command definition out of the way, here's one possible set of source for the RTVVARSIZ CPP:
Pgm Parm(&Var &Size)
Dcl Var(&Var) Type(*Char) Len(9999)
Dcl Var(&Size) Type(*Int)
ChgVar Var(&Size) Value(%bin(&Var 1 4))
EndPgm
Rather straightforward. The program simply takes the first four bytes of the parameter &Var (the 4-byte signed integer allocated size provided by the command processor), copies this value to the parameter &Size, and returns.
The source can be entered into QCLSRC member RTVVARSIZ and compiled using CRTBNDCL RTVVARSIZ.
A test program such as...
Pgm
Dcl Var(&Char13) Type(*Char) Len(13) Value('I am 13')
Dcl Var(&Char1000) Type(*Char) Len(1000) +
Value('I am 1000 bytes long')
Dcl Var(&VarSize) Type(*Int)
Dcl Var(&SizeChar) Type(*Char) Len(4)
RtvVarSiz Var(&Char13) Size(&VarSize)
ChgVar Var(%Bin(&SizeChar)) Value(&VarSize)
SndPgmMsg MsgID(Msg0301) MsgF(OurMsgs) MsgDta(&SizeChar) ToPgmQ(*Ext)
RtvVarSiz Var(&Char1000) Size(&VarSize)
ChgVar Var(%Bin(&SizeChar)) Value(&VarSize)
SndPgmMsg MsgID(Msg0301) MsgF(OurMsgs) MsgDta(&SizeChar) ToPgmQ(*Ext)
EndPgm
...should show results such as...
The size is 13.
The size is 1000.
To call the retrieve type API mentioned in the first paragraph of the article, without hard-coding a length value for the third parameter, with a format name of 'ABCD0100' and an Error Code Bytes Provided of 0, you would do this:
Pgm
Dcl Var(&RcvVar) Type(*Char) Len(80)
Dcl Var(&LenRcvVar) Type(*Int)
Dcl Var(&ErrCde) Type(*Int) Value(0)
RtvVarSiz Var(&RcvVar) Size(&LenRcvVar)
Call Pgm(SomeAPI) Parm(&RcvVar 'ABCD0100' &LenRcvVar &ErrCde)
EndPgm
With this method of calling the API, the Length of receiver variable parameter will automatically adjust to changes in the definition of the RcvVar parameter. We have one less thing to remember when maintaining our application.
I mentioned earlier that the above source for the RTVVARSIZ CPP is one possible set of source. The CPP as provided is written such that it will work on any of the current releases. But it may not be obvious to someone picking up the code just what is being done. Another approach to writing the CPP--and my preferred solution due to it being a bit more obvious (to me anyway!) about what is happening--would be this:
Pgm Parm(&Var &Size)
Dcl Var(&Var) Type(*Char) Len(32767)
Dcl Var(&VarSize) Type(*Int) Stg(*Defined) Len(4) +
DefVar(&Var)
Dcl Var(&Size) Type(*Int)
ChgVar Var(&Size) Value(&VarSize)
EndPgm
Here, we're using the V5R4 *DEFINED storage capability to define the parameter &Var as being a character variable but one in which the first four bytes represent the field &VarSize. The subfield &VarSize is then defined as a 4-byte signed integer, and the CPP simply changes the value of parameter &Size to the value of variable &VarSize. Performance-wise, there is no real difference between the two CPPs, but I find the intent of the CPP more obvious with the *DEFINED approach.
A third approach, and one that could leave a developer scratching his head for a while, would be this:
Pgm Parm(&Var &Size)
Dcl Var(&Var) Type(*Int)
Dcl Var(&Size) Type(*Int)
ChgVar Var(&Size) Value(&Var)
EndPgm
As parameters, when passed to a program, are passed by reference, there is no need to define the actual length of parameter &Var. Here, we simply define that portion of parameter &Var that we really care about (the initial signed integer representing the allocated length for the CL variable) and assign that value to the parameter &Size. Again, there is no real performance difference, but this code might cause many developers to jump to the conclusion that they are looking at the wrong source code. After all, what happened to the CL variable that was specified with the VAR keyword of RTVVARSIZ?
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online