Published on System iNetwork (http://systeminetwork.com)
Club Tech iSeries Programming Tips Newsletter
By tzura
Created May 20 2004 - 04:00

An iSeries Network Publication           
http://www.iseriesnetwork.com [1]
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 [2]

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 [3] 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 [4] ********************************************************************** 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 [5] ********************************************************************** 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 [6] . ***************** 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... [7] The Subscribe/Join page is here: http://www.iseriesnetwork.com/join/ [8] 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 [9] 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 [10] . ***************** 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 [11] 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 [12] . FOR NEW SUBSCRIPTIONS, you can subscribe by joining the iSeries Network with our handy Web form at http://www.iseriesnetwork.com/join/ [13] . TO CHANGE YOUR E-MAIL ADDRESS, modify your iSeries Network profile at http://www.iseriesnetwork.com/info/profile/profilelogin.cfm [14] . IF YOU HAVE PROBLEMS subscribing or unsubscribing or have questions about your subscription, e-mail customer service at mailto:prgrmtips@iseriesnetwork.com [15] . 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 [16] . 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 [17]
Copyright © Penton Media

Source URL: http://systeminetwork.com/node/61343

Links:
[1] http://www.iseriesnetwork.com
[2] http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80BIRV0Aq
[3] http://www.iseriesnetwork.com/noderesources/code/clubtechcode/GettingStartedWithAPIs2.zip
[4] http://www.iseriesnetwork.com/resources/clubtech/index.cfm?fuseaction=ShowNewsletterIssue&ID=18618
[5] http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80BHyU0AT
[6] http://lists.pentontech.com/cgi-bin3/DM/y/efyM0CWySP0Hp80Lfj0AT
[7] http://www.iseriesnetwork.com/info/join/membership_levels/index_nojs.html
[8] http://www.iseriesnetwork.com/join/
[9] http://www.iseriesnetwork.com/info/profile/profilelogin.cfm
[10] mailto:service@iseriesnetwork.com
[11] mailto:clubtechprogrammingtips@iseriesnetwork.com
[12] mailto:sklement@iseriesnetwork.com
[13] http://www.iseriesnetwork.com/join/
[14] http://www.iseriesnetwork.com/info/profile/profilelogin.cfm
[15] mailto:prgrmtips@iseriesnetwork.com
[16] http://www.iseriesnetwork.com/info/mediakit/ad_contacts.cfm
[17] http://www.iSeriesNetwork.com