Are you as knowledgeable about APIs as you should be?
While it seems reasonable to assume that anyone reading RPG Developer is quite familiar with RPG, not every reader may be as familiar with the application program interfaces (APIs) available with i5/OS. In this ongoing column, "The API Corner," I will be discussing some of the wild and wonderful capabilities that are open to the RPG developer through system APIs. But the first few articles will be oriented toward the developer who, while knowledgeable about RPG, may not be that familiar with APIs.
i5/OS provides a significant number of system APIs that RPG developers can use. Any given API generally falls into one of these categories:
• An i5/OS-unique interface that enables enhanced access to system capabilities
• An industry-standard interface that enables cross-platform application compatibility with other systems (often referred to as being UNIX-type)
• An IBM-standard interface that enables cross-platform application compatibility with other IBM systems (some of these APIs have also evolved to become industry-standard interfaces) These IBM-standard interface APIs, depending on their age, are often referred to as being Systems Application Architecture (SAA) type, Common Execution Environment (CEE) type, or some other family-type name.
The initial focus of this column will be on the first type of system APIs, those that are generally unique to i5/OS. These APIs tend to have a common set of standards. These standards include basics such as parameter passing conventions, data types supported, API naming, and the like. One critical standard is related to error-handling.
i5/OS-unique APIs use a common parameter, known as the error-code parameter, to give the application developer control over how errors are to be reported back to the application program. This error-code parameter is variable length with two predefined structure definitions. The most frequently used structure definition has the following subfields:
• Bytes provided-A 4-byte integer subfield (sometimes also referred to as being a 4-byte binary subfield) that is an input to the API and defines how many bytes of the error-code parameter structure are to be used by the API in reporting errors back to the developer
• Bytes available-A 4-byte integer subfield that can be an output from the API and defines how many bytes of error-related information were returned in the error-code parameter by the API
• Exception ID-A 7-byte character subfield that can be an output from the API and represents the *ESCAPE message ID associated with an error found by the API
• Reserved-A 1-byte character field that is not currently used in reporting error-related information back to the application program
• Exception data-A variable-length character subfield that can be an output from the API and may contain message-replacement data associated with the message ID returned in the Exception ID subfield
This error-code structure, as with most structures used by system APIs, is provided by IBM in the System Openness Includes (QSYSINC) library. If this library is not installed on your system, you need to install option 13 of i5/OS. This column will make extensive use of the QSYSINC includes in order to avoid having to duplicate API structure definitions within an article. Having said that, though, let's now examine the error-code structure that is provided in QSYSINC! The structure is found in member QUSEC of the file QSYSINC/QRPGLESRC and is shown below:
D*Record structure for Error Code Parameter
D*NOTE: The following type definition only defines the fixed
D* portion of the format. Varying length field Exception
D* Data will not be defined here.
D* Qus EC
D QUSBPRV 1 4B 0
D* Bytes Provided
D QUSBAVL 5 8B 0
D* Bytes Available
D QUSEI 9 15
D* Exception Id
D QUSERVED 16 16
D*QUSED01 17 17
D* Varying length
The first field, QUSBPRV, determines how errors will be reported back to you. If this subfield is set to 0, you are telling the API to not use any of the storage defined within the error-code parameter to report errors. Instead, the API is to send the application *ESCAPE messages when a severe error is found. In this case, you will want to utilize RPG features such as defining a monitor group, the (E) extender for CALL/CALLB/CALLP operations, or an error indicator on CALL/CALLB operations. Otherwise, the application will end abnormally. Using this approach, a QUSBPRV value of 0 will allow your application to be aware that an error has been encountered, but without some extra work, you generally only know the *ESCAPE message ID, not any replacement data associated with the *ESCAPE message that was sent.
Another approach is to set QUSBPRV to a value of 8 or greater. This value tells the API to not send *ESCAPE messages but rather to return error-related information, up to this specified value in bytes, directly in the error-code parameter. For instance, a value of 528 tells the API that we want up to 512 bytes of message replacement data (in addition to the 16 bytes for QUSBPRV, QUSBAVL, QUSEI, and QUSERVED) returned in an error situation. In this case, we do not need to start a monitor group or utilize an (E) operations extender; no *ESCAPE will be sent to the application.
Clearly, the value you select for the QUSBPRV subfield is critical in terms of how you want to handle error situations. It is also critical that you remember to always set this subfield to an appropriate value. By default, RPG initializes the storage of a data structure to blanks (x'40's). If you neglect to explicitly set QUSBPRV, the subfield will default to blanks or a 4-byte value of x'40404040'. This hex value, if passed to an API and processed as an integer subfield, indicates to the API that the application has allocated 1,077,952,576 bytes of storage for the error-code parameter and that the API should feel free to return up to 1,077,952,560 bytes of message-replacement data if necessary. As the QSYSINC-provided QUSEC data structure allocates only 16 bytes of storage, any error found in an API may cause quite a bit of the application storage to be inadvertently overwritten. This situation can be very difficult to debug and is generally externalized as extremely bizarre behavior within the application. Files that were open suddenly appear to be closed, variables used in other parts of the program (or even in different programs within the same job!) have what appears to be random data in them, etc.
To avoid this, I recommend this approach:
dErrCde ds qualified
d Common likeds(qusec)
ErrCde.Common.QUSBPRV = %size(ErrCde);
This creates an instance of the error-code structure utilizing the IBM-provided definitions for the first four subfields, implements my application need to support up to 512 bytes of message replacement data, and ensures an accurate value is used for QUSBPRV.
The second field of the error-code data structure, QUSBAVL, represents how many bytes of error-related data were returned to the application if an error was encountered during an API call. A few points about this field are worth mentioning:
• This subfield applies only to severe error situations that represent escape situations. Messages that are diagnostic, informational, or completion-oriented may be logged to the application program queue (joblog) but will not be returned via the error-code structure.
• This subfield has significance only if the associated QUSBPRV subfield has a value of 8 or more. If QUSBPRV is set to 0, this field could be set to virtually anything as *ESCAPE messages will be sent to the application if an error is found.
• This subfield will be 0 if no error was encountered and non-zero if a severe error was detected by the API. This subfield is the only way you should check for a severe error situation when QUSBPRV is set to 8 or more. Always first check this subfield after calling an API, with an associated QUSBPRV value of 8 or more, in order to determine if an error occurred.
• There is no need for the application to initialize this subfield to 0 prior to calling an API. One of the first functions an API performs when called is to set QUSBAVL to 0 if QUSBPRV is 8 or more. For the application to also initialize the subfield is a waste of time.
QUSBAVL is the only error-code structure subfield that an API will always initialize when QUSBPRV is 8 or more. Any other field, such as QUSEI, will be left in a last-used state unless an error is encountered in the running of the API. For this reason, first check QUSBAVL for a non-zero value when an API returns. If the value is 0, then the value of QUSEI could be anything. If the value of QUSBAVL is 8 (due to QUSBPRV being set to a value of 8), then once again, QUSEI could be anything as the QUSBPRV value told the API to not update more than the first 8 bytes of the error-code structure.
The third subfield, QUSEI, is the exception ID corresponding to the error that the API detected. This subfield will be updated by the API when a severe error is encountered and QUSBPRV is 15 or more. While the value of QUSEI represents an *ESCAPE message, note that the prefix of the message ID returned may not necessarily be CPF. You may very well find that CPDs, along with other prefixes, are used.
The fifth field, QUSED01 for message-related exception data, is commented out in the QYSINC QUSEC include. This field represents a variable-length field, and you determine, based on your application requirements, what size is appropriate given the error messages and related message-replacement data the application program might want to work with. In the previous definition of the error-code data structure ErrCde, I defined the subfield ExcpData as being 512 bytes in size. I find this size to be more than adequate for 99 percent of i5/OS error messages that I'm likely to run into (that is, as long as I'm not working with the IFS and really long path names!). Note that while this subfield is variable length, the subfield is not defined as VARYING. The subfield is variable-length only in the sense that i5/OS does not predefine a fixed size. You determine the appropriate size.
As any given API can return a variety of error messages, I also define message-replacement data using this style:
dErrCde ds qualified
d Common likeds(qusec)
dCPF2101 ds qualified based(ExcpData_Ptr)
d ObjTyp 7
dCPF9810 ds qualified based(ExcpData_Ptr)
d LibNam 10
dCPF9812 ds qualified based(ExcpData_Ptr)
d FilNam 10
d LibNam 10
ErrCde.Common.QUSBPRV = %size(ErrCde);
ExcpData_Ptr = %addr(ErrCde.ExcpData);
// Call a System API using the ErrCde Error code parameter and a
// ErrCde.Common.QUSBPRV = 528 as set above.
// We will assume that the API might return CPF2101, CPF9810, or
// CPF 9812 - each API documents those escape messages it might
// return in the Information Center. After calling the API:
if (ErrCde.Common.QUSBAVL > 0);
when ErrCde.Common.QUSEI = 'CPF2101';
dsply ('Invalid object type ' + %trimr(CPF2101.ObjTyp) +
when ErrCde.Common.QUSEI = 'CPF9810';
dsply ('Library ' + %trimr(CPF9810.LibNam) +
' not found.');
when ErrCde.Common.QUSEI = 'CPF9812';
dsply ('File ' + %trimr(CPF9812.FilNam) +
' not found in ' + %trimr(CPF9812.LibNam) + '.');
dsply ('Unexpected message ' + ErrCde.Common.QUSEI +
I find that using qualified, based data structures that are unique to each message makes my code much easier to review and maintain. And I assure you that I do have more robust error-handling in my production code than this simple example using DSPLYs. But this should give you the general idea! In order to determine how each message-related data structure (such as CPF9812 above) should be defined, use the Display Message Description (DSPMSGD) command with option 1 (Display message text) and option 2 (Display field data).
With this brief introduction to system API error-handling and some of the conventions I will be using in future articles, we are now ready to jump into the wonderful world of RPG and APIs. Keep an eye out for future editions of RPG Developer.