Contents:

The Basics Of Memory and Addresses

Every computer contains "storage" that is used to store programs, files, variables, etc. This storage (which we will also be referring to as "memory", even though it could actually be disk space on any modern operating system) is usually numbered, so that each byte within it has a unique address.

When you run a program, it needs to use space in memory to put things like procedures, variables, etc. Each job running the program (or any other program) needs its own unique space in memory that won't conflict with every other job running every other program. It therefore is up to the operating system to assign valid areas of memory when requested.

The process of requesting that memory be made available to the program is called "allocating memory". A typical scenario works something like this:

  • A program is called.
  • When the program needs a place to store a variable, it asks the operating system, saying something like "I need 10 bytes of memory"
  • The operating system replies with something like "You can use 10 bytes of memory beginning at location 71533!"
  • The program continues running, and when its done, says to the operating system "I'm done with the memory at location 71533"

So what's a pointer?

A pointer is nothing more than a variable that holds a memory address!

Since the address in memory that any given variable starts at can vary from program to program and job to job, this address must be stored in some sort of "memory address variable." This type of variable is called a "pointer".


How is this useful?

Normally, the RPG compiler will automatically create program code that will allocate memory to the variables that you've defined, saving you all of the work of having to do this yourself.

Sometimes, however, its handy to be able to directly work with the memory addresses yourself. This allows you to:

  • Place two or more variables into the same area of memory.
  • Move a variable from one place in memory to another
  • Move an entire data structure from place to place in memory (very useful for processing lists)
  • Allow you to create lists of data that can grow and shrink in memory usage as needed
  • Allow you to circumvent the limit of 32k that can be stored in a single character string in RPG


A Simple Example

Here is a very simple example of pointers in RPG IV:

     D Var1            S             12A
     D Ptr             S               *
     D Var2            S             12A   based(Ptr)

     c                   eval      Ptr = %addr(Var1)

      ** This displays "Hello World!"
     c                   eval      Var1 = 'Hello World!'
     c                   dsply                   Var2

      ** This displays "World Hello!"
     c                   eval      Var2 = 'World Hello!'
     c                   dsply                   Var1

     c                   eval      *inlr = *on
     c                   return

First, look at the D-Specs. "Var1" is declared as an ordinary, 12-char long variable. The compiler will automatically generate code to allocate memory to this location when the program runs. Var2 is also a 12-char long variable, but this time we've told it that Var2 is based on a pointer called "Ptr". Because of this, no memory is allcated to Var2 when the program starts, and Var2 will always occupy the area of memory thats pointed to by the pointer called Ptr.

First first line in the C-specs puts the address of the Var1 variable into the pointer called Ptr. Since Var2 is based on Ptr, it effectively moves the location in memory of Var2 to be the same as Var1!

To demonstrate this, we assign a value of "Hello World!" to Var1. Note that when we display Var2, it also says "Hello World!". We further demonstrate it by changing the value of Var2 to be "World Hello!", once again, we see that this change also affected Var1.


Pointer Arithmetic

As demonstrated in the previous example, its easy to put the address of another variable into a pointer. But what if there isn't already a variable in the area of memory that we want to point to? What if we want to move through a list of data? Thats where pointer arithmetic comes in.

As I write this, I'm still using V3R2 of OS/400. In V3R2, you can't do direct pointer math, you have to set your pointer equal to an address of another variable! I created a subprocedure that will allow me to add an arbitrary value to my pointer using an array. Here it is:

Member: QRPGLESRC,OFFSET_H:

     D OffsetPtr       PR              *
     D   pePointer                     *   Value
     D   peOffset                    10I 0 Value

