Optional Parameters and CL Procedures: Valuable Info from IBM
September 22, 2004 Hey, Ted:
In “Optional Parameters and CL procedures,” you said that ILE CL procedures can handle unpassed parameters by monitoring for message MCH3601. While it’s perfectly safe to test for omitted parameters (those for which the caller specified the special value *OMIT) by monitoring for MCH3601, it’s dangerous to depend on MCH3601 for unpassed parameters. RTV-type commands are also safe; they work like *OMIT parameters by passing null pointers.
It is possible for the system to fail to send MCH3601 when a parameter was not passed. This failure occurs when the system still has a pointer to a parameter from a previous call. If you then change that parameter, you will be changing the storage, which the old pointer happens to reference.
Here’s a little test to prove it. The caller is an RPG program.
D parmclle pr D p1 1a D p2 1a options(*nopass) D v1 s 1a inz('x') D v2 s 1a inz('y') c callp parmclle (v1) c eval v1 = 'm' c eval v2 = 'n' c callp parmclle (v1 : v2) c eval v1 = 'p' c eval v2 = 'q' c callp parmclle (v1) * v2 should not have been changed c eval *inlr = '1'
Here’s the called ILE CL procedure.
pgm parm(&p1 &p2) dcl &p1 type(*char) len(1) dcl &p2 type(*char) len(1) chgvar &p1 'a' monmsg mch3601 exec(sndpgmmsg 'p1 not passed') chgvar &p2 'b' monmsg mch3601 exec(sndpgmmsg 'p2 not passed')
Here are the compilation instructions I used to create a program from the two modules.
crtclmod qtemp/parmclle crtrpgmod qtemp/parmrpgle crtpgm qtemp/pimple + module(qtemp/parmrpgle qtemp/parmclle) + endmod(parmrpgle)
The first call behaves as expected; the CL procedure receives message MCH3601. Here’s the debugger output from the RPG module.
> EVAL v1 V1 = 'a' > EVAL v2 V2 = 'y'
The job log contains the message “p2 not passed.”
The second call passes both parameters. Two pointers get put on the call stack.
> EVAL v1 V1 = 'a' > EVAL v2 V2 = 'b'
The third call is like the first call, but the second pointer is still on the stack from the previous call, so the CL procedure behaves as if it received two parameters.
> EVAL v1 V1 = 'a' > EVAL v2 V2 = 'b'
But V2 should still have the value q. In this case, it’s my own storage that got corrupted by the call, but it’s very common to have completely unrelated pointers on the stack.
My first attempt at this test used a DSPLY operation after each call; with that version, I didn’t get the MCH3601 in the CL, but the V2 variable didn’t get updated, either. I have no idea what did get updated by the bad third call. It’s actually a bit dangerous even to do this test: you really don’t know what you might be changing when you do this.
There are a couple of options when you want to have a CL procedure with optional values, to prevent having to change all the callers.
1. Create an RPG wrapper procedure with the name of the old CL procedure. Have the RPG wrapper call through to the new CL, passing the number of passed parameters in the first parameter, or passing *OMIT in the unpassed parameters. You would need several calls, depending on the number of parameters.
if %parms = 0; callp cl (*omit : *omit : *omit); elseif %parms = 1; callp cl (p1 : *omit : *omit); elseif %parms = 2; callp cl (p1 : p2 : *omit); elseif ... else; callp error ('Too many parms'); endif;
2. Create a new ILE CL procedure with a new name, and change the old one to call through to the new one, passing *OMIT for the new parameters. Callers would have to remember to use the new name.
RPG Compiler Development
IBM Toronto Lab
Thanks yet again to Barbara Morris for sharing inside information with the iSeries community. I did not run her test; I didn’t want to take a chance of messing up the NetShare/400 machine.