Using QC2LE procedures in ILE RPG

From MidrangeWiki
Revision as of 11:36, 4 February 2008 by Garethu (talk | contribs) (Standard C types)
Jump to: navigation, search

Overview

On the iSeries, IBM supplies a large number of C standard library [1] and iSeries specific procedures pre-compiled in Service Programs. Any ILE language can access these procedures by prototyping them and binding the IBM supplied implementations.

This article is intended to give an overview on how to use these IBM provided procedures in ILE RPG programs.

Benefits of using QC2LE procedures

  • The procedures are all ready to use, saving a lot of development time
  • The procedure implementations have all passed IBM Quality Control, reducing the risk of faulty code
  • The procedures are mostly based on established standards. This also mean that documentation is readily available


Notes

Service Programs on the iSeries serves the same function as DLLs do in Microsoft Windows and libraries do in Unix environments

Program Compilation

In order to make use of ILE features, RPG programs should be compiled not to use the Default Activation Group. There are two ways this can be accomplished

  1. Setting DFTACTGRP to *NO on the CRTBNDRPG command
  2. Adding an H specification that disables the Default Activation Group
0016.00 H DftActGrp(*No)

The Binding Directory

IBM provides a binding directory called QSYS/QC2LE that groups all the provided Service Programs together.

The command WRKBNDDIRE QSYS/QC2LE will display all the service programs contained within this binding directory.

When binding QC2LE functions to an ILE RPG programs the compiler needs to be informed that it should look for procedure implementations by searching the QSYS/QC2LE binding directory. This is done by including an H specification in the program, as follows:

0017.00 H BndDir('QC2LE')

The Service Programs

Each service program within the binding directory contains a specific set of procedures that may be used.

For example:

  • QC2SYS -- Access to execute system (CL) commands
  • QC2UTIL1/2/3 -- Some utility and C standard library procedures
  • QC2POSIX -- General POSIX procedures
  • QC2IO -- Input and Output related procedures

The service programs provides two things that are of interest, these are Procedure exports and Data exports

The following commands can be used to view these exports (replace QC2IO with any of the service program names):

  • DSPSRVPGM SRVPGM(QSYS/QC2IO) DETAIL(*PROCEXP) - Procedure exports
  • DSPSRVPGM SRVPGM(QSYS/QC2IO) DETAIL(*DTAEXP) - Data exports

Both exported procedures and exported data can be accessed from within your ILE program.

The Procedures

Documentation

Due to most of the procedures being standards based it is easy to find information about them. On *nix operating systems there are generally man (manual) pages available that describe each procedure. These can be accessed by issuing the command man <procedure name> from a the shell.

These man pages are also commonly available on the internet and can simply be found using a search engine. Take the puts procedure from service program QSYS/QC2IO as an example. By searching Google for unix man puts pages like this one can be found.

Here is the same page from a recent Ubuntu Linux system

PUTS(3)                                                                Linux
Programmer’s Manual

NAME
       fputc, fputs, putc, putchar, puts - output of characters and strings

SYNOPSIS
       #include <stdio.h>

       int fputc(int c, FILE *stream);
       int fputs(const char *s, FILE *stream);
       int putc(int c, FILE *stream);
       int putchar(int c);
       int puts(const char *s);

DESCRIPTION
       fputc() writes the character c, cast to an unsigned char, to stream.

       fputs() writes the string s to stream, without its trailing ’\0’.

       putc() is equivalent to fputc() except that it may be implemented as a macro 
       which evaluates stream more than once.

       putchar(c); is equivalent to putc(c,stdout).

       puts() writes the string s and a trailing newline to stdout.

       Calls  to the functions described here can be mixed with each other and with
       calls to other output functions from the stdio library for the same output stream.

       For non-locking counterparts, see unlocked_stdio(3).

RETURN VALUE
       fputc(), putc() and putchar() return the character written as an unsigned char cast
       to an int or EOF on error.

       puts() and fputs() return a non-negative number on success, or EOF on error.

CONFORMING TO
       C89, C99

BUGS
       It is not advisable to mix calls to output functions from the stdio library with 
       low-level calls to write(2) for the file descriptor associated with the  same
       output stream; the results will be undefined and very probably not what you want.

SEE ALSO
       write(2), ferror(3), fopen(3), fputwc(3), fputws(3), fseek(3), fwrite(3), gets(3), 
       putwchar(3), scanf(3), unlocked_stdio(3)

GNU
1993-04-04

These man pages give an overview of what the procedure does and a provide a C prototype for using the procedure. These C prototypes are also available on the iSeries in the QSYSINC library. puts for example can be found in source member QSYSINC/H.STDIO. Notice that this matches the stdio.h include in the man page.

These C prototypes needs to be translated to RPG in order to use the procedures in a RPG program.

Translating C prototypes to RPG

One does not have to be a C expert in order to translate C prototypes to RPG, but a little background information is very helpful.

Standard C types

There are a number of standard C types that can be easily converted to RPG. Generally it is easiest to do all these in a copybook that can then be used in all RPG programs that need them.