Member: QRPGLESRC,OFFSETR4

     H NOMAIN

     D/COPY QRPGLESRC,OFFSET_H

     P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     P* Return a pointer at a specified offset value from another ptr
     P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     P OffsetPtr       B                   EXPORT
     D OffsetPtr       PI              *
     D   pePointer                     *   Value
     D   peOffset                    10I 0 Value

     D p_NewPtr        S               *
     D wkMove          S              1A   DIM(4097) BASED(p_NewPtr)

     c                   eval      p_NewPtr = pePointer

     c                   if        peOffset > 0

     C                   dow       peOffset > 4096
     C                   eval      p_NewPtr = %addr(wkMove(4097))
     c                   eval      peOffset = peOffset - 4096
     c                   enddo

     C                   eval      p_NewPtr = %addr(wkMove(peOffset+1))

     c                   endif

     c                   return    p_NewPtr
     P                 E

In this example, parameters begin with the prefix "pe", pointers with the prefix "p_" and internal work variables with the prefix "wk".

Here's what it does:

  • Takes a source pointer, and bases a 4097 byte array on it.
  • If the amount to move the pointer in memory is more than 4096, it moves the array 4096 bytes forward in memory. It does this until theres less than 4096 bytes left to move the pointer.
  • Finally, it uses the array to position the new pointer to point to a the desired area of memory.

If you're using a newer release of OS/400 that can do direct pointer math, you don't need to use the OffsetPtr subprocedure. Instead, you can just do something like this:

     c                   eval      p_NewPtr = pePointer + peOffset
In my examples, however, I'll be using the OffsetPtr subprocedure because I'm using V3R2 and can't test it, otherwise :)

To Compile the OffsetPtr service program:

  • CRTRPGMOD MODULE(OFFSETR4) SRCFILE(*LIBL/QRPGLESRC) DBGVIEW(*LIST)
  • CRTSRVPGM SRVPGM(OFFSETR4) EXPORT(*ALL)

A More Practical Example: A trigger with an external record format

In this example, I'm going to load an externally defined record format into a data structure that's based on a pointer. I'll then use my pointer to put that data structure into the area of memory thats required to use it in a trigger program. Finally, I'll use this record image to update the "record maintenance date" and "maintenance user" fields in the file.

This example uses the OffsetPtr service program that we just created. It also uses a file from my own system called "CUSTMAS" (our Customer Master file) to make this work on your system, you'll need to change the references to CUSTMAS to some externally described file that you use in your shop, and change "cuMDat" and "cuMUsr" to the fields for date & userid respectively.

Source Member: QRPGLESRC,SAMPTRIGR4

     D/COPY QRPGLESRC,OFFSET_H

     D AFTER           C                   CONST('1')
     D BEFORE          C                   CONST('2')
     D INSERT          C                   CONST('1')
     D DELETE          C                   CONST('2')
     D UPDATE          C                   CONST('3')

      ***  "Before" image of the record buffer
     D dsBefore      e ds                  EXTNAME(CUSTMAS)
     D                                     BASED(p_Before)
     D                                     PREFIX(B_)
     D p_Before        S               *

      ***  "After" image of the record buffer
     D dsAfter       e ds                  EXTNAME(CUSTMAS)
     D                                     BASED(p_After)
     D                                     PREFIX(A_)
     D p_After         S               *

      *** Trigger information passed by the operating system
      ***   as a parameter to this program.
     D dsTrg           ds
     D   dsTrgFile             1     10A
     D   dsTrgLib             11     20A
     D   dsTrgMbr             21     30A
     D   dsTrgEvent           31     31A
     D   dsTrgTime            32     32A
     D   dsTrgCmtLk           33     33A
     D   dsTrgFill1           34     36A
     D   dsTrgCSID            37     40I 0
     D   dsTrgFill2           41     48A
     D   dsTrgBOff            49     52I 0
     D   dsTrgBLen            53     56I 0
     D   dsTrgBNOff           57     60I 0
     D   dsTrgBNLen           61     64I 0
     D   dsTrgAOff            65     68I 0
     D   dsTrgALen            69     72I 0
     D   dsTrgANOff           73     76I 0
     D   dsTrgANLen           77     80I 0
     D   dsTrgFill3           81     96A

     D peLength        S             10I 0
     D wkDateFld       S               D

     D psds           SDS
     D  UserID               254    263A

     C     *ENTRY        PLIST
     C                   PARM                    dsTrg
     c                   parm                    peLength

      **************************************************************
      ** Point the "before buffer" and the "after buffer" to the
      **   external record layouts, as appropriate.
      **************************************************************
     c                   if        dsTrgEvent = UPDATE
     c                               or dsTrgEvent = DELETE
     c                   eval      p_Before = OffsetPtr(%addr(dsTrg):
     c                                           dsTrgBOff)
     c                   endif

     c                   if        dsTrgEvent = UPDATE
     c                               or dsTrgEvent = INSERT
     c                   eval      p_After = OffsetPtr(%addr(dsTrg):
     c                                           dsTrgAOff)
     c                   endif

      **************************************************************
      ** Change the after buffer to contain the current date and
      **   the user who made the change.
      **************************************************************
     c                   if        dsTrgEvent = UPDATE
     c                               or dsTrgEvent = INSERT
     c                   eval      A_cuMDat = *DATE
     c                   eval      A_cuMUsr = UserID
     c                   endif

     c                   return

