In previous columns, we discussed the basics of API error handling and data types. Today, we'll talk about accessing and using information returned by retrieve type APIs.
System APIs generally return information to you in one or more structures. One API that uses this approach, and that you might find handy to know about, is the Retrieve Call Stack (QWVRCSTK) API and format CSTK0100. This API and format is shown below and documented here.
Retrieve Call Stack (QWVRCSTK) API
Required Parameter Group:
1 |
Receiver variable |
Output |
Char(*) |
2 |
Length of receiver variable |
Input |
Binary(4) |
3 |
Format of receiver information |
Input |
Char(8) |
4 |
Job identification information |
Input |
Char(*) |
5 |
Format of job identification information |
Input |
Char(8) |
6 |
Error code |
I/O |
Char(*) |
Default Public Authority: *USE
Threadsafe: Yes
Format CSTK0100: Return OPM and ILE Call Stack Entries
Offset |
Type |
Field |
|
Dec |
Hex |
||
0 |
0 |
BINARY(4) |
Bytes returned |
4 |
4 |
BINARY(4) |
Bytes available |
8 |
8 |
BINARY(4) |
Number of call stack entries for thread |
12 |
C |
BINARY(4) |
Offset to call stack entry information |
16 |
10 |
BINARY(4) |
Number of call stack entries returned |
20 |
14 |
CHAR(8) |
Returned thread identifier |
28 |
1C |
CHAR(1) |
Information status |
29 |
1D |
CHAR(*) |
Reserved |
These fields repeat, in the order listed, for the number of call stack entries. |
BINARY(4) |
Length of this call stack entry |
|
BINARY(4) |
Displacement to statement identifiers |
||
BINARY(4) |
Number of statement identifiers |
||
BINARY(4) |
Displacement to the procedure name |
||
BINARY(4) |
Length of procedure name |
||
BINARY(4) |
Request level |
||
CHAR(10) |
Program name |
||
CHAR(10) |
Program library name |
||
BINARY(4) |
MI instruction number |
||
CHAR(10) |
Module name |
||
CHAR(10) |
Module library name |
||
CHAR(1) |
Control boundary |
||
CHAR(3) |
Reserved |
||
BINARY(4), UNSIGNED |
Activation group number |
||
CHAR(10) |
Activation group name |
||
CHAR(2) |
Reserved |
||
CHAR(10) |
Program ASP name |
||
CHAR(10) |
Program library ASP name |
||
BINARY(4) |
Program ASP number |
||
BINARY(4) |
Program library ASP number |
||
BINARY(8), UNSIGNED |
Activation group number long |
||
CHAR(*) |
Reserved |
||
ARRAY(*) of CHAR(10) |
Statement identifiers |
||
CHAR(*) |
Procedure name |
This API, which returns information about all programs and procedures active in a thread's call stack, requires six parameters. The Receiver variable is where the API returns the information, and, as we learned in "Understanding API Data Types," the Char(*) definition tells us it's a variable-length parameter. The Length of receiver variable is a 4-byte integer used to tell the API how much storage we've allocated for the Receiver variable parameter. Format of receiver information is an 8-byte character value that defines the type of information we want the API to return in the Receiver variable parameter. We will be using format CSTK0100, which instructs the API to only return information on OPM and ILE programs (*PGM) and service programs (*SRVPGM) and to return the information in the form shown immediately above. Job identification information is a variable-length input structure where you identify the job thread you want call stack information about, and Format of job identification information is an 8-byte character value defining the format of the Job identification parameter. We will be using format JIDF0100, which is shown here:
Format JIDF0100: Identify Job and Thread of Interest
Offset |
Type |
Field |
|
Dec |
Hex |
||
0 |
0 |
CHAR(10) |
Job name |
10 |
A |
CHAR(10) |
User name |
20 |
14 |
CHAR(6) |
Job number |
26 |
1A |
CHAR(16) |
Internal job identifier |
42 |
2A |
CHAR(2) |
Reserved |
44 |
2C |
BINARY(4) |
Thread indicator |
48 |
30 |
CHAR(8) |
Thread identifier |
The sixth parameter, Error code, is a variable-length structure that was discussed in "System API Basics."
The structures returned by this API are more complex than what you will find with many APIs, but they do include various features that I want to discuss. Below is a program that accepts one parameter: the number of call stack entries you want information about, starting with the procedure currently running. The program then displays the procedure and program names found.
dCallStack pr extpgm('CALLSTACK')
d NbrEntInput 15p 5 const
dCallStack pi
d NbrEntInput 15p 5 const
dGetCallStack pr extpgm('QWVRCSTK')
d RcvVar 1 options(*varsize)
d LenRcvVar 10i 0 const
d FmtRcvVar 8 const
d JobID 65535 const options(*varsize)
d FmtJobID 8 const
d ErrCde likeds(QUSEC)
/copy qsysinc/qrpglesrc,qwvrcstk
/copy qsysinc/qrpglesrc,qwcattr
/copy qsysinc/qrpglesrc,qusec
dRcvVar ds likeds(QWVK0100)
d based(RcvVarPtr)
dEntryInfo ds likeds(QWVCSTKE)
d based(EntInfPtr)
dProcName s 256 based(ProcNamePtr)
dNbrEnt s 10i 0
dCount s 10i 0
dCurProcName s 52
dPrvPgmName s 10
dWait s 1
/free
// Check for parameter, default to all
if %parms = 0;
NbrEnt = *hival;
else;
NbrEnt = NbrEntInput;
endif;
// API is to return escape messages if an error is found
QUSBPRV = 0;
// Initialize Job identification format JIDF0100
QWCF0100 = *loval; // Set structure to x'00's
QWCJN02 = '*'; // Job name: * = this job
QWCUN = *blanks; // User name
QWCJNBR00 = *blanks; // Job number
QWCIJID = *blanks; // Internal job ID
QWCTI00 = 1; // Thread = this thread
// Call API to find out how much storage is needed
GetCallStack(QWVK0100 :%size(QWVK0100) :'CSTK0100'
:QWCF0100 :'JIDF0100' :QUSEC);
// Check information status
select;
when QWVIS = ' '; // Info OK
when QWVIS = 'I'; // Info partial, still OK
when QWVIS = 'N'; // Info not available
dsply 'Information is not available.' ' ' Wait;
*inlr = *on;
return;
other;
dsply ('Unexpected status value of ' + QWVIS) ' ' Wait;
*inlr = *on;
return;
endsl;
RcvVarPtr = %alloc(QWVBAVL); // Get the storage
GetCallStack(RcvVar :QWVBAVL :'CSTK0100' // Call API again to get
:QWCF0100 :'JIDF0100' :QUSEC); // all of the data
// Check information status in case anything has changed
select;
when RcvVar.QWVIS = ' '; // Info OK
when RcvVar.QWVIS = 'I'; // Info partial, still OK
when RcvVar.QWVIS = 'N'; // Info not available
dsply 'Information is not available.' ' ' Wait;
*inlr = *on;
return;
other;
dsply ('Unexpected status value of ' + RcvVar.QWVIS) ' 'Wait;
*inlr = *on;
return;
endsl;
// If call stack isn't as large as user requested, then tell them
if NbrEnt > RcvVar.QWVERTN;
NbrEnt = RcvVar.QWVERTN;
dsply ('Showing ' + %char(NbrEnt) + ' call stack entries.');
endif;
EntInfPtr = RcvVarPtr + RcvVar.QWVEO; // Get the first entry
for Count = 1 to NbrEnt; // Process all entries
// that were requested
// Display Pgm/Srvpgm name when it changes
if EntryInfo.QWVPGMN <> PrvPgmName;
PrvPgmName = EntryInfo.QWVPGMN;
dsply ' ';
dsply ('Program name: ' + EntryInfo.QWVPGMN);
endif;
// If procedure name was returned, display it up to the max
// byte limitation of the dsply opcode (currently 52)
if EntryInfo.QWVPD > 0;
ProcNamePtr = EntInfPtr + EntryInfo.QWVPD;
if EntryInfo.QWVPL > %size(CurProcName);
CurProcName = %subst(ProcName :1 :%size(CurProcName));
else;
CurProcName = *blanks;
CurProcName = %subst(ProcName :1 :EntryInfo.QWVPL);
endif;
dsply CurProcName;
else;
dsply 'Cannot determine procedure name. OPM perhaps?';
endif;
EntInfPtr += EntryInfo.QWVEL; // Move to next entry
endfor;
dealloc RcvVarPtr; // Free the storage
dsply 'End of call stack list.' ' ' Wait; // Wait for the operator
// to indicate list read
*inlr = *on;
return;
/end-free
While the program is shown in its entirety, today we will only look at this program up through the initial call to the QWVRCSTK API. Because of space considerations, we will defer discussion of the remainder of the program to the next article.
As you are quite familiar with RPG, I won't go into the details of the program other than to explain how they relate to API usage or to explain an implementation decision. The first decision is the declaration of the NbrEntInput parameter as a packed decimal 15,5. This was done for ease of use in testing the program from the command line. If this parameter is not provided, the program will default to showing all call stack entries.
The program uses several include files that are provided with i5/OS in the QSYSINC library. The QWVRCSTK include file provides the definitions for format CSTK0100, QWCATTR the definition for format JIDF0100, and QUSEC the definitions for the QUSEC Error code structure. In case you're wondering how I knew to use these particular include files, the convention is that include file names are the same as the name of the *PGM or *SRVPGM implementing the API. As I'm using the QWVRCSTK API, the associated file member in QSYSINC/QRPGLESRC is QWVRCSTK. While I might expect member QWVRCSTK to also define format JIDF0100, I find in looking at the QWVRCSTK include that there is a comment saying that the job identification structures are found in include QWCATTR, so I /copy that include also. And as discussed in "System API Basics," the standard API error code structures are found in QUSEC. If you don't have QSYSINC on your system, you can install it with option 13 of i5/OS.
From an API usage point of view, the first item the program takes care of is how API error messages are to be returned. In our case, we are setting the Bytes provided field (QUSBPRV) of the Error code structure (QUSEC) to 0. This tells the API that we want serious errors to be returned as escape messages.
Next, the program initializes the Job identification data structure associated with format JIDF0100. In looking at source member QWCATTR, we can see that the name of the provided data structure is QWCF0100. An important aspect to keep in mind is how we initialize this structure. Imbedded within format JIDF0100 is a reserved field (QWCERVED06) that must be set to x'00's. When using API include files, never reference a reserved field by name. IBM reserves the right to start using any reserved field in a future release and, at that time, to change the reserved field's name to something more meaningful. This type of change by IBM would cause a compile failure if you had a statement in your application program such as 'QWCERVED06 = *loval;' and you needed to compile the application on that future release, even for some totally unrelated reason. You can avoid this exposure by initializing the entire structure to the required value and then setting the specific fields to indicate the function you want performed. In our case, we initialize QWCF0100 to x'00's and then set the other subfields (Job name QWCJN02, Job user name QWCUN, etc.) to indicate we want the call stack for the current job and thread.
At this point, the program calls the QWVRCSTK API. One structure being passed, QWCF0100, was discussed in the previous paragraph and identifies the job and thread we want call stack information about. The second structure, QWVK0100, is the IBM-provided data structure defining the fixed location fields of format CSTK0100, Bytes returned (QWVBRTN) through Information status (QWVIS).
Many i5/OS APIs return a receiver variable that contains only fixed-length fields and a fixed number of fields. The Retrieve Member Description API, QUSRMBRD, with format MBRD0100, introduced in "Understanding API Data Types," is such an API. Other APIs, such as QWVRCSTK, can return both a fixed amount of data (data structure QWVK0100) and a variable amount of data. In the case of QWVRCSTK, this variable amount of data associated with format CSTK0100 is defined by the data structure QWVCSTKE and the subfields Entry length (QWVEL) through Activation group number long (QWVAGNL). If you review Format_CSTK0100, you will see that the fields of QWVCSTKE are documented in the Offset section as not being at a fixed location within the Receiver variable and that the number of returned occurrences of QWVCSTKE is dependent on the number of call stack entries found. This is an indication that you will most likely find multiple data structures defined in the QSYSINC include file: one for the single occurrence structure, one or more for the multiple occurrence structure. To further muddy the waters, QWVCSTKE also returns a variable amount of data with each occurrence. A procedure name, for instance, can be from 1 to 256 bytes in length or, in the case of an OPM program, there may be no procedure name at all.
So how do we figure out how large a Receiver variable needs to be so that we get all of the call stack information? We don't know how many call stack entries there might be, and we don't know how big each call stack entry might be. We could just guess a large number of entries and multiply by the maximum size of an entry that might be returned to obtain a Receiver variable size, but there is a better way.
Most retrieve type APIs that return information to you include at least two fields in the fixed-size portion of the Receiver variable. These two fields are Bytes returned and Bytes available. Bytes returned tells you how many bytes of data the API actually returned to the Receiver variable. Bytes available tells you how many bytes of data could have been returned if you'd had a sufficiently large Receiver variable.
What we're doing with the initial call to QWVRCSTK is passing in the data structure QWVK0100 so that we can get back the Bytes available field QWVBAVL (along with some other very useful information). What we will do with QWVBAVL, and some of the other information found in QWVK0100, you'll see in the next article (or, if you're impatient, you can look at the sample program and see for yourself what we'll be doing!).
Meanwhile, if you have other API questions, send them to me at
LATEST COMMENTS
MC Press Online