Use the QCMDEXC, system, and QCAPCMD APIs to run CL commands from within your RPG application.
In last month's column, "Do I Really Need to Call a CL Program to Perform This Function?," the QCMDEXC, system, and QCAPCMD APIs were introduced. All three of these APIs are available to enable the running of CL commands from an application program. Today, we'll look at what is required to implement these APIs within your application program as well as some of the considerations that exist with each of the APIs.
What We Hope to Accomplish by Running CL from an RPG Program
To initially demonstrate the three APIs, we'll use a rather simple scenario: clearing a physical file member in preparation of further processing (this further processing will not be shown but could be along the lines of loading a new batch of records). The name of the file is SOMEFILE, and the name of the member is the user profile name associated with the job running the program. This initial scenario will either successfully clear the member and continue processing, or return an escape message to the caller of the program. Two different user escape messages are defined because of the characteristics of the APIs we will be using. To create message file OURMSGS, along with escape messages ESC0001 and ESC0002, use the following commands.
CRTMSGF MSGF(OURMSGS)
ADDMSGD MSGID(ESC0001) MSGF(OURMSGS) +
MSG('Unexpected error encountered. Examine job log for further information.')
ADDMSGD MSGID(ESC0002) MSGF(OURMSGS) +
MSG('Unexpected error &1 encountered .') FMT((*CHAR 7))
In a future scenario demonstrating error recovery using these APIs, we'll add checks for anticipated errors. These checks will include the following:
- If the clear fails due to the member not being found, the program will add the member and continue processing.
- If the clear fails due to the file not being found, the program will create the file and member and then continue processing
- If any other error is encountered, the program will end with one of the escape messages previously defined.
Using the Execute Command (QCMDEXC) API
Using the Execute Command (QCMDEXC) API, documented here, the following program implements the initial scenario described above.
h dftactgrp(*no)
dRunCmd pr extpgm('QCMDEXC')
d Cmd 512 const options(*varsize)
d LenCmd 15p 5 const
d IGC 3 const options(*nopass)
dSndMsg pr extpgm('QSYS/QMHSNDPM')
d MsgID 7 const
d QualMsgF 20 const
d MsgDta 65535 const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10 const
d CSE 65535 const options(*varsize)
d CSECtr 10i 0 const
d MsgKey 4
d QUSEC likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20 const options(*nopass)
d DSPWaitTime 10i 0 const options(*nopass)
d CSEType 10 const options(*nopass)
d CCSID 10i 0 const options(*nopass)
/copy qsysinc/qrpglesrc,qusec
dPSDS sds 429 qualified
d JobUsr 254 263
dCmd s 512
dMsgKey s 4
/free
QUSBPRV = 0;
monitor;
Cmd = 'CLRPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trim(Cmd)));
on-error;
SndMsg('ESC0001' :'OURMSGS *LIBL' :' ' :0
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endmon;
// Do further processing
*inlr = *on;
return;
/end-free
The program source, named CMDEXC0, starts by prototyping the QCMDEXC API along with the Send Program Message (QMHSNDPM) API. If you are not familiar with the QMHSNDPM API, you may want to review the article "Inform Users of Problems by Sending Error Messages from Application Programs." Related to the use of QMHSNDPM, the program later also copies the QSYSINC QRPGLESRC include QUSEC, defines the variable MsgKey, and sets the Bytes provided field of the QUSEC error code structure to zero. As the name of the member to be cleared is the same as the name of the user running the job, the program also defines the Program Status Data Structure (PSDS) and sub-field JobUsr (the job user profile name). This is followed by the definition for the Cmd variable. The Cmd variable will be used to hold the CL command to be run.
Actually running the command is very simple. The program starts a monitor group to detect if any errors are encountered (as mentioned in the previous article, "Do I Really Need to Call a CL Program to Perform This Function?," QCMDEXC returns errors as escape messages), sets the Cmd variable to the CL command string we want to run (CLRPFM with the MBR keyword set to the job user profile name PSDS.JobUsr), and then calls the QCMDEXC API (prototyped as function RunCmd). When calling QCMDEXC, the second parameter is set to the blank-trimmed length of the command string to run. This trimming of trailing blanks is not actually necessary (we could have just as easily used %size(Cmd) because the API tolerates trailing blanks), but is done as a personal preference.
If the CLRPFM command runs without error, the program resumes execution following the associated endmon statement. Note that, if you have a file named SOMEFILE in your library list, this demonstration program (and those that follow) will clear a member of that file named the same as the user profile you signed on with. To avoid inadvertently clearing a production file on your system if you do have a production file by this name, you will want to change the CLRPFM command string to reference another file name or ensure that the library containing SOMEFILE is not in your library list.
If any error is encountered when running the CLRPFM command, the associated on-error block is run. The on-error block sends the escape message ESC0001, which then ends the program. ESC0001 is used as QCMDEXC returns errors as exceptions, and while the monitor group handles the exception, the exception message can still be found in the job log.
To compile and run the program, you can use these commands:
CRTBNDRPG PGM(CMDEXC0)
CALL PGM(CMDEXC0)
If the file SOMEFILE is not currently in your jobs library list, you will receive the escape message ESC0001 and in your job log find CPF3142, File SOMEFILE in library *LIBL not found. If the file SOMEFILE does exist but does not contain a member with the name of the user profile you signed on with, you will receive the escape message ESC0001 and in your job log find CPF3141, Member XXXXXX not found, where XXXXXX is the name of your user profile. If the file SOMEFILE does exist and contains a member with the name of the user profile you signed on with, your job log will contain CPC3101, Member XXXXXX file SOMEFILE in YYYYYY cleared, where YYYYYY is the name of the library containing file SOMEFILE.
It's that easy. Now let's look at what's required to clear the member using the system API, which is documented in Chapter 2 here.
Using the Execute a Command (System) API
h dftactgrp(*no) bnddir('QC2LE')
dRunCmd pr 10i 0 extproc('system')
d Cmd * value options(*string)
dSndMsg pr extpgm('QSYS/QMHSNDPM')
d MsgID 7 const
d QualMsgF 20 const
d MsgDta 65535 const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10 const
d CSE 65535 const options(*varsize)
d CSECtr 10i 0 const
d MsgKey 4
d QUSEC likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20 const options(*nopass)
d DSPWaitTime 10i 0 const options(*nopass)
d CSEType 10 const options(*nopass)
d CCSID 10i 0 const options(*nopass)
/copy qsysinc/qrpglesrc,qusec
dPSDS sds 429 qualified
d JobUsr 254 263
dCmd s 512
dMsgID s 7 import('_EXCP_MSGID')
dMsgKey s 4
/free
QUSBPRV = 0;
Cmd = 'CLRPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
if RunCmd(Cmd) = 1;
SndMsg('ESC0002' :'OURMSGS *LIBL' :MsgID :%size(MsgID)
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
// Do further processing
*inlr = *on;
return;
/end-free
The program source, named SYSTEM0, is initially similar to the earlier program CMDEXC0 in terms of providing prototypes for the called APIs and declaring variables. There are two differences:
- The specification bnddir('QC2LE') in the H-spec—This addition is necessary if you will be creating the sample program and your System i is at V5R4 or earlier.
- The new variable, MsgID—MsgID is used to import the global variable _EXCP_MSGID, which is set by the system API if the CL command being run encounters an error. _EXCP_MSGID reflects the exception message ID encountered.
The actual running of the command, and related error detection, however, is quite different, though still easy to implement. Rather than the two parameters (command to run and length of the command string) of QCMDEXC, the system API accepts one parameter: the command to run. The length of the command is determined by a null byte and implemented by the options(*string) specification of the prototype. Error detection is also different in that, rather than returning errors as exception messages, the system API indicates that an error has been encountered by using an integer return value of positive 1.
Due to no exceptions being returned by the API, there is no need for the program to define a monitor group. After setting the proper command string value to the Cmd variable, the program calls the API and checks the return value of the API for positive 1. If the return value is not 1, the program resumes running after the ENDIF statement. If the return value is 1, the program sends the escape message ESC0002, which then ends the program.
To compile and run the program, you can use these commands:
CRTBNDRPG PGM(SYSTEM0)
CALL PGM(SYSTEM0)
If the file SOMEFILE is not currently in your jobs library list, you will receive the escape message ESC0002 with message replacement text of 'CPF3142' (file not found). If the file SOMEFILE does exist but does not contain a member with the name of the user profile you signed on with, you will receive the escape message ESC0002 with message replacement text of 'CPF3141' (member not found). If the file SOMEFILE does exist and contains a member with the name of the user profile you signed on with, your job log will contain message CPC3101, Member XXXXXX file SOMEFILE in YYYYYY cleared, where XXXXXX is the name of your user profile and YYYYYY is the name of the library containing file SOMEFILE.
Due to the system API not using exception messages to indicate failures, you will not find the CPF3142 and CPF3141 error messages in your job log as you did with CMDEXC0. And as ESC0001 points you to the job log, the SYSTEM0 demonstration program uses message ESC0002 (which does not refer you to the job log as there's little that's more aggravating than being told to look "over there" and finding nothing there). For this same reason (the lack of information in the job log), SYSTEM0 also sends the CPF exception message ID as replacement text in message ESC0002. For our demonstration program, with only one file being used, just having the CPF message ID should be sufficient for problem determination. A more complex application may need to use additional replacement text variables—for instance, to clearly identify the file being used if multiple files are involved.
Note that you do, however, find this completion message in the job log: CPC3101, Member XXXXX file SOMEFILE in YYYYYY cleared. With the system API completion messages, informational messages and the like will be available. It's just the actual escape message that is removed from the job log.
Now let's look at what's required to clear the member using the Process Commands (QCAPCMD) API, which is documented here.
Using the Process Commands (QCAPCMD) API with Escape Messages
h dftactgrp(*no)
dRunCmd pr extpgm('QCAPCMD')
d SourceCmd 65535 const options(*varsize)
d LenSrcCmd 10i 0 const
d CtlBlk 65535 const options(*varsize)
d LenCtlBlk 10i 0 const
d CtlBlkFmt 8 const
d ChgCmd 1 options(*varsize)
d LenAvlChgCmd 10i 0 const
d LenRtnChgCmd 10i 0
d QUSEC likeds(QUSEC)
dSndMsg pr extpgm('QSYS/QMHSNDPM')
d MsgID 7 const
d QualMsgF 20 const
d MsgDta 65535 const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10 const
d CSE 65535 const options(*varsize)
d CSECtr 10i 0 const
d MsgKey 4
d QUSEC likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20 const options(*nopass)
d DSPWaitTime 10i 0 const options(*nopass)
d CSEType 10 const options(*nopass)
d CCSID 10i 0 const options(*nopass)
/copy qsysinc/qrpglesrc,qcapcmd
/copy qsysinc/qrpglesrc,qusec
dPSDS sds 429 qualified
d JobUsr 254 263
dCmd s 512
dMsgKey s 4
dNotUsedChr s 1
dNotUsedInt s 10i 0
/free
QUSBPRV = 0;
QCAP0100 = *loval; // initialize input structure to nulls
QCACMDPT = 0; // Run command
QCABCSDH = '0'; // Ignore DBCS
QCAPA = '0'; // Do not prompt command
QCACMDSS = '0'; // User i5/OS syntax
monitor;
Cmd = 'CLRPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trim(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :QUSEC);
on-error;
SndMsg('ESC0001' :'OURMSGS *LIBL' :' ' :0
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endmon;
// Do further processing
*inlr = *on;
return;
/end-free
The program source, named PCMD0, is again quite similar to the earlier program CMDEXC0 in terms of providing prototypes for the called APIs and declaring variables. These are the differences:
- Quite a few more parameters for the prototype of QCAPCMD due to significantly more function being available (though we will not be using these additional features)
- The inclusion of QSYSINC member QCAPCMD from QRPGLESRC to define the Options control block parameter of the API
- The new variables NotUsedChr and NotUsedInt, which are not used but must be defined as they are prototyped as outputs of the QCAPCMD API
The actual running of the command, and related error detection, is essentially the same as with CMDEXC0. There is additional setup in order to initialize the various options available in the Options control block structure, which are being set to simply run a command, but the approach is the same.
To compile and run the program, you can use these commands:
CRTBNDRPG PGM(PCMD0)
CALL PGM(PCMD0)
If the file SOMEFILE is not currently in your jobs library list, you will receive the escape message ESC0001 and in your job log find CPF3142, File SOMEFILE in library *LIBL not found. If the file SOMEFILE does exist but does not contain a member with the name of the user profile you signed on with, you will receive the escape message ESC0001 and in your job log find CPF3141, Member XXXXXX not found, where XXXXXX is the name of your user profile. If the file SOMEFILE does exist and contains a member with the name of the user profile you signed on with, your job log will contain CPC3101, Member XXXXXX file SOMEFILE in YYYYYY cleared, where YYYYYY is the name of the library containing file SOMEFILE. This is the same output as you experienced with CMDEXC0.
The QCAPCMD API can however also provide the same output as the SYSTEM0 demonstration program, as shown in the following program.
Using the Process Commands (QCAPCMD) API Without Escape Messages
h dftactgrp(*no)
dRunCmd pr extpgm('QCAPCMD')
d SourceCmd 65535 const options(*varsize)
d LenSrcCmd 10i 0 const
d CtlBlk 65535 const options(*varsize)
d LenCtlBlk 10i 0 const
d CtlBlkFmt 8 const
d ChgCmd 1 options(*varsize)
d LenAvlChgCmd 10i 0 const
d LenRtnChgCmd 10i 0
d QUSEC likeds(QUSEC)
dSndMsg pr extpgm('QSYS/QMHSNDPM')
d MsgID 7 const
d QualMsgF 20 const
d MsgDta 65535 const options(*varsize)
d LenMsgDta 10i 0 const
d MsgType 10 const
d CSE 65535 const options(*varsize)
d CSECtr 10i 0 const
d MsgKey 4
d QUSEC likeds(QUSEC)
d LenCSE 10i 0 const options(*nopass)
d CSEQual 20 const options(*nopass)
d DSPWaitTime 10i 0 const options(*nopass)
d CSEType 10 const options(*nopass)
d CCSID 10i 0 const options(*nopass)
/copy qsysinc/qrpglesrc,qcapcmd
/copy qsysinc/qrpglesrc,qusec
dPSDS sds 429 qualified
d JobUsr 254 263
dCmd s 512
dMsgKey s 4
dNotUsedChr s 1
dNotUsedInt s 10i 0
/free
QUSBPRV = %size(QUSEC);
QCAP0100 = *loval; // initialize input structure to nulls
QCACMDPT = 0; // Run command
QCABCSDH = '0'; // Ignore DBCS
QCAPA = '0'; // Do not prompt command
QCACMDSS = '0'; // User i5/OS syntax
Cmd = 'CLRPFM FILE(SOMEFILE) MBR(' + PSDS.JobUsr + ')';
RunCmd(Cmd :%len(%trim(Cmd)) :QCAP0100 :%size(QCAP0100)
:'CPOP0100' :NotUsedChr :0 :NotUsedInt :QUSEC);
if QUSBAVL > 0;
SndMsg('ESC0002' :'OURMSGS *LIBL' :QUSEI :%size(QUSEI)
:'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);
endif;
// Do further processing
*inlr = *on;
return;
/end-free
This program source, named PCMD1, is the same as PCMD0 with the following exceptions:
- The Bytes provided field of the API error code structure is set to a non-zero value to turn off exceptions from the QCAPCMD API.
- The monitor, on-error, and endmon statements are replaced by a test for the Bytes available field of the API error code structure being non-zero.
- The escape message being sent is changed to ESC0002 with the Exception ID field of the API error code structure being used to set the message replacement text variable of ESC0002.
To compile and run the program, you can use these commands:
CRTBNDRPG PGM(PCMD1)
CALL PGM(PCMD1)
If the file SOMEFILE is not currently in your jobs library list, you will receive the escape message ESC0002 with message replacement text of 'CPF3142' (file not found). If the file SOMEFILE does exist but does not contain a member with the name of the user profile you signed on with, you will receive the escape message ESC0002 with message replacement text of 'CPF3141' (member not found). If the file SOMEFILE does exist and contains a member with the name of the user profile you signed on with, your job log will contain message CPC3101, Member XXXXXX file SOMEFILE in YYYYYY cleared, where XXXXXX is the name of your user profile and YYYYYY is the name of the library containing file SOMEFILE. This is the same output as you experienced with SYSTEM0.
By making very minor changes, namely the setting of the Error code Bytes provided field, you can control whether or not exception messages appear in the job log when using QCAPCMD. We will take advantage of this in future articles when we look at how to have the application program recover from errors such as file and/or member not found—and not leave "errors" in the job log when the application program is handling them (a pet peeve of mine that can impact the amount of time needed for problem determination).
Questions?
In the meantime, if you have any API questions, send them to me at
LATEST COMMENTS
MC Press Online