An iSeries Network Publication
http://www.iseriesnetwork.com
Home of iSeries NEWS Magazine
Editor: Scott Klement
Issue 169
May 20, 2004
SPONSORED BY Applied Logic Corp.
FREE DOWNLOADS: Popular Programmer Tools used in 90+ Countries...
* Restore deleted records, make global updates, edit spool files,
convert to PDF/HTML, and much more with FEU (File Edit Utility)
* Manage your software development with PDE/400 Change Management
* Convert your legacy S/36 Applications to run Native on the
iSeries with NCS (Native Conversion System)
http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80BIRV0Aq
THIS WEEK:
> Getting Started with APIs, Part 2
Pro and Pro VIP Edition Items:
> APIs by Example: Find File Member
> Embedded SQL with Variables
1. GETTING STARTED WITH APIs, PART 2
This is part two in my series about how to get started with APIs. This
article picks up where part one left off. Reading part one will help
you to understand this article. If you missed part one, there's a link
to it at the end of this article.
ERROR HANDLING
Most of IBM's APIs pass an "Error Code" parameter that your program
can read to determine if the call to the API was successful and, if it
was not, what went wrong.
The error code parameter is a data structure in the following format:
Offset Use Type Field
Dec Hex
0 0 INPUT BINARY(4) Bytes provided
4 4 OUTPUT BINARY(4) Bytes available
8 8 OUTPUT CHAR(7) Exception ID
15 F OUTPUT CHAR(1) Reserved
16 10 OUTPUT CHAR(*) Exception data
The "Bytes Provided" field tells the API how large the error code
structure is. The word "INPUT" is listed in the "Use" column of the
table above. It tells you that the API expects you to populate this
field before calling the API.
Why is the "Bytes Provided" field needed? Because the API leaves it up
to you to decide how much or how little of the error information you
want to receive. If you don't want to receive any error information,
you can set the "bytes provided" to zero. If you only want to know how
many bytes of error information the API has provided (and nothing
else) you can set the "bytes provided" field to eight. If you want to
receive the exception id and 100 bytes of exception data, you can set
the "bytes provided" field to 116.
The next field is the "Bytes available" field. This is an "OUTPUT"
field, which means that the API will set its value and return it to
your program. It tells you how much error information was returned by
the API. If this field is set to zero, no error occurred. If it is set
to a higher number, something went wrong. The "Bytes available" field
will never be larger than the "Bytes provided" field.
The "Exception ID" field is a message ID that tells you what went
wrong. A value like "CPF9812" might be returned to tell you that a
file could not be found. These are the same message IDs that are
returned by CL commands that fail. In fact, if you've specified zero
in your "bytes provided" field, errors are sent to you with exception
messages and you can handle them with the CL MONMSG command, just as
you would handle a failed CL command.
Tip: It's important to check "Bytes available" first to determine if
an error condition exists before checking the "Exception ID."
The "Reserved" field is reserved by IBM for future use. You should not
attempt to use it in your programs.
The "Exception Data" field contains the "replacement data" for the
exception ID. The easiest way to understand this data is to look at an
existing message and see how it works. Type the following command:
DSPMSGD CPF9812
You'll get a screen that looks like the following:
--------------------------------------------------------------------
Message ID . . . . . . . . . : CPF9812
Message file . . . . . . . . : QCPFMSG
Library . . . . . . . . . : QSYS
Message text . . . . . . . . : File &1 in library &2 not found.
Select one of the following:
1. Display message text
2. Display field data
5. Display message attributes
30. All of the above
--------------------------------------------------------------------
Choose option 2. You'll see:
--------------------------------------------------------------------
Decimal Vary
Field Data Type Length Positions Length Dump
&1 *CHAR 10 *NO
&2 *CHAR 10 *NO
--------------------------------------------------------------------
These two fields are the "exception data." The first 10 bytes of the
exception data returned to your program will be the &1 field. The next
10 bytes are the &2 field. If the error was supposed to read "File
CUSTMAS in library CUSTLIB not found," then the exception data field
will contain "CUSTMAS CUSTLIB ".
The "Exception data" field can be as large or small as you like. If it
is too small for all of the message data that the API has available,
the extra exception data will be discarded.
Here's a sample "error code" data structure defined in ILE RPG:
*****************************************************
* API error code data structure
*****************************************************
D MyErrCode DS
D BytesProv 10I 0 inz(%size(MyErrCode))
D BytesAvail 10I 0 inz(0)
D MsgID 7A
D Reserved 1A
D MessageData 1000A
For example, if I used this error code data structure with the QLIRNMO
API, as discussed in last week's article, it would look like this:
c callp QLIRNMO( 'MYFILE *LIBL'
c : '*FILE'
c : 'YOURFILE *CURLIB'
c : '1'
c : MyErrCode )
c if BytesAvail <> 0
c ... here I can check MsgID and MessageData
c ... to see what went wrong.
c endif
There is another error code data structure called ERRC0200, which will
be explained in a future newsletter.
USER SPACES
Sometimes the information that an API returns is too large to store in
a simple variable. For example, what if you wanted to get a list of
all of the active jobs on the system? There could be 1000 active jobs.
That's too much information to put into a simple variable.
For these instances, APIs will return data using a special disk object
called a "user space." A user space is similar to a file, except that
it has only one record that can vary in length. This record, or
"space," can be as large as 16 megabytes.
User spaces have certain properties, as follows, that make them ideal
for use with APIs:
a) You can address them with a pointer. This makes it possible for you
to reference them directly with variables from your program rather
than having to do "read" and "write" style operations.
b) You can tell the system to "auto-extend" a user space so that the
system will automatically make the space larger when needed. This
means that you don't need to know in advance how large a user space
needs to be!
c) The system automatically will load user spaces into memory when
they're used frequently. That means that when you access a user space,
it's nearly as fast as direct access to your RAM.
MORE ABOUT OFFSETS
In the last article of this series, I explained that an offset is a
count of bytes from one position to another. When you're defining a
data structure to be used with an API, the "from position" of the data
structure is always one greater than the offset listed in the manual
because the first position of any data structure is always one.
Some APIs, however, will return offsets as variables instead of
listing the offset as a fixed-value in documentation. This is usually
used when the length of the data can vary, and therefore the offset to
that data will also vary.
IMPORTANT: When an offset is returned to your program as a variable,
*NEVER* hard-code it. Even if it appears that the offset will never
change, don't forget that new fields can be added to a data structure
in a future release of OS/400. If you don't want your program to stop
working when that happens, you must not hard-code these offsets!
There are several ways to deal with variable offsets in an RPG
program. The easiest and best way is to use basing pointers.
When you have a variable that's defined with the BASED keyword, that
variable is "based on" a pointer. A full explanation of pointers would
be too much for this article, but I will give you an analogy to help
you understand.
I like to think of a based variable as being like a window on a bus.
When you look at the value of the variable, it's like looking out the
window. When the bus moves you see new areas. The old areas are still
there, but you're no longer looking at them. If you threw something
out the window, it's still where it landed -- but if the bus has
moved, you can't see it anymore. If the bus backs up, you'll be able
to see it again.
Pointers and based variables work the same way. The pointer is set to
an area of memory; this is where the "bus starts from." If you look at
the contents of the based variable, you're "looking out the window of
the bus" -- you're seeing whatever is stored at that position in
memory. When you change the pointer, you "move the bus." Your variable
will now view a different area of memory. If you set the variable to a
value, you're changing what's seen through the window. If the pointer
changes ("the bus moves") you'll no longer see the area that you
changed, but if you set the pointer back to where it was, that value
will still be there.
What does that have to do with APIs? When an API returns a whole list
of data, you set your pointer to the start of that data. When you want
to view the next item in the list, you add an offset to the pointer,
which changes the data that you're viewing. If you want to go back and
view the prior data again, you can set the pointer back.
It's time for an example to illustrate all of these concepts. The
QUSLJOB API lists the jobs on the system. As I mentioned last week,
you can find it by going to the Information Center, clicking on
"Programming," "APIs," and "API Finder." Under "Search by Name," type
"QUSLJOB" and click "GO!"
The Information Center lists the parameters for the API. Using the
process that I explained in last week's article, I create the
following ILE RPG prototype:
*****************************************************
* List Jobs API (QUSLJOB) prototype
*****************************************************
D QUSLJOB PR ExtPgm('QUSLJOB')
D UserSpace 20A const
D Format 8A const
D QualJob 26A const
D Status 10A const
* optional group 1:
D ErrorCode 8000A options(*varsize: *nopass)
* optional group 2:
D JobType 1A const options(*nopass)
D NbrKeyFld 10I 0 const options(*nopass)
D KeyFlds 10I 0 const dim(1000)
D options(*varsize: *nopass)
* optional group 3:
D ContHandle 48A const options(*nopass)
In order to call this API, I need to first create a user space. I do that
with the Create User Space API. You'll find it in the "Object APIs" category
of the Information Center, or you can use the API finder to search for
"QUSCRTUS." After reading that manual page, I know that I need to create
this prototype:
*****************************************************
* Create User Space (QUSCRTUS) API
*****************************************************
D QUSCRTUS PR ExtPgm('QUSCRTUS')
D UserSpace 20A const
D Attrib 10A const
D InitSize 10I 0 const
D InitVal 1A const
D PubAuth 10A const
D Text 50A const
* optional group 1:
D Replace 10A const options(*nopass)
D ErrorCode 8000A options(*varsize: *nopass)
* optional group 2:
D Domain 10A const options(*nopass)
* optional group 3:
D XferSizeReq 10I 0 const options(*nopass)
D OptAlign 1A const options(*nopass)
By the way, it's helpful to put all of these APIs into a "copy book" (/copy
member) so that you can use them in all of your programs without the need to
re-define them each time.
Now that I have a prototype, I can call it to create a user space. The
following code creates a user space called "JOBLIST" in my "QTEMP" library.
To determine if the API worked, I check the "BytesAvail" member of the
MyErrCode data structure; if there was an error, I'll display the message id
on the screen and end the program:
** Create a user space that the QUSLJOB API can store it's
** output into.
**
** The initial size of the user space will be 256k (256 * 1024)
** however, the QUSLJOB API will extend it if it needs to be
** larger.
**
c callp QUSCRTUS( 'JOBLIST QTEMP'
c : 'MYPGMNAME'
c : 256 * 1024
c : x'00'
c : '*USE'
c : 'List of active jobs'
c : '*YES'
c : MyErrCode )
c if BytesAvail <> 0
c eval Msg = 'QUSCRTUS API failed with ' +
c 'error ' + MsgID
c dsply Msg
c eval *inlr = *on
c return
c endif
Now that I have a user space to put its results into, I call the QUSLJOB API
to list the active jobs. Once again, I check to see if an error occurred. If
so, I'll display it and end the program.
**
** List all active jobs on the system. The output
** will go into the user space we created (above)
**
c callp QUSLJOB( 'JOBLIST QTEMP'
c : 'JOBL0100'
c : '*ALL *ALL *ALL'
c : '*ACTIVE'
c : MyErrCode )
c if BytesAvail <> 0
c eval Msg = 'QUSLJOB API failed with ' +
c 'error ' + MsgID
c dsply Msg
c eval *inlr = *on
c return
c endif
Incidentally, you probably want to handle errors differently in your own
application. You'll probably want to display the error message in a display
file in your interactive application. The use of the DSPLY op-code is only
done to simplify things in this demonstration.
If the QUSLJOB API succeeded, the list of jobs is now in the user space. The
next step is to get a pointer to that user space in order to read the list.
This is done with the "Retrieve Pointer to User Space (QUSPTRUS)" API.
As before, I look this API up in the Information Center and I create a
prototype:
*****************************************************
* Retrieve Pointer to User Space (QUSPTRUS) API
*****************************************************
D QUSPTRUS PR ExtPgm('QUSPTRUS')
D UserSpace 20A const
D Pointer *
D ErrorCode 8000A options(*varsize: *nopass)
Unless I typed the user space name incorrectly, I can't imagine why this API
would fail. Rather than complicate my code with error handling for something
that I don't expect to fail, I'll set the "Bytes Provided" in my error code
structure to zero. This way, the system will send me an exception message
and unless I use the MONITOR op-code or a *PSSR to catch the error, my
program will crash.
**
** Get a pointer to the user space. In this example, the
** "Bytes Provided" field is zero, so the program will halt
** with a "CPFxxxx" error if something goes wrong:
**
c eval BytesProv = 0
c callp QUSPTRUS( 'JOBLIST QTEMP'
c : p_ListHeader )
The pointer called "p_ListHeader" points to the very start of my user space.
According to the documentation for the QUSLJOB API (which is the API that
populated the user space), there's a "generic header" data structure at the
start of the user space. In that generic header are fields that list the
offset to the start of the list, the size of each entry in the list, and the
number of entries in the list.
*****************************************************
* Generic Header Format used by the "List APIs"
*
* There is a lot of information returned in the
* generic header, but all I'm interested in is
* the offsets needed to access the list entries
* themselves.
*****************************************************
D p_ListHeader s *
D ListHeader ds based(p_ListHeader)
D DataOffset 125 128I 0
D NumEntries 133 136I 0
D EntrySize 137 140I 0
In order to get these values, I've created a data structure that's based on
the "p_ListHeader" pointer. This data structure is the "window of the bus."
Since p_ListHeader is pointing to the start of the user space, I'm looking
at the values at the start of the user space.
To read the list data, I will create another data structure that's based on
a different pointer. Since I told the QUSLJOB API that I wanted to use
format JOBL0100, the data structure for each entry in the list is the one
that's documented in the section about the "JOBL0100 format." It looks like
this:
*****************************************************
* This structure is designed to match format
* JOBL0100 the QUSLJOB API.
*****************************************************
D p_ListEntry s *
D JOBL0100 ds based(p_ListEntry)
D JobName 10A
D JobUser 10A
D JobNbr 6A
D InternalID 16A
D Status 10A
D JobType 1A
D JobSubtype 1A
The list data starts at a variable offset. This offset is the one that I
called "DataOffset" in my ListHeader data structure. Again, an offset is a
count of bytes from a starting position to a desired location. DataOffset is
the number of bytes that I need to move the pointer ("move the bus") from
the start of the User Space in order to read the first entry. To get to that
position, I can add DataOffset to p_ListHeader.
c eval p_ListEntry = p_ListHeader + DataOffset
To read the subsequent entries in the list, I will move the pointer forward
to the next list position. This is done by adding the size of each list
entry (which is also a variable offset) to the p_ListEntry pointer.
c for EntryNo = 1 to NumEntries
c*** ... at this point, the JOBL0100 data structure contains
c*** ... one of the entries of the list.
c eval p_ListEntry = p_ListEntry + EntrySize
c endfor
Each time I change the value of p_ListEntry, I'm "driving the bus." So, each
time through the loop, a different value will be in the JOBL0100 data
structure.
The JOBL0200 format of the QUSLJOB API is a little more complicated. In
addition to everything that I did for the JOBL0100 format, I have to contend
with variable-length fields at the end of each entry.
To handle these, I will once again use pointer logic. I will set a pointer
to the first of the variable-length entries and then move through each entry
by adding a variable offset.
I'm out of space for this week's newsletter. In the next newsletter, I will
continue this series with information about ILE "bindable" APIs.
Two sample programs, one that demonstrates the JOBL0100 format as detailed
above and another that demonstrates the JOBL0200 format with the variable-
length entries, can be downloaded from the iSeries Network Web site at the
following link:
http://www.iseriesnetwork.com/noderesources/code/clubtechcode/GettingStartedWithAPIs2.zip
You can read the previous article in this series at the following link:
http://www.iseriesnetwork.com/resources/clubtech/index.cfm?fuseaction=ShowNewsletterIssue&ID=18618
**********************************************************************
SPECIAL ISERIES NETWORK OFFER
Find out which iSeries applications are candidates to take to the Web.
Craig Pelkie and the iSeries Network will host this FREE Webcast on 5/25
at 12 p.m. Eastern and will cover: preparing for Web projects, Web
enablement solutions, off-platform solutions, using Web servers, and
more. You'll also learn about technologies and tools that are already on
your iSeries. Enroll now:
http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80BHyU0AT
**********************************************************************
SPECIAL ISERIES NETWORK OFFER
JAVA E-CLASS STARTS IN TWO WEEKS
Save your seat now for this popular e-learning class, "Intro to Java,"
starting May 25 at the iSeries Network. iSeries expert Sharon Hoffman
teaches you basic Java programming concepts and syntax, how to access
AS/400 resources from Java programs, how to set up a Java development
environment, how to write a Java program that displays information
from an AS/400 database, and more. The class costs just $299 for
iSeries Network Pro VIPs. Sign up today at
http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80Lfj0AT .
***************** ABOUT ISERIES NETWORK NEWSLETTERS ******************
WHY DID I GET ONLY ONE TIP WHEN THREE ARE LISTED?
The last two tips in this newsletter are premium content that is
available only to Pro and Pro VIP members of the iSeries Network.
If you're not a Pro or Pro VIP member, you will see the titles of
these two extra tips but will not be able to read their content.
Please consider upgrading your membership. If you do, you will receive
not only the two bonus tips but also a subscription to iSeries NEWS
magazine -- and the cost can be as low as USD $29/year!
More info about the benefits is here:
http://www.iseriesnetwork.com/info/join/membership_levels/index_nojs.htm...
The Subscribe/Join page is here:
http://www.iseriesnetwork.com/join/
If you're a Pro or Pro VIP member and you're not getting the extra
tips, here's what you need to do:
a) Follow this link:
http://www.iseriesnetwork.com/info/profile/profilelogin.cfm
b) Make certain that the e-mail address you're using matches the e-
mail that you're subscribed to this newsletter with. If not,
unsubscribe using the instructions at the bottom of the newsletter
and then resubscribe through the membership profile page.
c) If you're still having problems, contact
mailto:service@iseriesnetwork.com .
***************** ABOUT ISERIES NETWORK NEWSLETTERS ******************
Club Tech iSeries Programming Tips Newsletter is published weekly on
Thursday, except for the first Thursday of the month. Club Tech
iSeries Systems Management Newsletter is published on alternate
Wednesdays. NEWS Wire Daily brings you iSeries industry news, tech
tips, product news, and IBM announcements Monday through Thursday.
All are *FREE OF CHARGE*!
IF YOU HAVE a technical question, please submit it to
mailto:clubtechprogrammingtips@iseriesnetwork.com or post it in the
appropriate iSeriesNetwork.com forum. This issue of the Programming
Tips newsletter was edited by Scott Klement, at
mailto:sklement@iseriesnetwork.com .
FOR NEW SUBSCRIPTIONS, you can subscribe by joining the iSeries
Network with our handy Web form at
http://www.iseriesnetwork.com/join/ .
TO CHANGE YOUR E-MAIL ADDRESS, modify your iSeries Network profile at
http://www.iseriesnetwork.com/info/profile/profilelogin.cfm .
IF YOU HAVE PROBLEMS subscribing or unsubscribing or have questions
about your subscription, e-mail customer service at
mailto:prgrmtips@iseriesnetwork.com .
IF YOU WANT TO SPONSOR a Club Tech iSeries Programming Tips
Newsletter, please contact your iSeries Network sales manager. Click
here for details:
http://www.iseriesnetwork.com/info/mediakit/ad_contacts.cfm .
iSeries is a trademark of International Business Machines (IBM)
Corporation and is used by Penton Media, Inc., under license. iSeries
NEWS, iSeriesNetwork.com, and the iSeriesNetwork.com newsletters are
published independently of IBM, which is not responsible in any way
for the content. Penton Media, Inc., is solely responsible for the
editorial content and control of iSeries NEWS, iSeriesNetwork.com, and
the iSeriesNetwork.com newsletters.
___________________________
Copyright 2004
Penton Technology Media, 221 E. 29th St., Loveland, CO 80538
(800) 793-5714 or (970) 203-2894
http://www.iSeriesNetwork.com