When prototyping APIs or your own subprocedures, you really need to be aware of two important facts that will greatly simplify both the acceptance level and the use of subprocedures and prototyped program calls.
The first thing you need to know is that not every parameter has to be modified. This myth that defining a parameter and not bothering to determine if CONST or not to CONST is necessary is completely amateur. In fact, it's affectively backward thinking. Why backward? Because if anything, you should assume all parameters are read-only and then justify why you need to modify any given parameter when the situation comes up. Otherwise, default to read-only.
How are read-only parameters defined on a prototyped call? The CONST keyword. Let's look at a commonly used API (although most people I meet don't realize they're using an API when they use this program) -- that API is QCMDEXC.
QCMDEXC has three parameters -- yes, three! Traditionally, programmers call QCMDEXC with only two parameters, and that is correct, but there is a third parameter that needs to be included when you prototype this API. Here's the basic starter code for a prototype of QCMDEXC:
H DFTACTGRP(*NO) OPTION(*SRCSTMT : *NODEBUGIO)
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 256A
D cmdLength 15P 5
Of course, this will work, but it isn't finished yet. First, we need to add the infamous (and seldom used) third parameter. The API documentation states that the third parameter should be specified when the command string contains double-byte character set (DBCS) data. Okay, it actually says it is for "IGC process control" and the only valid entry is IGC (in all upper case). We add this parameter, as follows:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 256A
D cmdLength 15P 5
D IGCflag 3A
At this point, the three parameters are specified but are still not strictly correct. Since the igcFlag (parameter 3) is optional, we have to add the OPTIONS(*NOPASS) keyword to the parameter definition, as follows:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 256A
D cmdLength 15P 5
D IGCflag 3A OPTIONS(*NOPASS)
The OPTIONS(*NOPASS) keyword allows you to define the parameter, and then not use it. It makes it optional and allows you to call QCMDEXC with only two parameters. Of course, once OPTIONS(*NOPASS) is used, any subsequent parameters must also be optional; you can't have the third parameter be optional but the fourth be required, for example.
While we have all the parameters included, there are still several errors in this prototype. The first is largely due to knowing about the read-only parameters and the CONST keyword, and that is the length of the first parameter (CMDSTRING) is actually permitted to be 32,702 bytes. Most people set it as 256 or smaller so that they can declare a field at 256 bytes, store a command in it, and then pass that variable to the API. The only reason it is set at 256 bytes is that the CONST keyword hasn't been specified. CONST benefits this parameter by making it read-only. When a parameter is CONST, the length of the value passed on the parameter can be up to the length of the parameter definition, which means it can be shorter. The correct length definition for this parameter would be as follows:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 32702A Const
D cmdLength 15P 5
D IGCflag 3A OPTIONS(*NOPASS)
Now that the length is increased and the CONST keyword is included, we can specify a field with a length up to 32,702 bytes that contains our command string. Because of CONST, we can specify a 256-byte field, a 2,000-byte field, or any size between 1 and 32,702. In addition, CONST allows us to specify a literal for the command string, so passing in 'ADDLIBLE MYLIB' without first storing it in a field is permitted.
The second parameter, the command-string length has a definition of a Packed(15,5) value. This is nearly a 30-year-old legacy size for numeric values passed on Command Entry. It allows CL programmers to build a command string inside a CL program, and then hard-code the command string's length on the second parameter. But in RPG IV, with prototyping we can do better.
By making the command-string length parameter as read-only or CONST parameter, we are allowed to pass a numeric value of any length and decimal positions. The compiler will move that length value into its own internal "temp variable" (which will be Packed(15,5)) and pass that temp variable to the API. So let's add the CONST keyword to the second parameter:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 32702A Const
D cmdLength 15P 5 Const
D IGCflag 3A OPTIONS(*NOPASS)
With the second parameter also defined as a CONST value, the ease of using QCMDEXC amplifies. You can now call QCMDEXC using the extended Factor 2 syntax with CALLP or in free-format syntax as follows:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 32702A Const
D cmdLength 15P 5
D IGCflag 3A OPTIONS(*NOPASS)
D myCmd S 50A Varying
/free
myCmd = 'ADDLIBLE RPGCODER';
qcmdexc(myCmd : %len(myCmd));
/end-free
By storing our command in a variable-length field, the %LEN built-in function can extract the length of the command string. We pass %LEN(MYCMD) to QCMDEXC, and the compiler figures out that the command is 17 characters in length and therefore passes 17 to the API. Can this get any cooler? Yes.
| RPG TnT: 101 Dynamic Tips 'n Techniques with RPG IV by Bob Cozzi is available now. | |
![]() |
The latest book from author Bob Cozzi is 300 pages, containing 101 example RPG IV Tips and Techniques for everyday programming tasks, from date calculations, to regular express searches, to using APIs. Cozzi wrote down every cool technique he's found over the years, updated them, and consolidated them into this compact book that is a great desktop companion -- and it includes full example source code. Order your copy today. |
To help the compiler optimize things, another keyword can be added to the QCMDEXC prototype. This keyword is applied to the command string parameter and is OPTIONS(*VARSIZE).
If you recall, the CONST keyword allows the parameter to accept values with lengths from 1 up to the length of the command. This is okay, but the compiler normally generates a temp variable that is the same size as the parameter. So we end up with a huge 32,702-byte temp variable being declared.
To allow the compiler to optimize things a bit, we need to add the OPTIONS(*VARSIZE) keyword. This keyword, which applies to both CONST and non-CONST character parameters, allows the parameter to accept a value of any length. That is, the prototype doesn't kick out values that don't match the parameter definition. Here's what the completed prototype for QCMDEXC looks like:
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 32702A Const OPTIONS(*VARSIZE)
D cmdLength 15P 5 Const
D IGCflag 3A Const OPTIONS(*NOPASS)
If you want to be really professionally, stick this in a /COPY member and wrap some compiler directives around it. I also tend to change the name of the prototype to RUNCMD since I never really liked the QCMDEXC name; here's the final version as I use it in my own code.
/IF NOT DEFINED(QCMDEXC)
/DEFINE QCMDEXC
D QCmdExc PR extPgm('QCMDEXC')
D cmdString 32702A Const OPTIONS(*VARSIZE)
D cmdLength 15P 5 Const
D IGCflag 3A Const OPTIONS(*NOPASS)
/ENDIF
Bob Cozzi is currently touring the U.S. with his one-day RPG World Academy seminar on RPG Application Modernization entitled Subprocedures Modules, and Service Programs. Visit www.RPGWorld.com for details, dates and locations.
D RunCmd PR 10U 0 ExtProc('system') D CmdString * Value Options(*String) /free RunCmd('ADDLIBLE RPGCODER'); /end-free