Several of you commented on Capturing QCMDEXC error codes that you preferred to use the C library function SYSTEM( ) or the API QCAPCMD.
I have to confess that I had not used the QCAPCMD API prior to writing this post. So after some playing around with it this is what I found out about using it:
QCAPCMD uses two data structures which I need to explain. The first is called the "Option Control Block", and is used to pass values to the API to control how it behaves. Below is how I coded it in this example.
02 D OptCtlBlock DS 03 D 10I 0 inz(0) 04 D 1 inz('0') 05 D 1 inz('0') 06 D 1 inz('0') 07 D 4 inz(x'00000000') 08 D 10I 0 inz(0) 09 D 9 inz(x'000000000000000000') |
I have not given the sub-fields names as I am not going to change or use the values of this data structure in any other way except to call QCAPCMD. The sub-fields are:
- Type of command processing. '0' (zero) means "command running", or I want to execute the command passed.
- DBCS data handling. '0' (zero) means I want to ignore DBCS data.
- Prompter action. '0' (zero) no prompting.
- Command string syntax. '0' (zero) means I will use the system (IBM i) syntax, rather than System/38 syntax (scary!).
- Message retrieve key. This is used during the prompting process. As I am not prompting I need to set this to 8 hexadecimal zeros.
- CCSID of command string. '0' (zero) means that I will use the CCSID of the job.
- Reserved. Not used but still has to be set to 18 hexadecimal zeros.
The next data structure we have to be concerned with is QUSEC. This is used by several APIs for returning error data. IBM has provided us with a source member for it that we can copy into this program.
10 D/copy qsysinc/qrpglesrc,qusec |
If you look in that source member you find that QUSEC has the following sub-fields:
Sub-field | Description |
QUSBPRV | Bytes provided |
QUSBAVL | Bytes available |
QUSEI | Exception id (CPF error code) |
QUSERVED | Reserved (unused) |
There is also a data structure called QUSC0200 in the copied source member. I have failed to find what it could be used for in this example.
In this example I am defining QCAPCMD as a procedure, with the EXTPGM keyword, therefore, I can call this program without having to leave free format. This is how I have defined the procedure prototype for it:
11 D QCAPCMD PR ExtPgm('QCAPCMD') 12 D 32702 options(*varsize) const 13 D 10I 0 const 14 D ocb like(OptCtlBlock) const 15 D 10I 0 const 16 D 8 const 17 D 32702 options(*varsize) const 18 D 10I 0 const 19 D 10I 0 20 D 32767 options(*varsize) |
Yet again I have not given field names to the parameter fields, the exception is ocb as I have used the LIKE keyword I have to give it a name. The parameters are:
- Command string.
- Length of the command string.
- Option control block.
- Length of the option control block.
- Format of option control block.
- Changed command string. Used with prompting, not relevant in this example.
- Length available for changed command string. Used with prompting, not relevant in this example.
- Length of changed command string available to return. Used with prompting, not relevant in this example.
- Error code, data structure QUSEC.
Now we can put it all together into the program:
01 H dftactgrp(*no) * Option Control Block 02 D OptCtlBlock DS 03 D 10I 0 inz(0) 04 D 1 inz('0') 05 D 1 inz('0') 06 D 1 inz('0') 07 D 4 inz(x'00000000') 08 D 10I 0 inz(0) 09 D 9 inz(x'000000000000000000') * Error code parameter 10 D/copy qsysinc/qrpglesrc,qusec * Procedure definition for program QCAPCMD 11 D QCAPCMD PR ExtPgm('QCAPCMD') 12 D 32702 options(*varsize) const 13 D 10I 0 const 14 D ocb like(OptCtlBlock) const 15 D 10I 0 const 16 D 8 const 17 D 32702 options(*varsize) const 18 D 10I 0 const 19 D 10I 0 20 D 32767 options(*varsize) 21 D Command S 1000 varying 22 D ChgdCommand S 32702 varying 23 D Unused S 10I 0 /free 24 Command = 'CLRPFM QTEMP/TESTFILE' ; 25 QCAPCMD(Command: %len(Command): OptCtlBlock: %size(OptCtlBlock): 'CPOP0100': ChgdCommand: %len(ChgdCommand): Unused: QUSEC) ; 26 if (QUSEI <> ' ') ; . . 27 endif ; 28 *inlr = *on ; |
The only part that is left to explain is the call of the procedure QCAPCMD, line 25. I have put each parameter on a seperate line to make it easier to understand. The parameters are:
- Command = Command string to clear the file QTEMP/TESTFILE.
- %len(Command) = Length of the string in the Command field.
- OpCtlBlock = Option control block data structure.
- %size(OptCtlBlock) Size of the option control block data structure.
- 'CPOP0100' = Format of option control block (C-P-O-P-zero-1-zero-zero).
- ChgdCommand = Not used, blank.
- %len(ChgdCommand) = As ChgdCommand is blank then this is zero.
- Unused = Not used, zero.
- QUSEC = Error code returned in the QUSEC data structure.
The built in functions %LEN and %SIZE are not the same. %LEN returns the length of the string in the field, %SIZE returns the total length. If we use these two built in functions on the Command field in the program:
x = %len(Command) ; y = %size(Command) ; |
The results would be: x = 21 and y = 1002. y is 1002 as it is the length of the Command field, 1000 bytes, and 2 additional bytes as the field was defined as variable, varying.
Back to the program, on line 26 I check if the QUSEC sub-field QUSEI is not blank, if it is not then the command produced an error.
You can learn more about these from the IBM web site:
- Process commands (QCAPCMD) API
- ILE RPG Example of QCAPCMD API
- %LEN built in function
- %SIZE built in function
This article was written for IBM i 7.1, and it should work with earlier releases too.
Conclusion
Of the three methods I have described in this series of excuting CL commands in RPGLE programs, QCMDEXC, SYSTEM( ), or QCAPCMD I still prefer to use QCMDEXC with a MONITOR group as the easiest method to code and for others to understand.
What do you think? Add your thoughts below in the Comments secition of this post.
>> There is also a data structure called QUSC0200 in the copied
ReplyDelete>> source member. I have failed to find what it could be used for
>> in this example.
Just a FYI - the API Error Code structure can be longer than the structure defined by QUSEC (see Infocenter link below). If a longer structure is defined and the first "Bytes provided" field is set to the structure length, when an error occurs, the exception/message data for the error is stored, starting at offset 16. In some cases the exception data might be useful to the application executing the CL command.
Another idea: if using ILE, a common module/routine (eg in a *SRVPGM) could be created to "wrap" this API, with a simple calling linkage that hides much of this API's complexity.
http://pic.dhe.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fapiref%2Ferrorcodeformat.htm
I recently discovered with 7.1 you do not need BndDir( 'QC2LE' ) in your “H” spec. for calling the Extproc command.
ReplyDeleteThat has been true since V6.1 Andrew.
DeleteWe all lack the time to play around with the system and investigate useful or new features, therefor I appreciate exchanging ideas like provided in the thread.
ReplyDeleteBe careful when including QUSEC from QSYSINC to use it "as is". The Bytes Provided field (QUSBPRV) must be initialized with the size of the QUSEC data structure.
ReplyDeleteThis should do the trick
Clear QUSEC;
QUSBPRV = %Size(QUSEC);
A good idea is to redefine QUSEC in a /Copy of your own, using the INZ option to make sure each field is OK:
D QUSEC DS
D QUSBPRV 10I 0 Inz(%Size(QUSEC))
D QUSBAVL 10I 0 Inz(0)
D QUSEI 7A Inz(*Blank)
D QUSERVED 1A Inz(*Blank)
Moreover, the RESET BIF can...reset...the DS, with the correct values, for further use in the program.
I Think QCMDEXE is the easiest way to call CL commands within RPG... This API has a lot of parameters and need a lot of coding....
ReplyDeleteWhich is why most who use it wrap it in a subprocedure so that is ends up being every bit as as easy to use as QCMDEXE. In fact it can be simpler since the subproc can return as value allowing the success/failure status to be returned as a variable just as system() does. Actually I also wrap up QCMDEXE the same way.
ReplyDeleteRe Sebastien's QUSEC comment, I'd like to add that the Error Code structure is common to many other APIs as well. In addition to initialization, the "Bytes available" field can be tested after the API call, to determine if the API call encountered an error. Zero means no error; greater than zero means an error occurred. This assuming "Bytes provided" field is initialized to the size of the Error Code structure prior to the API call.
ReplyDelete