QCMDEXC is a useful API, that has been around since the launch of the AS400 (IBM i), that is called to execute a single command. It is one of the simpler APIs to call with just two parameters:
- Command string
- Command string length
I have seen many examples of RPGLE/RPG IV source code that looks something like this:
01 D CmdString S 132 02 D CmdLen S 15 5 03 /free 04 CmdString = 'CLRPFM TESTFILE' ; 05 CmdLength = 15 ; 06 /end-free 07 C call(e) 'QCMDEXC' 08 C parm CmdString 09 C parm CmdLength 10 /free 11 if (%error) ;
The programmer has gone "out" of RPG/free, line 6, to be able to CALL QCMDEXC, as the CALL operation is not supported in RPG/free.
On line 7 the programmer has used the 'E' operation extender to stop the program erroring if an error was encountered by QCMDEXC, such as TESTFILE not being in the library list or the file is in use by another job. Then they have gone back "into" RPG/free, line 10, before performing a test to see if there is an error, line 11. But what was the error?
By using the Program Status Data Structure (PSDS) it is possible to see the error message code.
You could either code the relevant parts of the PSDS like this:
01 D PgmDs SDS qualified 02 D ErrCde 40 46 03 D ErrTxt 91 170
Or if you have read the post Externally described Data Structures you know how to insert an external data structure with all of the PSDS's fields in it like this:
01 D PgmDs ESDS extname(RPG4DS) 02 D qualified
My preference is to use the externally described data structure as the names of the fields in the PSDS are the same across multiple source members, and you have all the other fields there in case you need them at a later date.
Below is an example of how I could do the same as the previous example without going "out" of RPG/free and discovering what the error was:
01 D PgmDs ESDS extname(RPG4DS) 02 D qualified 03 D QCmdExc PR extpgm('QCMDEXC') 04 D 132 options(*varsize) const 05 D 15 5 const 06 D CmdString S 132 07 D ErrorMsg S 7 08 D ErrorTxt S 80 /free 09 CmdString = 'CLRPFM TESTFILE' ; 10 callp(e) QCmdExc(CmdString:%len(CmdString)) ; 11 if (%error) ; 12 ErrorMsg = PgmDs.ExcptTyp + PgmDs.ExcptNbr ; 13 if (ErrorMsg = 'CPF3142') ; //File not found 14 ErrorTxt = PgmDs.RtvExcptDt ; . . 15 endif ; 16 endif ;
I have defined QCMDEXC as a procedure, line 3, with the EXTPGM keyword. This indicates that when the procedure is executed the program QCMDEXC is called. When a program called in this manner it cannot return any values.
The 'E' operation extender on the CALLP, line 10, allows me to test if an error was detected on line 11.
By combining the Exception type, PgmDs.ExcptTyp, and Exception number, PgmDs.ExcptNbr, from the PSDS I have the error message code that I can use to condition further processing.
On line 13 I move the Exception text, PgmDs.RtvExcptDt, to another field. I do this just to show that it is possible, I do not think that the text "File TESTFILE in library *LIBL not found" is as helpful to the average user as "The required file could not be found" would be.
Rather than use the CALLP I could use the MONITOR operation code instead:
01 CmdString = 'CLRPFM TESTFILE' ; 02 monitor ; 03 QCmdExc(CmdString:%len(CmdString)) ; 04 on-error ; 05 ErrorMsg = PgmDs.ExcptTyp + PgmDs.ExcptNbr ; 06 if (ErrorMsg = 'CPF3142') ; 07 ErrorTxt = PgmDs.RtvExcptDt ; . . 08 endif ; 09 endmon ;
For more information about the MONITOR operation code read MONITOR for errors in RPG.
You can learn more about these from the IBM web site:
This article was written for IBM i 7.1, and it should work with earlier releases too.