Brief: Parameter usage and programming go hand in hand. Yet, when it comes to submitting jobs to batch through the Submit Job (SBMJOB) command, parameters can cause you a lot of grief. This article will explain how OS/400 handles various parameter types and will illustrate several techniques to eliminate potential problems when parameters are passed through the SBMJOB command.
One of the greatest ironies I find in the OS/400 operating system is that the most common CL command to start programs-CALL-and the most common command to start a batch job-SBMJOB-do not work well together. I will show you how to avoid trouble when SBMJOB runs a CALL command.
The problem is that variables are passed as literals to the SBMJOB command through the CALL command. For clarification, I'll explain how CL programs handle parameters.
CALL and Literal Parameters
When a CL program calls another program, all parameters that are CL variables are passed to the called program exactly as they are defined in the calling program. Consider these fragments of code from a CL program.
DCL &ACTION *CHAR 8 DCL &ACCOUNT_NO *DEC (5 0) ... CALL PGMB PARM(&ACTION &ACCOUNT_NO)
PGMB will have no problem using the data passed to it if it defines entry parameters of the same type and length as the calling program. For instance, if PGMB is written in RPG, PGMB receives the parameters in the following way:
*7890123456789012345678901234567890123456789012345 C *ENTRY PLIST C PARM ACTION 8 C PARM ACCTNO 50
However, when a parameter is a literal, CL reformats it according to three rules.
1. A decimal literal is always formatted as a 15-digit number with five decimal places.
2. A character literal less than 32 bytes long is padded with blanks to a length of 32.
3. A character literal 32 bytes or longer is left as is.
For example, consider this code from a program we'll call PGMC.
CALL PGM(PGMD) + PARM(500 GO + 'XYZ CORPORATION + AGED ACCOUNTS RECEIVABLE')
PGMC passes a 15-digit, packed-decimal number with the value 0000000500.00000, a 32-character string with the value GO and 30 blanks, and a 40-character string with the value: 'XYZ CORPORATION AGED ACCOUNTS RECEIVABLE' PGMD should declare three parameters in a compatible manner:
*7890123456789012345678901234567890123456789012345 C *ENTRY PLIST C PARM CUTOFF 155 C PARM ACTION 6 C PARM TITLE 40
Although the CALL parameter passes the second parameter as 32 bytes, the called program can define the parameter as any length up to 32 bytes.
CALL does not actually pass the value of each parameter to the called program; it passes the memory address of each parameter. When a CL program calls another program, it furnishes the address of each parameter declared as a variable to the called program. But when the calling program finds a literal parameter, there is no CL variable whose address it can pass to the called program. Therefore, it makes a copy of the literal, reformatting its value according to the rules given above, and passes the address of the copy to the called program.
Using CALL and the SBMJOB Command
It's important to understand how CALL formats literal parameters before passing their addresses to another program, because that's how CALL formats all parameters, variable and literal, when it's used in the CMD parameter of the SBMJOB command. (All examples in this article will use the CMD parameter, but the same principles apply to the RQSDTA parameter.)
Here's the program fragment we saw earlier, except that now the program submits a job to batch, instead of directly calling program PGMB.
DCL &ACTION *CHAR 8 DCL &ACCOUNT_NO *DEC (5 0) ... SBMJOB CMD(CALL PGMB PARM(&ACTION &ACCOUNT_NO)) ...
When PGMB begins execution, it will receive the addresses of a 32-byte action code and a 15-digit account number with five decimal places, even though this is not the way the submitting program defined them.
Because the command sent to a submitted job is really a request message, the submitted job will interpret the request message the same way an interactive job interprets a command typed on a command line: it will not access the submitting CL program's variables.
Now you understand why SBMJOB and CALL do not cooperate with respect to passing parameters.
The fact that the system pads short character parameters to a length of 32 will never cause you a problem. Passing a character literal longer than 32 bytes causes problems only if it is passed as a literal with a length less than the length defined in the called program. The real problem is with decimal data.
There are methods to work around decimal data problems. I'd like to present five common ones to you.
Method 1: Declare Numeric Parameters In CALL Format
Surrender to CALL's demands and declare decimal parameters in the called program as 15 digits with five decimal positions to the left of the decimal point. This is the "if you can't beat 'em, join 'em" philosophy.
This method works unless you pass a decimal value with more than 10 positions to the left of the decimal point, or a decimal value with more than five decimal positions to the right.
The worst thing about this method is that it puts the caller, rather than the called program, in the driver's seat and thus violates a basic programming rule. Each module, whether it's a program or a subroutine within a program, should define what data it needs to do its job. Modules should pass data according to the called module's definition.
Method 2: Convert numeric values to character data
Place the value of a decimal number into a character variable, which is passed to the called program. The called program must receive all parameters as character data.
Let's look at the previous example using this method.
DCL &ACTION *CHAR 8 DCL &ACCOUNT_NO *DEC (5 0) DCL &ACCOUNT_X *CHAR 5 ... CHGVAR VAR(&ACCOUNT_X) VALUE(&ACCOUNT_NO) SBMJOB CMD(CALL PGM(PGMB) PARM(&ACTION + &ACCOUNT_X))...
At the time the SBMJOB command is executed, the variables would have these values:
&ACCOUNT_NO 19232 (X'19232F') &ACCOUNT_X 19232 (X'F1F9F2F3F2')
The called program, PGMB, must receive the account number as character data or zoned-decimal data. If it receives the account number as character data, it must move the parameter to a numeric field.
*7890123456789012345678901234567890123456789012345 C *ENTRY PLIST C PARM ACTION 8 C PARM ACCTXX 5 C MOVE ACCTXX ACCTNO 50
This commonly used method works well, except with negative numbers and numbers with decimal positions. When copying a decimal value to a character variable, the CL CHGVAR command can store a decimal point and/or a leading minus sign. Let's say we want to pass a seven digit number with two decimal positions through a character variable.
DCL &GROSS_AMT *DEC (7 2) DCL &ALPHA_AMT *CHAR 9 ... CHGVAR VAR(&ALPHA_AMT) VALUE(&GROSS_AMT) SBMJOB CMD(CALL PGMF PARM(&ALPHA_AMT)) ...
If the called program is written in CL, this works well. CHGVAR will de-edit &AMOUNT_P-which surely contains a decimal point and may contain a leading minus sign-and copy the correct value to &GROSS.
PGM PARM(&AMOUNT_P) DCL &AMOUNT_P *CHAR 9 DCL &GROSS *DEC (7 2) CHGVAR VAR(&GROSS) VALUE(&AMOUNT_P) ...
It is more difficult if the called program (PGMF) is written in RPG, which has no de-editing features.
As with the first method, the caller, not the called program, determines the format of the passed data. This method is not suitable if you prefer that numbers be passed as numbers as opposed to edited numeric data stored in character variables.
Method 3: Pass data through another object
Use some other object, such as a data area, data queue, user space, or file, to pass the data. Anyone who has spent time programming the S/34 or S/36 will be very familiar with this approach, since programming these systems requires heavy use of the local data area.
Using this method, you avoid problems of decimal parameters and character parameters greater than 32 bytes. In addition, more data can be passed to the called program than with the previous two methods.
Disadvantages of this method are that another object is required (using parameters requires no additional objects); more code may be required to store and receive the data (e.g., a call to SNDDTAQ or to the user space APIs); and programs may compete for the same resources (e.g., the same data area or the same positions of the local data area).
Method 4: Passing hexadecimal literals
To pass a numeric constant in any format other than a length of 15 and a precision of 5, code the constant in hexadecimal format. The following CALL command shows how to pass the value 15.7 to a program variable that is declared as LEN(5 2):
CALL PGM(PGMB) PARM(GO X'01570F')
This special format is actually how the AS/400 represents a packed decimal value. It requires appending a sign character-F for a positive value and D for negative value-to the end of the character string, and the number of characters must always be even. This is not a method for carrying out production work, but it is a good way to test a program from a command line.
It is possible to use this method to pass decimal values through the parameters of a CALL command executed by SBMJOB. It requires a program that will copy a decimal value to a character variable from left to right, one byte at a time, until it finds a byte with a digit of D or F. The character variable is used as the parameter in place of the decimal variable.
I'll leave this as an exercise for the interested reader. Using this technique can get you into trouble, so I do not recommend it for production work. Let's move along to another method of solving our problem.
Method 5: Define a command
Submit a command that you write yourself, rather than submitting a CALL. PGMB's entry parameter list looks like this:
*7890123456789012345678901234567890123456789012345 C *ENTRY PLIST C PARM ACTION 8 C PARM ACCTNO 50
1 illustrates the command source I'll call CMDB for a command to execute PGMB.
Figure 1 illustrates the command source I'll call CMDB for a command to execute PGMB.
Here's how the command would be used by SBMJOB.
DCL &ACCOUNT_NO *DEC (5 0) DCL &ACTION *CHAR 8 ... SBMJOB CMD(CMDB ACTION(&ACTION) ACCOUNT(&ACCOUNT_NO)...
The beauty of this approach is that CMDB, unlike CALL, knows what format the submitted program uses for each parameter and automatically reformats the data accordingly. No matter how the data is defined in the submitting job, the command processor will reformat it according to the command definition. This completely eliminates the conflict inherent in the use of CALL in SBMJOB commands.
Other advantages are:
o You don't have to pass any parameter for which you've defined a default value in the command source.
o All the power of the command processor, such as validity checking, is available to you.
o The format of a parameter does not have to be the same in the submitting program and the called program.
o You can change the size of a parameter in the command and the called program
Now, let's take a look at a technique that will allow you to prompt the user for information prior to submitting a command to batch through the SBMJOB command.
Prompt-and-Submit Jobs
Using your own commands can help with a common task-prompting for information before submitting a job to run in batch. 2 contains the source for a command I'll call CMDA. 3 illustrates its command processing program, PGMA.
Using your own commands can help with a common task-prompting for information before submitting a job to run in batch. Figure 2 contains the source for a command I'll call CMDA. Figure 3 illustrates its command processing program, PGMA.
PGMA's first task is to determine whether it is running in an interactive job or in a batch job. If PGMA is running interactively (&JOBTYPE = '1') it submits itself to batch by submitting CMDA. The interactive invocation of PGMA ends and a batch job running PGMA begins.
PGMA runs PGMB, and has no parameter conflicts because the CALL is issued within a program, not as part of SBMJOB.
There is no mechanism in this CL program to prompt the user to enter an action code and account number, because OS/400 has a way to handle that sort of thing. You're already familiar with this method if you've ever typed a command and pressed the F4 key.
To run this job, a user could type CMDA at a command line and press the F4 key, but a better way is to assign the following command to a menu option.
? CMDA ?-SBMTOJOBQ(Y)
The leading question mark, like the F4 key, causes OS/400 to prompt for command parameters. The "?-" combination doesn't let the user see or modify the SBMTOJOBQ parameter, forcing the job to submit to batch. Of course, you, the programmer, may want to run the job interactively for testing or debugging. SBMTOJOBQ gives you a way to do that.
You could allow different capabilities to different users by giving the users distinct versions of the command on their menus. 4 shows some sample command prompting for three fictitious users. Bob can use any action code, but Sally and Leroy are each limited to only one action. All users can choose an account number; none are allowed to run the job interactively.
You could allow different capabilities to different users by giving the users distinct versions of the command on their menus. Figure 4 shows some sample command prompting for three fictitious users. Bob can use any action code, but Sally and Leroy are each limited to only one action. All users can choose an account number; none are allowed to run the job interactively.
Writing your own command not only solves the problem of passing parameters, but also relieves you of the chore of prompting for parameter values.
The Verdict, Please
All five of these methods have their merits. A good programmer should understand all of them and use each one when it is appropriate. I would encourage you to not use method 1 or method 4 for production work. Method 1 is too limited, and method 4 is too risky.
It's easier to continue to use CALL when the submitted program has no parameters or has only character parameters 32 bytes or less in length. My favorite by far is the fifth one-submitting my own commands rather than CALLs. It is the only one that consistently allows straightforward submission of jobs to batch without undesirable side effects. If you are in the habit of starting all your programs with CALL, let me encourage you to begin to write your own commands.
Ted Holt is an associate technical editor for Midrange Computing.
Passing Parameters with the SBMJOB Command
Figure 1 User-written Command for Command Processing
Program PGMB CMD PROMPT('Print report by account number') PARM KWD(ACTION) TYPE(*CHAR) LEN(8) RSTD(*YES) + DFT(GO) VALUES(GO REPRINT REBUILD CLOSE) + EXPR(*YES) PROMPT('Action') PARM KWD(ACCOUNT) TYPE(*DEC) LEN(5 0) MIN(1) + PROMPT('Account number')
Passing Parameters with the SBMJOB Command
Figure 2 User-written Command with Job Queue Option
CMD PROMPT('Print report by account number') PARM KWD(ACTION) TYPE(*CHAR) LEN(8) RSTD(*YES) + DFT(GO) VALUES(GO REPRINT REBUILD CLOSE) + EXPR(*YES) PROMPT('Action') PARM KWD(ACCOUNT) TYPE(*DEC) LEN(5 0) MIN(1) + PROMPT('Account number') PARM KWD(SBMTOJOBQ) TYPE(*CHAR) LEN(1) RSTD(*YES) + DFT(Y) VALUES(Y N) PROMPT('Submit to job + queue?')
Passing Parameters with the SBMJOB Command
Figure 3 Command Processing Program PGMA for
Command CMDA PGM PARM(&ACTION &ACCOUNT_NO &SBMTOJOBQ) DCL VAR(&ACCOUNT_NO) TYPE(*DEC) LEN(5 0) DCL VAR(&ACTION) TYPE(*CHAR) LEN(8) DCL VAR(&JOBTYPE) TYPE(*CHAR) LEN(1) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(132) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGFLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&SBMTOJOBQ) TYPE(*CHAR) LEN(1) RTVJOBA TYPE(&JOBTYPE) IF COND(&JOBTYPE *EQ '1' *AND &SBMTOJOBQ *NE + 'N') THEN(DO) SBMJOB CMD(CMDA ACTION(&ACTION) + ACCOUNT(&ACCOUNT_NO)) JOB(MYJOB) GOTO CMDLBL(FWDMSG) ENDDO CALL PGM(PGMB) PARM(&ACTION &ACCOUNT_NO) RETURN FWDMSG: RCVMSG MSGDTA(&MSGDTA) MSGID(&MSGID) MSGF(&MSGF) + SNDMSGFLIB(&MSGFLIB) IF COND(&MSGID *NE ' ') THEN(DO) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) + MSGDTA(&MSGDTA) MONMSG MSGID(CPF2469) GOTO CMDLBL(FWDMSG) ENDDO ENDPGM
Passing Parameters with the SBMJOB Command
Figure 4 Sample User Prompting
User Command Bob ?CMDA ?-SBMTOJOBQ(Y) Sally ?CMDA ?-ACTION(REBUILD) ?-SBMTOJOBQ(Y) Leroy ?CMDA ?-ACTION(CLOSE) ?-SBMTOJOBQ(Y)
LATEST COMMENTS
MC Press Online