Scott, thanks for the informative reply. I generally think I'm not so shabby at searching for answers online, but I think in this case I was plugging in the wrong search keywords or something.
This definitely makes sense to me. However everyone is saying "use a command," and I am, it's just that I'm passing along these parameters in a call that's submitted.
The submitted CL I'm working on was accepting a 45a parm, but afterward was changing the value to a value in the *LDA. This made no sense to me originally because I was thinking, "It was passed as a parameter, why are you overwriting it?" (To add to the confusion, it was using the LDA for the 'big' parm and every subsequent parm afterward). I now realize that it was to deal with this issue where the parameter is too big. However, this only had to be done with parameters that exceed 32a (no numerics in this case), and when doing that there's no reason to pass the field as a parameter as well. This is one of those situations where so much time would have been saved if there was at least some documentation about why it was using the *LDA to override a parameter to another value.
I plan on using the *LDA to pass these large parms, and remove them from the CL's actual parm list.
Again, thanks for hammering home the details on this topic. My exposure to CL has been relatively limited in my past jobs.
-Kurt
-----Original Message-----
From: midrange-l-bounces@xxxxxxxxxxxx [mailto:midrange-l-bounces@xxxxxxxxxxxx] On Behalf Of Scott Klement
Sent: Monday, December 07, 2009 5:38 PM
To: Midrange Systems Technical Discussion
Subject: Re: CL Parameters sharing memory?
Hi Kurt,
I have a CL that submits a CL, and it appears two of the parameters
are sharing a location in memory. Is there a limitation on the size
of a field that can be submitted to a CL?
They aren't 'sharing a location'. The caller (The OS/400 QCMD program
in this case) is defining the parameter to be 32 bytes long, and you are
trying to read 45 bytes from it. So your program isn't stopping after
32 bytes, it's reading an additional 13 byes that doesn't belong to that
parameter. You're reading whatever happens to be adjacent in memory.
In this case, that just happens to be the spot in memory where the other
parameter is stored. There's no guarantee that this will always be the
case.
I tried searching online but wasn't finding anything.
That surprises me, as this is one of the most frequently discussed
issues in the world of IBM i programming.
I can get around this situation by putting the big parameter last,
but I wanted to understand why this is happening.
Noooooooo! Don't do that.... You aren't solving the problem if you do
that. You are still reading more than the 32 bytes that are allocated
to the parameter, you just (by luck) happen to be getting space that's
not used for anything, so it's empty. This isn't solving your problem
because there's no guarantee that it'll never be used for anything.
The solution isn't to rearrange the parameters, the solution is to make
the parameter definitions match!
First of all, how do parameters work?
They work by sharing memory. Naturally a program that has variables has
a space in memory where those variables are stored. When it calls
another program and passes a parameter, it "shares" that variable. It
says to that other program "hey other program... you can read my
variable from memory location x'123456' -- whatever the memory location
happens to be -- and the new program simply uses that same spot in the
computer's memory for it's parameter.
For an RPG programmer, the easiest way to visualize this might be a data
structure. Think of your computer's memory ("main storage") as one big
huge data structure that's gigabytes long. All programs are sharing the
data structure.
I realize you aren't deliberately coding it this way, but THIS is what
you're ending up with:
PGM
DCL VAR(&VAR1) TYPE(*CHAR) LEN(32)
DCL VAR(&VAR2) TYPE(*CHAR) LEN(1)
CALL PGM2 PARM(&VAR1 &VAR2)
ENDPGM
PGM PARM(&PARM1 &PARM2)
DCL VAR(&VAR1) TYPE(*CHAR) LEN(45)
DCL VAR(&VAR2) TYPE(*CHAR) LEN(1)
... code here...
ENDPGM
Looking at your computer's main storage as one big data structure would
give you something like this:
D Main_Storage DS
.. data from other programs here...
D Pgm1_var1 1000 1032a
D Pgm1_var2 1033 1034a
.. more data from more programs here...
When PGM1 calls PGM2, it says to PGM2 "Hey, my first parameter is in
location 1000. My second parameter is in location 1033". So pgm2 uses
that location for it's input parameters.
D Main_Storage DS
.. data from other programs here...
D Pgm1_var1 1000 1032a
D Pgm1_var2 1033 1034a
.. more data from more programs here...
D Pgm2_parm1 1000 1045a
D Pgm2_parm2 1033 1034a
.. more data from more programs here...
Now maybe you can see the problem. The only thing passed is the
location of the variable, not the name, size or data type. just the
location, the "memory address" (which corresponds to the DS start
position in my analogy).
PGM1's first parameter was only 32 bytes long. But PGM2 defined it's
first parameter as 45 bytes long. Since PGM2 shares teh same location
(position 1000 in this example), it'll have the data from PGM1's
variable -- but it doesn't stop there. It keeps going beyond position
1032.. all the way out to 1045. Since the second parameter is in 1033,
it happens to include that data as well. It also includes more stuff in
positions 1034-1045 that may be unused by the OS right now, but may end
up getting used later, or in the future might be in use by another
variable (most likely if PGM1 is enhanced), etc, etc. It's never safe
to use a mismatched parameter in this way.
Now the obvious question is: WHY does it end up this way? Your program
isn't actually coding the first parameter as 32 *char, so why is this
happening?
The answer: Because that's the way the QCMD program interprets a
command string. You can't simply share memory when you submit a
program... it's too dangerous. The calling program might release the
memory before the called job finishes. Or might reuse that memory for
something else. It'd be a mess.
So SBMJOB doesn't work that way. Instead, it takes your parameters and
builds a command string. It passes that command string to the new job,
and the new job interprets the command string just like it would
interpret it if you had typed it at the command-line.
So if your PGM1 looks like this:
PGM
DCL VAR(&VAR1) TYPE(*CHAR) LEN(32)
DCL VAR(&VAR2) TYPE(*CHAR) LEN(1)
SBMJOB CMD(CALL PGM2 PARM(&VAR1 &VAR2))
ENDPGM
The system builds a command string consisting of
CALL PGM2 PARM('your data here' 'X')
One major caveat is that it trims blanks off of the parameters when it
does this.
In the newly created job, it interprets that command string. But it no
longer knows how the variables were defined in the calling program. So
it uses the same rules that it would use from the command-line. If the
parameter is character and less than 32 bytes, it gets left-aligned in a
32A field, with the remaining bytes set to blank. If it's greater than
32A, then the system creates it with the exact length of the parameter
data. (Which is really only useful if the length is provided somewhere
else...) If the parameter appears to be numeric, the command line makes
it a 15,5 packed. This is one of the general caveats of the CALL command...
The solution is to force the command line to provide a full 45 bytes for
the first parameter. The easiest/best way to do that is to eliminate
the CALL command (since you can't control it's variable sizes), and
instead create your own command (*CMD object):
source:
CMD PROMPT('Kurt''s Nifty Command')
PARM KWD(PARM1) TYPE(*CHAR) LEN(45)
PARM KWD(PARM2) TYPE(*CHAR) LEN(1)
build it with:
CRTCMD CMD(RUNPGM2) PGM(PGM2)
Then change PGM1 to look like this:
PGM
DCL VAR(&VAR1) TYPE(*CHAR) LEN(32)
DCL VAR(&VAR2) TYPE(*CHAR) LEN(1)
SBMJOB CMD(RUNPGM2 PARM1(&VAR1) PARM2(&VAR2))
ENDPGM
Now the system knows the first parameter needs to be 45 long (instead of
32) and will set it appropriately.
As an Amazon Associate we earn from qualifying purchases.