Guru: Learn %PARMS! Solve Two CL Problems!
October 8, 2018 Ted Holt
Those wonderful people at IBM have done it yet again! They have gladdened my existence with new CL functionality that solves two problems, and I will never have to face those problems again. Let me tell you about the new %PARMS built-in function.
The %PARMS function returns the number of parameters that are passed into a CL procedure (i.e. a CL program or a CL module). In the past, I have monitored for message MCH3601. That works in some situations, but not in all. The %PARMS function gives me an unambiguous way to know whether a parameter was passed or not. IBM released it for IBM i 7.3 in PTF SI66721.
I’ll gladly illustrate the two problems and show how to use %PARMS to avoid them.
The Program Problem
Consider program PARMTEST:
pgm (&p1 &p2 &p3) dcl &p1 *char 3 dcl &p2 *char 3 dcl &p3 *char 3 dcl &msg *char 14 ChgVar &msg ('/' *cat &p1 *cat ' ' *cat + &p2 *cat ' ' *cat + &p3 *cat '/') SndPgmMsg msg(&msg) MsgType(*comp)
If I call this program and pass it three parameters, I see a message that contains the values I passed in.
call PARMTEST (AAA BBB CCC) /AAA BBB CCC/
But if I call it with fewer than three parameters, I see something else.
call PARMTEST (AAA BBB) MCH3601 Pointer not set for location referenced. CPF9999 Function check. MCH3601 unmonitored by PARMTEST at statement 0000000900, instruction X'0000'. CPA0702 MCH3601 received by procedure PARMTEST. (C D I R)
The CHGVAR command canceled because I did not supply a parameter value. Here’s how %PARMS allows me to avoid that problem.
pgm (&p1 &p2 &p3) dcl &p1 *char 3 dcl &p2 *char 3 dcl &p3 *char 3 dcl &x1 *char 3 value('DFT') dcl &x2 *char 3 value('DFT') dcl &x3 *char 3 value('DFT') dcl &msg *char 14 if (%parms *ge 1) then(ChgVar &x1 &p1) if (%parms *ge 2) then(ChgVar &x2 &p2) if (%parms *ge 3) then(ChgVar &x3 &p3) ChgVar &msg ('/' *cat &x1 *cat ' ' *cat + &x2 *cat ' ' *cat + &x3 *cat '/') SndPgmMsg msg(&msg) MsgType(*comp)
I rewrote the code so that the CHGVAR command no longer refers to the parameter variables, but to local variables instead. I load the local variables from the parameters, but only if those parameters were passed to the program. Otherwise, the local variables have default values.
call PARMTEST (AAA BBB CCC) /AAA BBB CCC/ call PARMTEST (AAA BBB) /AAA BBB DFT/ call PARMTEST (AAA) /AAA DFT DFT/ call PARMTEST /DFT DFT DFT/
Problem number one is solved. Now let me show you problem number two.
The Module Problem
Let’s consider the same code, except that PARMTEST is compiled as a module, not a program. Here’s the source code for module CALLER, which calls module PARMTEST.
pgm CallPrc PARMTEST (AAA BBB CCC) CallPrc PARMTEST (DDD EEE) CallPrc PARMTEST (FFF) CallPrc PARMTEST endpgm
I use the Create CL Module (CRTCLMOD) command (option 15 in PDM) to create both modules and CRTPGM to bind them into one program.
CRTCLMOD MODULE(MYLIB/PARMTEST) + SRCFILE(QCLSRC) SRCMBR(PARMTEST) CRTCLMOD MODULE(MYLIB/CALLER) + SRCFILE(QCLSRC) SRCMBR(CALLER) CRTPGM PGM(CALLER) MODULE(CALLER PARMTEST) + ACTGRP(*NEW)
Now I have a program. Here’s what happens when I call it.
CALL CALLER /AAA BBB CCC/ /DDD EEE CCC/ /FFF EEE CCC/ /FFF EEE CCC/
CALLER thinks I passed all three parameters, even when I didn’t. When I don’t supply a parameter, CALLER uses the data from an earlier call, as the pointers to that data just happen to still be in memory. This can certainly lead to erroneous, unpredictable, undesirable results.
However, if I use the new version of the PARMTEST module instead, I see this:
CALL CALLER /AAA BBB CCC/ /DDD EEE DFT/ /FFF DFT DFT/ /DFT DFT DFT/
Thanks to %PARMS, I was able to use default values for unpassed parameters.
Will I ever monitor for MCH3601 again? Who knows? It doesn’t look very likely that I will.