4.5. Packaging our utilities into a service program

Now that we've written our RdLine() and WrLine() procedures, a few other details should be ironed out:

  1. We need a prototype for Translate() added to the top of the service program.

  2. We need to include our socket_h header file in our service program.

  3. We need binding language source to use when creating the service program.

I'll assume at this point that you've already coded the RdLine() and WrLine() procedures from the previous chapters. At the top of the member that you placed those subprocedures in, you'll need the following code:

         H NOMAIN
    
         D/COPY SOCKTUT/QRPGLESRC,SOCKET_H
         D/COPY SOCKTUT/QRPGLESRC,SOCKUTIL_H
    
         D Translate       PR                  ExtPgm('QDCXLATE')
         D    peLength                    5P 0 const
         D    peBuffer                32766A   options(*varsize)
         D    peTable                    10A   const
     

You'll also want to create a member that contains the prototypes for the RdLine and WrLine functions. We'll call this SOCKUTIL_H, and put that in QRPGLESRC as well. It will look like this:

          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          * RdLine(): This reads one "line" of text data from a socket.
          *
          *   peSock = socket to read from
          *   peLine = a pointer to a variable to put the line of text into
          *   peLength = max possible length of data to stuff into peLine
          *   peXLate = (default: *OFF) Set to *ON to translate ASCII -> EBCDIC
          *   peLF (default: x'0A') = line feed character.
          *   peCR (default: x'0D') = carriage return character.
          *
          *  returns length of data read, or -1 upon error
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         D RdLine          PR            10I 0
         D   peSock                      10I 0 value
         D   peLine                        *   value
         D   peLength                    10I 0 value
         D   peXLate                      1A   const options(*nopass)
         D   peLF                         1A   const options(*nopass)
         D   peCR                         1A   const options(*nopass)
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  WrLine() -- Write a line of text to a socket:
          *
          *      peSock = socket descriptor to write to
          *      peLine = line of text to write to
          *    peLength = length of line to write (before adding CRLF)
          *            you can pass -1 to have this routine calculate
          *            the length for you (which is the default!)
          *     peXlate = Pass '*ON' to have the routine translate
          *            this data to ASCII (which is the default) or *OFF
          *            to send it as-is.
          *      peEOL1 = First character to send at end-of-line
          *            (default is x'0D')
          *      peEOL2 = Second character to send at end-of-line
          *            (default is x'0A' if neither EOL1 or EOL2 is
          *            passed, or to not send a second char is EOL1
          *            is passed by itself)
          *
          * Returns length of data sent (including end of line chars)
          *    returns a short count if it couldnt send everything
          *    (if you're using a non-blocking socket) or -1 upon error
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         D WrLine          PR            10I 0
         D  peSock                       10I 0 value
         D  peLine                      256A   const
         D  peLength                     10I 0 value options(*nopass)
         D  peXLate                       1A   const options(*nopass)
         D  peEOL1                        1A   const options(*nopass)
         D  peEOL2                        1A   const options(*nopass)
     

If you'd prefer to download my copy of SOCKUTIL_H instead of writing your own, you can get it here: http://www.scottklement.com/rpg/socktut/qrpglesrc.sockutil_h

Finally, we need to create binding source to tell the system how to create our service program. If you're not familiar with this, I'll explain it a little.

When creating a service program, the system calculates a 'signature' for your service program. This works in a similar manner to the way 'record format level checks' work on a database file -- if something in the service program changes, they prevent you from possibly accessing it incorrectly.

Binding language serves to tell the system which procedures are exported, and also to tell the system which procedures were exported LAST time you ran CRTSRVPGM, and maybe the time before that, and so on. If you don't specify binding language for your service program, each time a procedure changes in it, you'll have to recompile ALL of the programs that call that service program. If you don't, you'll get "Signature Violation" errors when trying to call them! But, thanks to the ability to keep track of 'previous exports' in a service program, you can make your service program backwards compatible.

I hope that's clear enough -- if not, you probably want to read the 'ILE Concepts' manual, as this tutorial really isn't intended to teach all of the nuances of service programs and signatures.

At any rate, it's very easy to create binding source. It looks like this:

    Member SOCKUTILR4 of file QSRVSRC:
    STRPGMEXP PGMLVL(*CURRENT)
         EXPORT SYMBOL(RDLINE)
         EXPORT SYMBOL(WRLINE)
    ENDPGMEXP
     

That's it... it's that simple... we've told that this is the program exports for the current level of our service program, and which procedures to export. It can hardly be easier!

Now, we compile our service program by typing:

CRTRPGMOD MODULE(SOCKUTILR4) SRCFILE(SOCKTUT/QRPGLESRC) DBGVIEW(*LIST)

CRTSRVPGM SRVPGM(SOCKUTILR4) EXPORT(*SRCFILE) SRCFILE(SOCKTUT/QSRVSRC)

And, while we're at it, lets create a binding directory to make our service program available to the CRTBNDRPG command. We do this by typing:

CRTBNDDIR BNDDIR(SOCKTUT/SOCKUTIL) TEXT('Socket Utility Binding Directory')

ADDBNDDIRE BNDDIR(SOCKTUT/SOCKUTIL) OBJ((SOCKTUT/SOCKUTILR4 *SRVPGM))