This month we decided to look at "one of those questions." This particular question crops up repeatedly in online support forums, and probably with monotonous regularity on IBM's support line. One of the reasons it arises so frequently is that it can materialize itself in many ways. For example, one person might ask: "Why do I get decimal data errors when I call my program from the command line? It works perfectly when I call it from another program!" While another might pose this question: "Why are there garbage characters at the end of the parameters that I entered on the call?" It's hardly surprising, therefore, that even the most carefully worded Google search may fail to reveal an answer.
Why such variety? Because the underlying issue relates to the way parameters are passed on any call that's handled by the command line processor. Among others, this includes:
- A simple CALL from the 5250 command line
- A CALL embedded in a SBMJOB command
- CALLs executed through QCMDEXC or the C 'system' function
Let's begin by posing a simple question. If I enter the following CALL statement on the command line, how should I code the *Entry PLIST (or Procedure Interface) in program TEST in order to handle the parameters correctly?
CALL PGM(TEST) PARM('abcd' 1.2)
If you answered by saying that the first parameter should be defined as being a character field of at least four characters in length, and the second as a 15,5 packed decimal field -- then our congratulations, you've passed the first test. If this wasn't your answer, read on and all will be explained. Even if you got the right answer, you may still want to read on because there's at least one more twist in this tale.
So why is this the right answer? To understand this, you first have to remind yourself of the mechanics of a program call. When we call a program and pass it a parameter, we're actually giving it a pointer to the first byte of the parameter data. We aren't actually passing the data itself. When we call one program from another, the calling program defines the type and size of the data being passed, and as long as the two programs agree with each other as to the definition of the parameters, there should be no problem -- at least with the parameter passing. (Note: If you want to get deeper into the topic of parameter passing, check out our article "RPG IV Prototypes") However, when passing a parameter from the command line, the command processor has no such knowledge, and since the system provides no way of interrogating the called program so that its parameter requirements can be determined, it has no choice but to make assumptions about the characteristics of the parameter(s). So exactly what assumptions will the command processor make? It depends on the parameters you entered on the command. The base assumptions are that:
- If the parameter is a character string then it's either 32 characters in length or the length of the actual string passed, whichever is greater. You can see from this that the answer of four characters given earlier is technically incorrect, but it wouldn't cause any problems since our program would simply see the first four characters. Of course if we entered a 10-character parameter, only the first four would be "seen" by the called program.
- If the parameter is numeric, the processor automatically assumes that it's a packed field with a length of 15 digits and five decimal places. It will stick rigidly to this definition even if your input parameter defines more than five decimal places. Any decimal places in excess of five are simply discarded without warning. If, however, you attempt to enter more than 10 integer positions, you'll receive the error message "Numeric value for parameter PARM is not valid."
Having established what it assumes are the required type and length of the parameter(s), the processor then allocates temporary storage for them and copies the data from the command into it. It's a pointer to this temporary storage that's ultimately passed to your program.
As noted above, numeric fields passed from the command line are always treated as 15,5 packed -- no exceptions. As annoying as that may be, it's at least a simple rule that we can deal with. However, now that you understand the basic principles involved, you can hopefully see that the assumptions made for character fields can cause a few other problems.
For instance, you might have been successfully calling a program that takes an IFS file name as a parameter from the command line for months. Yet today when you called the program, it insisted that there was no such file! How could that be? An investigation of the program in question might reveal that it expected a 50-character file name. However, for some reason today's input file name was only 36 characters long. Given the "assumption formula" we just looked at, how big would the command processor have made the temporary field? That's right -- 36 characters long. As a result, when the called program tried to use the field that it viewed as 50 characters long, there was a very high probability that it would encounter garbage in the last 14 characters. The result being that it tried to open a file with a very strange name indeed!
If you don't believe us (or are simply convinced that you've seen cases where this information didn't hold true), you may find the following small program useful for experimentation:
D CallMe Pr ExtPgm('CALLME')
D P1 52a
D P2 52a
D CallMe PI
D P1 52a
D P2 52a
D wait s 1a
/Free
dsply ('parms = ' + %char(%Parms));
dsply p1;
dsply p2;
dsply 'Waiting' '' wait;
*inlr = *On;
Take a look at the contents of P1 and P2 fields in debug when you pass a numeric value. To really see what's going on you'll have to view the data in hex. If you don't know how to do this, it's really very simple, just append ":x" to the debugger's eval command (e.g., EVAL P1:x). When studying the program's behavior in debug, notice that any character parameters you pass are always padded with blanks up to the standard length of 32 characters but will not be padded at all if they exceed 32 characters. Numeric parameters will always be padded with leading and/or trailing zeros if necessary and will always end with a hex "F" or a "D" if the value is negative.
How can you deal with this situation and ensure that a program can be used consistently from the command line? If you search through the responses in Internet forums, you'll encounter many suggestions ranging from the sublime to...:
- Enter numeric parameters as character strings (this only works if the called program is expecting zoned numerics and is a real pain if decimal places are involved)
- Enter numeric parameters in hex (that's really user-friendly!)
- Type character parameters as one character longer than the maximum (thereby ensuring that your trailing blanks are included)
- Modify the called program to scan for multiple spaces and use a substring of the input parameter based on the result (only works in limited circumstances -- e.g. if multiple embedded spaces can't exist within the parameter value)
- And other even more bizarre options!
In reality there's only one certain way to handle the situation, and that's to build a command interface for the called program. That way you'll be assured that the parameters will always be passed in the way you want -- no matter how they're entered on the command line. We won't go further into the creation of commands etc., as that's a whole topic on its own. However, you'll find a number of examples as well as references to the related IBM documentation online (faq.midrange.com/data/cache/57.html).
In addition to knowing the answer to the questions we identified at the beginning of the article, we hope that we've helped you understand why a few other "simple calls" didn't work out quite the way that you expected them to.
If you have any other "why does it do..." type of questions, let us know and we'll try to answer them in future articles.