Change commands with the Command Analyzer Change exit point.
This article is the fourth in a series that discusses how to use proxy commands and the Command Analyzer Change exit point. In the last article, we reviewed the validation-checking that an exit program should perform prior to processing command-related data passed to the program. In this article, we will look at the actual processing of this command-related data.
Before reading this article, you may find it beneficial to review the three prior articles:
- "Use Your Own Names for Supplied Commands"
- "Overriding Commands and Their Parameter Values"
- "Understanding the CHKKILL Program"
For space reasons, the source for program CHKKILL is not being repeated in this article, so you may want to refer to the previous article, "Overriding Commands and Their Parameter Values."
Having verified that the exit program was called by the QIBM_QCA_CHG_COMMAND exit point, that the format of the data passed conforms to format CHGC0100, and that the command being processed is ENDJOB, CHKKILL then determines if any proxy commands were used. If the number of proxy commands in the proxy chain, field &Nbr_Prx, is equal to 0 then CHKKILL can simply return as the KILL proxy was clearly not used.
Having determined that one or more proxy commands were used in reaching the ENDJOB command, the CHKKILL program now accesses the proxy chain passed by the exit point in the &Chg_Info parameter. To do this, CHKKILL sets the pointer variable &Ptr_Prx to the address of the &Chg_Info structure and then adds the offset to the first proxy command entry, &Off_Prx, to &Ptr_Prx. As the structure &Prx_Struct defines one proxy command entry and is declared as being *BASED on the pointer &Ptr_Prx, the &Prx_Name and &Prx_Lib subfields of the &Prx_Struct structure now contain the values of the first proxy entry's proxy command name and proxy command library, respectively.
But prior to examining these values, CHKKILL first determines the size of each proxy command entry by using the RTVVARSIZ command. This is done because we need to know the size of each entry to step from the first entry to the second, third, etc. The size of each proxy command entry is determined by using the RTVVARSIZ command and is stored in the variable &Size_PrxID.
CHKKILL then enters into a DoFor loop to process all of the proxy entries (&Nbr_Prx). If the proxy command name is not KILL, CHKKILL adds the size of one entry (&Size_PrxID) to the pointer &Ptr_Prx and simply moves to the next proxy command entry in the chain. If all proxy entries in the proxy chain have been examined and the KILL proxy was not found, CHKKILL simply returns to the exit point. If the proxy command name is KILL, CHKKILL looks to see if the keyword OPTION was specified with the command. If OPTION was specified by the user, we do not want CHKKILL to override the user-provided value. We only want CHKKILL to use OPTION(*IMMED) as a default.
To perform this check, CHKKILL uses the Scan for String Pattern (QCLSCAN) API, which is documented here. This API has a restriction that the string to be searched (in our case, the initial command string) cannot be greater than 999 bytes in length. We could work around this limitation, but I elected not to in the sample program as 999 bytes is sufficient for the vast majority of command strings you are likely to run into. If the initial command string is greater than 999 bytes in length (field &Len_InlCmd of the &Chg_Info structure), CHKKILL sends message PRX1004 using the SndErrMsg subroutine. PRX1004 does return the actual command string length as a replacement variable.
If the initial command string length is less than or equal to 999 bytes, CHKKILL accesses the initial command string. To do this, CHKKILL sets the pointer variable &Ptr_InlCmd to the address of the &Chg_Info structure and then adds the offset to the initial command string, &Off_InlCmd, to &Ptr_InlCmd. As the variable &Inl_Cmd is declared as being *BASED on the pointer &Ptr_InlCmd, &Inl_Cmd now contains the value of the initial command string in the first &Len_InlCmd bytes of &Inl_Cmd.
CHKKILL now calls the QCLSCAN API, searching for the pattern 'OPTION('. If the pattern 'OPTION(' is found within the initial string, indicated by a &Result value greater than 0, CHKKILL simply returns as we do not want to override the user-specified OPTION value.
If the pattern 'OPTION(' is not found, indicated by a &Result value of 0, or the initial command string isn't long enough to even hold the value 'OPTION(', indicated by a &Result value of -1, CHKKILL then determines if the command string can be modified. The ability to change the initial command string is controlled by the variable &Alw_Chg. There are certain conditions, discussed in the Command Analyzer Change exit documentation, where the exit program is not allowed to modify the initial command string. If CHKKILL has determined that it should add OPTION(*IMMED) to the initial command string but is not allowed to, then message PRX1005 is sent using SndErrMsg.
If a change to the initial command string is permitted, CHKKILL then copies the initial command string &Inl_Cmd to the &New_Cmd parameter and concatenates the value 'OPTION(*IMMED)' to this copied value. CHKKILL then updates the &Len_NewCmd parameter. This parameter, which reflects the length of the new command string, is set to the length of the initial command string, &Len_InlCmd, plus the length of the 'OPTION(*IMMED)' literal, plus a separating blank as we used *BCat for the concatenation, a total of 15 bytes. When the exit program CHKKILL returns to the Command Analyzer Change exit point, it is this &New_Cmd that will be run rather than the initial command passed to CHKKILL.
While the logic used for setting &Len_NewCmd works, there is a better approach. The problem with the shown solution is that we now have hard-coded dependencies that span two lines of CL source code. In one line, we are concatenating the literal value 'OPTIONS(*IMMED)', and in a separate line, setting the length of this literal value (14 + 1 separating blank). If a developer a year from now were to change the literal 'OPTIONS(*IMMED)' to the literal 'OPTIONS(*IMMED) LOGLMT(0)', the developer would need to know that the ChgVar updating &Len_NewCmd must also be updated, in this case changing the '15' to '25'. This is something that can be easily overlooked.
A better approach would be to calculate at run time the actual length of the new &New_Cmd command string. To do this, we can create a new command: Retrieve Variable Length (RTVVARLEN). The ability to create this type of command was alluded to in the article "Just How Big Is That Variable?"
To create the RTVVARLEN command, we should add three more messages to the USERMSGF message file created in the article "Overriding Commands and Their Parameter Values." The following commands will accomplish this:
ADDMSGD MSGID(MSG0201) MSGF(USERMSGF) MSG('Retrieve Variable Length')
ADDMSGD MSGID(MSG0202) MSGF(USERMSGF) MSG('Variable name')
ADDMSGD MSGID(MSG0203) MSGF(USERMSGF) MSG('Variable length')
This is the command definition for the RTVVARLEN command:
Cmd Prompt(MSG0201)
Parm KWD(Var) Type(*Char) Len(5000) Min(1) +
Vary(*Yes *Int4) Prompt(MSG0202)
Parm KWD(Length) Type(*Int4) RtnVal(*Yes) Min(1) +
Prompt(MSG0203)
And this is the RTVVARLEN command processing program (CPP) source:
Pgm Parm(&Var &Length)
Dcl Var(&Var) Type(*Char) Len(5000)
Dcl Var(&VarLength) Type(*Int) Stg(*Defined) +
Len(4) DefVar(&Var)
Dcl Var(&Length) Type(*Int)
ChgVar Var(&Length) Value(&VarLength)
EndPgm
To compile the CPP and command use this:
CRTBNDCL RTVVARLEN
CRTCMD CMD(RTVVARLEN) PGM(RTVVARLEN) ALLOW(*IPGM *BPGM *IMOD *BMOD)
PMTFILE(USERMSGF)
With the addition of the RTVVARLEN command, you can now replace this line...
ChgVar Var(&Len_NewCmd) +
Value(&Len_InlCmd + 15)
...with this line:
RtvVarLen Var(&New_Cmd) +
Length(&Len_NewCmd)
Using the RTVVARLEN command, you no longer have to worry about keeping the value for variable &New_Cmd in sync with the value assigned to variable &Len_NewCmd. With one consideration, you can add new keywords without the need to count characters or update a dependent source line in the program. The one consideration is that commands do not allow a *CHAR parameter to exceed 5000 bytes in size after performing blank truncation. If you try to pass a parameter exceeding 5000 bytes in length to RTVVARLEN, you will receive a run-time error of CPD0074 - Value &1 for VAR exceeds 5000 characters. For this situation, other solutions, which I will not cover in this article, exist.
Returning back to CHKKILL, the last check after calling the QCLSCAN API is to determine if an error occurred when calling the API, indicated by a &Result value of less than -1. In this situation, CHKKILL sends the error message PRX1006 using the SndErrMsg subroutine. PRX1006 does include the actual &Result value that was returned so that the developer has a starting point for further investigation.
And that's it. In this series of articles, you have seen how to...
- Create proxy commands
- Use the Command Analyzer Change exit point to customize command options based on the proxy command used
- Send messages containing replacement data values
- Use the MATPGMNM MI instruction to determine the currently running program name
- Use the PROPB MI instruction to propagate a character value across a variable
- Determine the length of a character string using the RTVVARLEN command
Hopefully, you will be able to put some of this knowledge to good use in future development efforts.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online