To Compile & Attach to file:

  • CRTRPGMOD MODULE(SAMPTRIGR4) SRCFILE(*LIBL/QRPGLESRC) DBGVIEW(*LIST)
  • CRTPGM PGM(SAMPTRIGR4) BNDSRVPGM(OFFSETR4)
  • ADDPFTRG FILE(CUSTMAS) TRGTIME(*BEFORE) TRGEVENT(*INSERT) PGM(SAMPTRIGR4) ALWREPCHG(*YES)
  • ADDPFTRG FILE(CUSTMAS) TRGTIME(*BEFORE) TRGEVENT(*UPDATE) PGM(SAMPTRIGR4) ALWREPCHG(*YES)

How it works:

If you're not already familiar with trigger programs, you may want to peruse the IBM manuals and other books that will explain that part to you. My goal here is only to explain how the pointer aspect of this program works.

In the D-specs we pull the external definition of the CUSTMAS file into a data structure. To accomodate the (future) possibility of wanting to compare data in the "before" and "after" record images, I've also prefixed the data strucutres with "B_" and "A_" respectively.

The dsTrg data structure contains the various pieces of information that we'll get passed as a parameter from the operating system when the trigger is called. This includes an "offset" from the start of the data structure to the "before record buffer" and also includes an "offset" from the start of the data structure to the "after record buffer".

This program then changes the pointer that the "before" record image is based on to the address in memory that is "dsTrgBOff" bytes later in memory than the start of the dsTrg data structure, and does a similar thing with the "after" record image.

Finally, we're able to make changes to the after record image, and exit.


Processing a list API

Arguably the most common use for pointers in RPG IV is when reading the output of an API. The "List API's" output their data to a user space. The output is somewhat standardized, so that it will contain: "A user area", "A generic header", "an input parm section", "a header section" and a "list data section."

The first time that I looked at a list API, I remember thinking "you've got to be kidding me! Thats way too complicated!", but as it turns out, its not so bad! The "user area" is only 64 bytes long, and I have not yet found a need to reference the data stored in it. The "generic header" contains little more than offsets to the "input section", "header section" and "list section", along with sizes. Allowing us to skip the "input section" and "header section" if we don't need them. (which we don't for this example!)

In this example, we will list all of the members in a physical (or source physical) file using the QUSLMBRL API. The "sections" that we wish to deal with in this example will be the "generic header" to get our offsets from, and the "list section" to get the details from. We'll also code a "user area", and just put it into our "generic header" structure, since this simplifies the program significantly.

This example will also use a few other API's. The QUSCRTUS API will be used to create a user space to output to, the QUSPTRUS API will be used to get a pointer to the user space, so we can manipulate the data.