The following is a copybook with some of the commonly used types (the compiler directives at the top is just an easy way to ensure that a copybook is not copied by accident more than once)

      /If Not Defined(CTYPES)
      /Define         CTYPES
      /Else
      /Eof
      /EndIf

      * Pointers
     D NulPtr          S               *
     D Pointer         S               *   Based(NulPtr)
     D BuffPtr         S               *   Based(NulPtr)
     D ProcPtr         S               *   Based(NulPtr) ProcPtr

      * C Types
     D char            S              1    Based(NulPtr)
     D short           S              5I 0 Based(NulPtr)
     D int             S             10I 0 Based(NulPtr)
     D long            S             10I 0 Based(NulPtr)

     D uchar           S                   Like(char)
     D ushort          S              5U 0 Based(NulPtr)
     D uint            S             10U 0 Based(NulPtr)
     D ulong           S             10U 0 Based(NulPtr)

     D float           S              8F   Based(NulPtr)
     D smallfloat      S              4F   Based(NulPtr)

C variable declarations

Variable are declared in C in the following format

<<signedness>> <<size>> <<type qualifiers>> <type specifier>

<<*>><variable name>

where <<>> are optional items, <> are required

For instance

int myinteger;

declares a variable called myinteger with type int


C procedure prototypes

The man page for the sleep procedure [2] provides the following C prototype

unsigned int sleep(unsigned int seconds);

This states the following:

  • The procedure will return an unsigned int
  • The procedure requires an unsigned int specifying the number of seconds to sleep as input. seconds here is simply the name of the variable used on the prototype

Passing parameters by value or by reference

There are two ways to pass parameters to a procedure

  • When passing by value, the actual value of the parameter is placed on the stack before the procedure is called. An example is the sleep procedure
unsigned int sleep(unsigned int seconds);
  • When passing by reference, it is not the actual value of the parameter that is placed on the stack, but rather a pointer to that value. An example is the puts procedure. This passing method can be identified by the * in front of the variable name that denotes a pointer to the variable.
int puts(const char *s);

RPG procedures default to passing parameters by reference. This behavior can be changed by adding the Value keyword on parameter definitions.

Creating a RPG prototype

We now have enough background information to implement something like the sleep procedure in RPG.

unsigned int sleep(unsigned int seconds);

The RPG D spec Pr needs to state that an external procedure sleep is to be called, that it takes an unsigned int as a parameter passed by value and that it will return an unsigned int.

An RPG equivilant for the above C prototype will therefor look like the following

0034.00  * sleep(duration) will SIGW the job for <duration> seconds.             
0035.00 D sleep           Pr                  Like(uint) ExtProc('sleep')  
0036.00 D  duration                           Like(uint) Value           


Notes
The procedure name used internally within the RPG program does not have to match the external name. It is valid to do the following:
0035.00 D delay Pr Like(uint) ExtProc('sleep')
and then use delay(123) rather than sleep(123) in order to invoke the procedure

A note about null terminated strings

Since C strings defined by char *mystring do not specify a length for the string, some other mechanism needs to be employed to determine the end of a string. This is done by terminating a string with a Null character, x'00'. When defining a prototype that has a null terminated string as a parameter the Options(*String) keyword can be used in RPG to ensure that this Null character is added automatically.

Using puts as an example,

int puts(const char *s);

both the following RPG prototype declarations are valid

0008.00 D puts            Pr                  Like(uint) ExtProc('puts')                            
0009.00 D  InString                           Like(Pointer) Value

0015.00 C                   callp     puts('Hello world'+x'00') 
       

and

0008.00 D puts            Pr                  Like(uint) ExtProc('puts')                            
0009.00 D  InString                           Like(Pointer) Value Options(*String)      

0015.00 C                   callp     puts('Hello world') 

but in the first the x'00' terminator will have to be manually supplied while it is taken care of automatically in the second

Putting it all together in a program

Hello world

Program Requirement 
Write Hello world on the screen and keep it there for 5 seconds

The puts procedure from QSYS/QC2IO can be used to write directly to standard output and sleep from QSYS/QC2POSIX can be used to delay the job for 5 seconds.

The following constitutes a complete working program that does just that:

0001.00 H DftActGrp(*No)                                                                 
0002.00 H BndDir('QC2LE')                                                                
0003.00    
0003.01  * Define the C pointer and unsigned int types in RPG
0004.00 D NulPtr          S               *
0004.01 D Pointer         S               *   Based(NulPtr)
0005.00 D uint            S             10U 0 Based(NulPtr)
0006.00                                                                                  
0007.00  * Prototype puts - Write line to standard output                                                 
0008.00 D puts            Pr                  ExtProc('puts')                            
0009.00 D  InString                           Like(Pointer) Value Options(*String)      
0010.00                                                                                  
0011.00  * sleep(duration) will SIGW the job for <duration> seconds.             
0012.00 D sleep           Pr                  Like(uint) ExtProc('sleep')  
0013.00 D  duration                           Like(uint) Value           
0014.00  
0014.01  * Write "Hello world" on the 5250 terminal screen                                                                                
0015.00 C                   callp     puts('Hello world') 
0015.01  * Sleep for 5 seconds                               
0016.00 C                   if        sleep(5) = 0                                          
0016.01 C                   callp     puts('We have slept for 5 seconds') 
0016.02 C                   else
0016.03 C                   callp     puts('Our sleep was interrupted for some unknown reason') 
0017.00 C                   endif     
0017.01                                                           
0018.00 C                   eval      *InLR = *On                                          

In the above example the result returned by puts is simply ignored, while the result returned by sleep is tested to see if the program did in fact sleep for the full 5 seconds.