RPG programmers often need to run a CL command, such as OVRDBF, CLRPFM, or CPYF, in the middle of an RPG program. There are three basic ways to do this. In this article, I demonstrate each method and talk about the pros and cons of each. I also offer in-depth information about how to handle errors with each method. Because error handling is one of the biggest differences between the methods, I think it deserves some extra attention.
Perhaps the simplest and most commonly used way to run a command is the QCMDEXC API. This API accepts three parameters, though the last one is optional.
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
QCMDEXC is easy to use; just pass the command string in the first parameter, and the length in the second.
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
/free
QCMDEXC( 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)' : 33);
QCMDEXC automatically trims trailing spaces from your command string, freeing you from trimming them yourself. For example, in the following code, mycmd ends in 2,767 trailing blanks, but QCMDEXC strips them:
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
D mycmd s 3000a
/free
mycmd = 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)';
QCMDEXC( mycmd: %size(mycmd) );
Although you could use the %trim() BIF to trim the spaces, it would provide no advantage. QCMDEXC can trim them as quickly as %trim() can--perhaps even more quickly. On the other hand, if you use a VARYING string, no blanks are added to begin with, and therefore they don't need to be trimmed--resulting in faster performance, especially for large fields:
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
D mycmd s 32702a varying
/free
mycmd = 'OVRPRTF FILE(QSYSPRT) OUTQ(PRT01)';
QCMDEXC( mycmd: %len(mycmd) );
All the APIs that I discuss in this article (including QCMDEXC) run commands at the same call-stack level as the caller. This behavior is useful for commands such as overrides that are sensitive to the call-stack level that they're run in. It's also useful from the perspective that any errors that occur are sent directly to your program, and they can therefore be monitored for.
When a command run via QCMDEXC fails, an *ESCAPE message is sent to your program and logged to the job log. You can use traditional RPG methods to catch the error and see what the problem is:
D PSDS SDS qualified
D MsgId 7A overlay(PSDS:40)
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
D cmd s 100a varying
D errorMsg s 80a varying
/free
monitor;
cmd = 'CPYF FROMFILE(CUSTMAS) +
TOFILE(QTEMP/CUSTCOPY) +
TOMBR(*FROMMBR) +
MBROPT(*ADD) +
CRTFILE(*YES)';
QCMDEXC( cmd : %len(cmd));
on-error;
ErrorMsg = 'Copy of CUSTMAS failed with '
+ PSDS.MsgId;
// ...now log error, or show to user,
// or whatever is appropriate.
endmon;
Because the details are logged to the job log, you can use the QMHRCVPM API to retrieve all the message details in their full glory and get a lot more information than the MsgId I demonstrated. You also get the MsgDta, information about the message file, Coded Character Set Identifier (CCSID) information, and much more. Basically, you can retrieve anything you want to know about the error message this way, albeit at the cost of making the code a bit more complex.
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
D QMHRCVPM PR extpgm('QMHRCVPM')
D RcvVar 32767A options(*varsize)
D RcvVarLen 10I 0 const
D Format 8A const
D StackEnt 10A const
D StackCount 10I 0 const
D MsgType 10A const
D MsgKey 4A const
D WaitTime 10I 0 const
D Action 10A const
D ErrorCode 32767A options(*varsize)
D ErrorCode ds qualified
D BytesProv 10I 0 inz(0)
D BytesAvail 10I 0 inz(0)
D MsgInfo ds qualified
D BytesRtn 10i 0
D BytesAvail 10i 0
D MsgSev 10i 0
D MsgId 7a
D MsgType 2a
D MsgKey 4a
D Msgf 10a
D MsgfReqLib 10a
D Msgflib 10a
D SendJob 26a
D SendPgm 12a
D SendPgmInst 4a
D DateSent 7a
D TimeSent 6a
D RcvPgm 10a
D RcvPgmInst 4a
D SendType 1a
D RcvType 1a
D 1a
D CcsidStsTxt 10i 0
D CcsidStsDta 10i 0
D Alert 9a
D CCSIDmsg 10i 0
D CCSIDdta 10i 0
D MsgDtaLen 10i 0
D MsgDtaAvail 10i 0
D MsgLen 10i 0
D MsgAvail 10i 0
D MsgHlpLen 10i 0
D MsgHlpAvail 10i 0
D MsgDta 32767a
D cmd s 100a varying
D errorMsg s 80a varying
/free
monitor;
cmd = 'CPYF FROMFILE(CUSTMAS) +
TOFILE(QTEMP/CUSTCOPY) +
TOMBR(*FROMMBR) +
MBROPT(*ADD) +
CRTFILE(*YES)';
QCMDEXC( cmd : %len(cmd));
on-error;
QMHRCVPM( MsgInfo
: %size(MsgInfo)
: 'RCVM0200'
: '*'
: 0
: '*LAST'
: *blanks
: -1
: '*OLD'
: ErrorCode );
// Now the MsgInfo structure is filled in
// with loads of information about the
// error...
endmon;
One of the chief frustrations people encounter with QCMDEXC is having to calculate and pass the length in the second parameter. To me, passing a length parameter doesn't seem like a big deal, but others prefer to avoid it. The system() API does exactly that, and it works much like QCMDEXC, except it calculates the length for you.
The system() API is part of the ILE C/C++ runtime, which means that you have to bind to the QC2LE binding directory to use it, and you can call it only as an ILE procedure, so you must be using an ILE program--that is, a program compiled with DFTACTGRP(*NO)--in order to use it.
H DFTACTGRP(*NO) BNDDIR('QC2LE')
D system PR 10I 0 extproc('system')
D command * value options(*string)
There's only one parameter--the command to execute--so it's pretty self-explanatory.
H DFTACTGRP(*NO) BNDDIR('QC2LE')
D system PR 10I 0 extproc('system')
D command * value options(*string)
/free
system('CLRPFM QTEMP/MYFILE');
Unlike QCMDEXC, the system() API returns no escape message to your program if the command that it called fails. Instead, you have to check the return value. If the return value is 0, the command succeeded; otherwise, something went wrong. You can import the _EXCP_MSGID variable from the ILE C/C++ runtime to get the MsgId of the failure.
H DFTACTGRP(*NO) BNDDIR('QC2LE')
D system PR 10I 0 extproc('system')
D command * value options(*string)
D ErrMsgID S 7A Import('_EXCP_MSGID')
D rc S 10i 0
D ErrMsg S 80a varying
/free
rc = system('CLRPFM QTEMP/MYFILE');
select;
when rc = 0;
// success!
when ErrMsgId = 'CPF3142';
system('CRTPF QTEMP/MYFILE RCDLEN(1000)');
other;
ErrMsg = 'Unable to clear flat file. Error ' + ErrMsgId;
// show error to user, or write to log, or...
endsl;
*inlr = *on;
/end-free
Error messages sent to commands that are run via system() don't get logged to the job log, and this is my biggest complaint about the system() API. If something goes wrong, there's no way to get the complete error message information. Oftentimes, the MsgId is ambiguous, relying on the MsgDta to provide more information, but you can't get the MsgDta when using system(), and this makes it inflexible for handling errors.
I don't recommend the system() API, because its error handling is poor. Providing the length to QCMDEXC is a small price to pay for having proper diagnostic information.
The last API, QCAPCMD, is an extremely deluxe and versatile API. It can work just like QCMDEXC, but it can also do more, including prompting, syntax checking, S/36 commands, and more.
The major drawback of QCAPCMD is its complexity. It requires a great deal more effort to use than QCMDEXC because it has more options and therefore more parameters.
D QCAPCMD PR ExtPgm('QCAPCMD')
D command 32702a options(*varsize) const
D cmdlen 10i 0 const
D optblk like(CPOP0100_t) const
D optblklen 10i 0 const
D optblkfmt 8a const
D chgcmd 32702a options(*varsize)
D chglenprv 10i 0 const
D chglenrtn 10i 0
D errorcode 32767a options(*varsize)
D CPOP0100_t ds qualified
D type 10i 0 inz(0)
D dbcs 1a inz('0')
D prompter 1a inz('0')
D syntax 1a inz('0')
D msgkey 4a inz(x'00000000')
D ccsid 10i 0 inz(0)
D 5a inz(x'0000000000')
D ErrorCode ds qualified
D bytesprov 10i 0 inz(%size(ErrorCode))
D bytesavail 10i 0 inz(0)
D msgid 7a
D 1a
D msgdta 32759a
D optblk ds likeds(CPOP0100_t)
D inz(*likeds)
D myCmd s 500a varying
D NotUsed s 1a
D NotUsedLen s 10i 0
/free
myCmd = 'ALCOBJ +
OBJ((CUSTMAS *FILE *EXCL *FIRST)) +
WAIT(1)';
QCAPCMD( myCmd
: %len(myCmd)
: optblk
: %size(optblk)
: 'CPOP0100'
: NotUsed
: 0
: NotUsedLen
: ErrorCode );
To keep this article relatively short and simple, I'm deliberately glossing over QCAPCMD's various options and parameters. For details about all its options, please see the Information Center:
http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/apis/qcapcmd.htm
Suffice it to say that with the options I've given, QCAPCMD will work the same way as QCMDEXC, except that error information is returned in the ErrorCode data structure.
QCAPCMD returns error information in the standard API error code data structure. This means you can easily get the MsgId and MsgDta in your program. Just examine the ErrorCode data structure.
select;
when ErrorCode.bytesAvail = 0;
// no errors;
when ErrorCode.MsgId = 'CPF0939';
// object not found
when ErrorCode.MsgId = 'CPF0952';
// library not found
when ErrorCode.MsgId = 'CPF1002';
// unable to allocate
other;
// unexpected errror. Now what?
endsl;
If you prefer to have QCAPCMD send an *ESCAPE message, you can set the bytes provided to zero, and QCAPCMD sends an *ESCAPE message to the job log just like QCMDEXC does.
D ErrorCode ds qualified
D bytesprov 10i 0 inz(0)
D bytesavail 10i 0 inz(0)
D msgid 7a
D 1a
D msgdta 32759a
Now you can check the Program Status Data Structure (PSDS) or call QMHRCVPM, just as you would have done with QCMDEXC.
In most of my software, I have errors that I want to handle and errors that I'd prefer to propagate to my caller.
D QCMDEXC PR ExtPgm('QCMDEXC')
D cmd 32702a const options(*varsize)
D len 15p 5 const
D igc 3a const options(*nopass)
D QMHRSNEM PR ExtPgm('QMHRSNEM')
D MsgKey 4A const
D ErrorCode 32767A options(*varsize)
D EscapeErr ds qualified
D bytesProv 10i 0 inz(0)
D bytesAvail 10i 0 inz(0)
D PSDS SDS qualified
D MsgId 7A overlay(PSDS:40)
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* util_LockObj(): Gain exclusive use of an object on disk
*
* Obj = (input) object name
* Lib = (input) library containing object
* Type = (input) object type (*FILE, *DTAARA, etc)
* Member = (input/optional) name of member (if this
* is a file) defaults to *FIRST
*
* Returns 0 if successful, -1 if object is in use
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
P util_LockObj B export
D util_LockObj PI 10i 0
D Obj 10a const
D Lib 10a const
D Type 10a const
D Member 10a const options(*nopass:*omit)
D Mbr s like(Member)
D cmd s 100a varying
D err s 7a
/free
if ( %parms >= 4 and %addr(Member) <> *null );
Mbr = Member;
elseif type='*FILE';
Mbr = '*FIRST';
endif;
monitor;
cmd = 'ALCOBJ OBJ((' + %trim(Lib) + '/' + %trim(Obj)
+ ' ' + %trim(Type)
+ ' *EXCL'
+ ' ' + %trim(Mbr) + ')) +
WAIT(1)';
QCMDEXC( cmd : %len(cmd));
err = *blanks;
on-error;
err = PSDS.MsgId;
endmon;
if err = 'CPF1002'; // Object in use
return -1;
elseif err <> *blanks;
QMHRSNEM( *blanks: EscapeErr );
return -1;
else;
return 0;
endif;
/end-free
P E
In this example, I've written a subprocedure to call for the ALCOBJ command, because no equivalent API exists. If the objects are in use, I want to handle that as a "normal condition." However, if any other error occurs, I want to send that back to my caller, forcing the caller to use a MONITOR or *PSSR to catch it. The QMHRSNEM API resends the last *ESCAPE message in my job log to my caller. I find that almost always when I'm running commands from my RPG programs, I want to do something like this. That is, I want to "handle" certain errors and let any other error get sent back to my caller. Here are some things to be aware of for this type of handling:
System(): Doesn't work for this type of logic. It's incapable of propagating errors, because not all the error information is available.
QCMDEXC(): Works well, but if you need the MsgDta, you have to use the QMHRCVPM API to get it.
QCAPCMD(): Works well and provides the MsgDta. But, how do you propagate errors? You can't simply resend the error message from the API error structure, because the structure doesn't have message file information. You can set the bytes provided field of the ErrorCode to 0, and get an *ESCAPE message just like QCMDEXC--but then why have you gone to all the additional effort of using QCAPCMD?
My conclusion is usually to use QCMDEXC. I never use system(), because, even though it calculates the length of the command for me, it provides inadequate diagnostics. Although QCAPCMD provides tons of diagnostics and more options than QCMDEXC, I rarely find a use for them. I end up using QCAPCMD the same way I'd have used QCMDEXC, so why not use the simpler API?
Thanks, Randal.
I did think about including CLLE modules in my list of options but decided against it. CLLE modules are especially useful when working with commands that retrieve data (since these cannot be executed from QCMDEXC, system(), or QCAPCMD). For example, the RTVMBRD command has lots of information that's difficult to get in RPG without calling complex APIs. It's a great candidate for a CLLE module!
However, I decided that this topic would be better served by its own article, instead of including it in this one. As you said, it can be a lot of work for a single command; therefore, I think CLLE modules usually serve a little bit different purpose than in-line calls to the APIs and therefore should be discussed separately.