You'll note that I'm glossing over a lot of information about the API's themselves. Please bear in mind that explaining API's is not part of the scope of this document. This is, after all, intended to explain how pointers work!

Source Member: QRPGLESRC,LISTMBRR4

      ******************************************************************
      * API Error Code Structure -- used with most non-bindable API's
      ******************************************************************
     D dsEC            DS
     D*                                    Bytes Provided (size of struct)
     D  dsECBytesP             1      4B 0 INZ(256)
     D*                                    Bytes Available (returned by API)
     D  dsECBytesA             5      8B 0 INZ(0)
     D*                                    Msg ID of Error Msg Returned
     D  dsECMsgID              9     15
     D*                                    Reserved
     D  dsECReserv            16     16
     D*                                    Msg Data of Error Msg Returned
     D  dsECMsgDta            17    256


      ******************************************************************
      ** List Header (Generic Header for List API's)
      ******************************************************************
     D p_GenHdr        S               *
     D dsLH            DS                   BASED(p_GenHdr)
     D*                                     User Area
     D   dsLHUsrAra                  64A
     D*                                     Size of Generic Header
     D   dsLHSize                    10I 0
     D*                                     Data structure release & level
     D   dsLHDsLvl                    4A
     D*                                     Format Name
     D   dsLHFormat                   8A
     D*                                     API that generated this
     D   dsLHAPI                     10A
     D*                                     Date & Time generated
     D   dsLHCrtDT                   13A
     D*                                     Status (I=Incomplete,C=Complete
     D*                                             F=Partially Complete)
     D   dsLHStatus                   1A
     D*                                     Size Of User Space Used
     D   dsLHSpcSiz                  10I 0
     D*                                     Offset to input parms section
     D   dsLHInpOff                  10I 0
     D*                                     Size of input parms section
     D   dsLHInpSiz                  10I 0
     D*                                     Header section offset
     D   dsLHHdrOff                  10I 0
     D*                                     Header section size
     D   dsLHHdrSiz                  10I 0
     D*                                     List section offset
     D   dsLHLstOff                  10I 0
     D*                                     List section size
     D   dsLHLstSiz                  10I 0
     D*                                     Count of Entries in List
     D   dsLHEntCnt                  10I 0
     D*                                     Size of a single entry
     D   dsLHEntSiz                  10I 0
     D*                                     CCSID of data in list section
     D   dsLHCCSID                   10I 0
     D*                                     Country ID
     D   dsLHCntry                    2A
     D*                                     Language ID
     D   dsLHLang                     3A
     D*                                     Subsetted List Ind
     D   dsLHSubSet                   1A
     D*                                     Reserved
     D   dsLHReserv                  42A

      ******************************************************************
      * Prototypes:
      ******************************************************************
      * Create User Space API
      *
     D CrtUsrSpc       PR                  ExtPgm('QUSCRTUS')
     D   UsrSpc                      20A   CONST
     D   ExtAttr                     10A   CONST
     D   InitSiz                     10I 0 CONST
     D   InitVal                      1A   CONST
     D   PubAuth                     10A   CONST
     D   Text                        50A   CONST
     D   Replace                     10A   CONST
     D   ErrorCode                  256A

      * Retrieve Pointer to User Space API
      *
     D RtvPtrUS        PR                  ExtPgm('QUSPTRUS')
     D   UsrSpc                      20A   CONST
     D   PtrUsrSpc                     *

      * List Members (QUSLMBR) API
      *
     D ListMbrs        PR                  ExtPgm('QUSLMBR')
     D   UsrSpc                      20A   CONST
     D   Format                       8A   CONST
     D   DataBase                    20A   CONST
     D   Member                      10A   CONST
     D   Override                     1A   CONST
     D   ErrorCode                  256A

      * OffsetPtr service program:
      *
     D/COPY QRPGLESRC,OFFSET_H

      ******************************************************************
      * Local variables:
      ******************************************************************
     D p_MbrName       S               *
     D MbrName         S             10A   BASED(p_MbrName)
     D Offset          S             10I 0
     D Msg             S             50A


      *** Create the User Space QTEMP/MBRDATA
     C                   callp     CrtUsrSpc('MBRDATA   QTEMP':'USRSPC':
     C                             1:x'00':'*ALL':'List of Members in File':
     c                             '*YES': dsEC)

     c                   if        dsECBytesA > 0
     c                   eval      Msg = 'Error ' + dsECMsgID + ' calling -
     c                             QUSCRTUS API!'
     c                   dsply                   Msg
     c                   eval      *inlr = *on
     c                   return
     c                   endif


      *** Ask the List Members API to put a list of members in the
      ***   QRPGLESRC file into the desired user space:
     C                   callp     ListMbrs('MBRDATA   QTEMP':'MBRL0100':
     C                             'QRPGLESRC *LIBL':'*ALL':'0':dsEC)

     c                   if        dsECBytesA > 0
     c                   eval      Msg = 'Error ' + dsECMsgID + ' calling -
     c                             QUSLMBR API'
     c                   dsply                   Msg
     c                   eval      *inlr = *on
     c                   return
     c                   endif


      *** Get a pointer to the start of the user space.
     C                   callp     RtvPtrUS('MBRDATA   QTEMP':p_GenHdr)


      *** Loop through the list of members, each time changing the
      ***   p_MbrName pointer to point to the next member in the list
     C                   do        dsLHEntCnt    Entry             4 0
     c                   eval      Offset = ((Entry-1) * dsLHEntSiz)
     c                               + dsLHLstOff
     c                   eval      p_MbrName = OffsetPtr(p_GenHdr: Offset)
     c     MbrName       dsply
     c                   enddo

     C                   eval      Msg = 'Press ENTER to end program'
     c                   dsply                   Msg

      *** End Program
     c                   eval      *INLR = *On
     c                   Return

To Compile:

  • CRTRPGMOD MODULE(LISTMBRR4) DBGVIEW(*LIST)
  • CRTPGM PGM(LISTMBRR4) BNDSRVPGM(OFFSETR4)

How It Works:

The line with "callp CrtUsrSpc" on it calls the program identified by the CrtUsrSpc prototype, which is the QUSCRTUS API. This API will create a user space thats suitable to receive the output of the "List Members (QUSLMBR)" API. Then, we check to make sure the API was successful by checking to see if any bytes are now in the error code data structure.

Our next step is calling the QUSLMBR API itself. We tell it the name of the user space that we just created, so it knows where to put the data. We also tell it that we want a list of "*ALL" members in the file called "*LIBL/QRPGLESRC". If any error occurs here, there will be bytes in the error code data structure. So, next we check for that, and complain if we get an error!

Finally, we get a pointer to the user space by calling the QUSPTRUS API. This API will be assigning a memory address to the p_GenHdr pointer. Since the List Header Data Structure (dsLH) is based on the p_GenHdr pointer, it will actually occupy the same area of memory as the start of the user space does. This is very useful to us, because now we can read the values of the generic header for the user space directly from the dsLH data structure!

To get an actual list of members in the user space, we set up a loop that goes through each entry of the list. For each entry, we offset the pointer called "p_MbrName" to the position within the user space that contains the member name that we want. Since the "MbrName" variable is based on the p_MbrName pointer, we'll be able to display the member name directly from that variable.


Conclusion

Pointers have gotten a reputation as being complicated and dangerous to use. Many people resist using pointers in RPG IV without understanding what they are for and why they are necessary.

I hope that this tutorial has helped to dispel those fears, and show how pointers can be a useful tool. Your next step is to write programs and experiment! You will learn far more through experimentation and experience than any tutorial can teach you.

Good Luck!