Using QC2LE procedures in ILE RPG
Contents
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 means 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
- Setting DFTACTGRP to *NO on the CRTBNDRPG command
- 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 need 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. IBM's Barbara Morris put a useful web page on Converting from C prototypes to RPG prototypes.
A note here about RPG's B data type is probably in order. Although one might be tempted to use the B data type, don't! Use one of the integer data types, either U (unsigned) or I (signed.) The B data type will truncate large values. It's left in the language for backwards compatibility and is almost never the right thing for use with APIs.
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) <source lang="rpg">
/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)
</source>
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 behaviour 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 equivalent for the above C prototype will therefore 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: <source lang="rpg"> H DftActGrp(*No) H BndDir('QC2LE')
* Define the C pointer and unsigned int types in RPG
D NulPtr S * D Pointer S * Based(NulPtr) D uint S 10U 0 Based(NulPtr)
* Prototype puts - Write line to standard output
D puts Pr ExtProc('puts') D InString Like(Pointer) Value Options(*String)
* sleep(duration) will SIGW the job for <duration> seconds.
D sleep Pr Like(uint) ExtProc('sleep') D duration Like(uint) Value
* Write "Hello world" on the 5250 terminal screen
C callp puts('Hello world')
* Sleep for 5 seconds
C if sleep(5) = 0 C callp puts('We have slept for 5 seconds') C else C callp puts('Our sleep was interrupted for some unknown reason') C endif
C eval *InLR = *On </source>
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.