Impossible though it may seem, you can bind C functions to RPG IV programs. Since so much software has been written in C, you may very well find that great solutions to the problems you face have already been written in C for other systems and are just waiting for you to put them to work on your AS/400. This article uses Cs character-to- number conversion functions to show you how to bind C functions to RPG.
When I was asked to write an article that would adequately demonstrate the use of C functions in ILE RPG applications, I was hard-pressed to think of practical uses. After all, when you think about it, there are only two reasons to use C functions in an RPG application: Either the C function is superior to the RPG-equivalent operation, or no RPG- equivalent operation exists.
For example, say you want to read a customer file by the customer number key. In RPG, you would probably accomplish that with a CHAIN operation. To do the same thing in C (after having previously called the _Ropen()function to open the keyed file and receiving a pointer or handle to the file), you would need to call the Read a Record by Key (_Rreadk()) function specifying the file handle, address of the record buffer to receive data read, and the address of the key used to access the file. Additionally, specifications for file name, open mode, access options, and so on, would need to be passed as null-terminated strings, adding unnecessary complexity to a language that has a far better solution to the problem. What self-respecting RPG programmer would choose the C function over the efficiency and elegance of the CHAIN operation?
Yet many functions have been developed for both the standard C library and offthe-shelf libraries that were developed to handle specific problems in particular industries and to provide a higher level of access to more primitive functions in order to improve programmer productivity and code reuse. And while C is being phased out (very slowly) by its object-oriented incarnation, C++, it is still a thriving language (like RPG), enjoying the full support of many language vendors. This accessibility and availability of language extensions should be factored into your thinking when you make decisions about design implementations. In some cases, the availability of C functions to handle application features may even change the design of an application (yes, even on the AS/400). An example of this continued support can be found in recent additions made to the Call Level
Interface (CLI) for SQL (ODBC on the AS/400). All the new functions implemented to support the native JDBC driver (and implemented in the CLI) for the AS/400 have been written in C. This is not a coincidence. When performance and low-level access to system functions are required, IBM systems development teams have relied on the indisputable superiority of C. In addition, when binding to Java functions using the Java Native Interface (JNI), the steps described in this article can be equally applied to that process (see Don Denoncourts sidebar, Using C Functions with Java on this subject). Yet, notwithstanding RPGs overwhelming support for file I/O, where can you tap into the vast reservoir of language features that C programmers enjoy each day?
What About Concatenation or Substringing Operations?
To use the Concatenate Strings (strcat()) and String Copy (strcpy()) functions, you would have to append a null-terminator character (hex 00) to the end of each text parameter used in these functions because C expects null-terminated strings to be passed. With RPG IV, however, you can accomplish the same thingelegantly and more intuitivelywith the
EVAL statement, the + operator, and the %SUBST built-in function. We have a bounty of operations in RPG to handle file access and text manipulation, as well as a number of other things that C doesnt have. What, then, is a practical application of C functions that does not already exist in RPG?
Absence Makes the Heart Grow Fonder
Given an acceptable RPG solution, using C functions in an RPG application is not my first choice. However, I must concede to the existence of a set of standard functions that deal more extensively with text conversions than RPG. This is the area where C stands to fill the void left by an incomplete set of ILE RPG built-in functions. For releases V3R7 and up, built-in functions were delivered that enable the conversion of zoned and packed numeric fields to float (%FLOAT) and integer (%INT) data types. Yet, conspicuously absent is a function to convert text to float or integer data types.
Why would you want to do this? Well, suppose you had a feature in your application to allow the user to enter alphabetic and numeric characters in a text field for comparative operations to be executed by another process. You would need to convert the textual expression of, say, 100.00 to a numeric-compatible data type that could then be compared to a numeric field value. As I mentioned, there are operations to convert decimal and nondecimal, zoned and packed, numeric field values to float and integer data types. So I can, for example, take a value from a field called AMT and convert it to a float data type with %FLOAT(AMT). However, now I need to take the textual expression of 100.00 and convert it to a float as well (to do a compatible comparison operation), but how? ILE RPG currently has no equivalent for the C functions atof() and atoi() that do just that.
A Rose by Any Other Name...
What, exactly, are the atof() and atoi() functions anyway? IBM describes the atof() as the Convert Character String to Float function, and the atoi() as the Convert Character String to Integer function. Given that the history of C parallels that of UNIX (both were developed at Bell Laboratories), and UNIX uses an ASCII encoding scheme for data storage and recognition, the a in atof() means ASCII, which is used for typical text or byte- stream files, such as those used in UNIX. So, the atof() (or ASCII to Float) and the atoi() (or ASCII to Integer) functions are used by C programmers to convert textual or string representations to float and integer data types, respectively, for comparison operations.
With ILE RPG, you, too, can use these operations to convert your character or text fields to a compatible data type for comparative operations. Before I get to the solution of my immediate problem, I will need to know how to map C data types to equivalent ILE RPG data types and read a C function prototype to convert it to an ILE RPG prototype.
Figure 1 presents a table showing how each C data type maps to equivalent ILE RPG data types.
A Case of Some Importance
In RPG IV, whether or not a subroutine or field is keyed as uppercase or lowercase is of no importance since both are converted to uppercase during compilation. Yet, in C, which is case sensitive, whether you use uppercase or lowercase can make all the difference in the world. If you attempt to bind to a C function identified as atof (all lowercase) in an EVAL statement, atof will be converted to ATOF (all uppercase) during compilation, and the subsequent C function atof will not be found. To avoid this problem, in the prototype for the atof function, you can define the function by identifying it in the ExtProc keyword as ExtProc(atof). Such a declaration preserves the case of letters enclosed by the quotes. Then, you will be able to bind to any C function no matter what the case or special characters might be.
Batteries Not Included!
Lets get a bit of terminology out of the way. When discussing C functions, the term include (#include or header) files arise. Include files are identical to /COPY file members in RPG. For a standard set of C library functions on the AS/400, most of the #include headers can be found in library QCLE, source physical file H, member STDLIB.
Compile-time macros are defined using the #define compiler directive. These macros are replaced by their corresponding values when encountered in source statements during compilation.
In order to properly bind with and utilize C functions, you must first understand what the functions require in the way of arguments and return values. All of this information is disclosed in the function prototype. Standard C function #include header files come with the ILE C/400 licensed program product. If you do not currently license this product, you must request it from IBMs System Development Division for the ILE C/400. Be prepared to furnish a waiver of some sort, verifying that you do not expect any support (and rest assured that support will not be forthcoming from IBM) should you encounter problems using these functions. You may be told that such includes cannot be distributed except to licensed program product holders. Dont dismay; I am told that these include headers are being considered for distribution in the Openness Includes library, QSYSINC.
The C #includes, defined in the QCLE/H file, contain all of the C function prototypes for the standard C functions. These includes are required to determine the number of arguments passed (and their data types) to C functions, values returned from these functions, and the value given to any compile-time macros passed to these functions. Only when you know this information can you successfully bind to these functions. Figure 2 illustrates the prototypes for the atof() and atoi() functions that I will be using in my example.
The atof() function prototype expects a pointer to a character string to be passed to it, as indicated by the phrase const char * enclosed in parentheses. It also expects to return a double-precision floating point value upon successful execution. Figure 3 presents a table that shows the format that you can expect each C function to adopt and gives you an idea of how to convert it to an ILE RPG subprocedure prototype.
Using the format shown in the table for Figure 3 and the data mapping table presented in Figure 1, you should be able to successfully prototype each function to the ILE RPG equivalent, as shown in Figure 4. You will note from Figure 4 that the pointer to the character-string representation of the number or amount is passed by value and not by reference. Most of the standard library functions will require arguments to be passed by value since they will operate upon a copy of the data rather than upon the actual data. If you forget and omit the Value keyword from arguments passed, the compiler will issue error RNF7542 (Parameter cannot be passed by reference when it can be changed during the call). If you receive this error, add the Value keyword to your prototype definition for the function, and recompileit should properly compile.
Completion of the Task
To put our C functions to the task, I have developed a trivial application that allows the user to pass one parameter as a text string representing some number (no decimals) or some amount (decimals). The user can pass a negative number or amount by prefixing the value with a minus sign (such as -100). Since this is a trivial application meant only to demonstrate the use of C functions in an ILE RPG program, I have not added any bells or whistles. The application simply displays the converted string passed as a float or integer value with the DSPLY operation code, and compares the value passed to 100.00 or 100. It then displays another message about the values relationship to 100, as shown in Figure 5.
Case 1 of Figure 5 shows the results when an amount (-980.50) is passed in a call to String to Numeric Value (StrToNV), my program. Note that since the value passed has decimals, the string is converted to a float for purposes of the comparison. The relationship shows that -980.50 is less than 100.00. Case 2 of Figure 5 shows the results when a number is passed as a text argument to StrToNv. Note that 101 was passed, and the resulting relationship test shows that 101 is greater than 100. Thus, the string was converted to an integer for purposes of the test.
Figure 6 illustrates the program source to convert a string of text representing a numeric value to either a float (if it has decimals) or an integer (if it has no decimals). At Label A of Figure 6, the instructions for compiling the program are presented. Note that, in order to successfully compile StrToNV, you must specify the binding directory QC2LE. This binding directory includes the service program that contains the two C functions that I am binding to my application. That service program is Q2UTIL1. Using the DSPSRVPGM SRVPGM(QC2UTIL1) DETAIL(*PROCEXP) command, you can review all of the C functions available in that program. The table presented in Figure 7 shows four service programs in the QC2LE binding directory that make up the bulk of all the C functions you will ever need (unless you are developing a TCP/IP socket programthe subject of an upcoming article).
At Label B of Figure 6, our /COPY statements (or includes, using C parlance) for the function prototypes for atof() and atoi() are defined. Label C denotes the beginning of some fields defined to support the features of StrToNv; in particular, we have defined a float (designated by the f type) and an integer (designated by the i type). The float field TPakFloat will be used to hold the converted packed amount (MyPackedAmt) that was defined with an initial amount of 100.00. MyPackedAmt is included here only to demonstrate the use of the built-in function %FLOAT, as shown at Label E. TPakFloat has been defined as a double (8f), as well as StrInFlt, that will be used to hold the result of the character string conversion function atof() at Label F. TZonInt has been defined as a 4-byte integer to hold the conversion of MyZonedNbr defined with an initial value of 100 at Label
C. The conversion of MyZonedNbr to TZonInt is demonstrated at Label H, while Label I shows the passed string argument StringToCvt field being converted to an integer (no decimals) with our bound C function atoi() for comparison with TZonInt immediately below that. Finally, at Label J, using the EVAL to concatenate the string argument with the selected relationship evaluation, I can display the results with the DSPLY operation code as a last step before ending the application.
More There Than Meets the Eye
When I first started to do the research to write this article, I must say that I was not convinced that there was that much in the C function service programs worth using in an ILE RPG application (unless you had a scientific or statistical program you needed to develop). However, after doing a little legwork and a little generalizing, I found that I have an ongoing need to use these functions in current applications. For example, I have used just these two functions (atof() and atoi()) in an application that enables users to state, in text, their predicate conditions for applying business rules (rules-based applications) for data selection. Additionally, I have used C socket functions for developing ILE RPG TCP/IP socket programs to communicate with applications running on NT servers (emailing invoices to customers). I think that, after reading this article and mulling over
your own projects, you will come to the same conclusion. While RPG can be used to develop subprocedures that are capable of doing the same things as our atof() and atoi() functions, why reinvent the wheel?
For another practical application using C functions where equivalent RPG functions are nonexistent, see TCP/IP Socket Programming in RPG? Cool! in a future issue of MC.
References
Essential JNI: Java Native Interface. Gordon, Rob. Upper Saddle River, New Jersey: Prentice Hall, Inc., 1998.
ILE C for AS/400: Programmers Reference V4R2 (SC09-2514-00, CD-ROM QB3AG100)
ILE C/400 Programmers Reference V3R7 (SC09-2072-01, CD-ROM QBJAQY01)
ILE RPG for AS/400 Reference V4R2 (SC09-2508-00, CD-ROM QB3AGZ00) ILE RPG/400 Reference V4R1 (SC09-2077-01, CD-ROM QBJAQE01)
Using C Functions with Java
I have been using the ILE/C compiler since V2R3. The C programming language is the easiest way to take advantage of the full breadth of OS/400s system APIs. As you might expect, this is also the case in many other operating systems; after all, C has historically been the language of systems programmers.
For this reason, the Java programming language has a feature, the Java Native Interface (JNI), that allows you to easily integrate C (and C++) code with your Java applications. A Java class has a list of functions that describe the behaviors of the entity that the Java class represents. For those functions that you would like to implement with C, you simply add Javas reserved word native as a modifier preceding the function declaration:
public class JNIExample { private int value; native void callCFunction(); int getValue() { return value;
}
static { System.loadLibrary(C_CODE.SRVPGM); }
}
The functions that do not have the native modifier provide Java code implementations between the enclosing curly braces of the Java function. The Java function with the native modifier has no code implementation; youre going to be coding that in C, and then youll compile that C code into an ILE module. That ILE module is then bound into a service program. The service program is then associated with the Java class with a call to Javas loadLibrary function. A good place to insert that loadLibrary call is in your Java classs static pseudo-function. (Java guarantees that the pseudo-function is called the first time that class is instantiated.)
Note that, on an AS/400, the loadLibrary function must be passed a string that contains the dot SRVPGM qualifier. On a Microsoft platform, the loadLibrary qualifier would be dot DLL for a Dynamic Link Library. Your Java JNI class is compiled as usual with Javas JAVAC command. What is different, however, with a Java class that uses JNI is that you then run Javas JAVAH command, specifying your Java class as a parameter. The JAVAH utility then generates a C header file in a language native to your operating system. That file contains the C prototypes for the Java functions that you said you would use. The C function prototypes contain not only any parameters that correspond to the Java class functions, but also a C pointer (JNIEnv*) to the JNI environment and a special C structure called jobject. Through that pointer and the jobject structure, your C function can access the fields of the Java class as well as the other functions of that Java class.
In effect, the C function is just like its sister Java functions in that it has access to all of the fields and functions of its class. Realize, however, that the use of the JNIEnv pointer and the jobject structure does get fairly complex.
If you would like to know more about JNI, I suggest reading Essential JNI: Java Native Interface by Rob Gordon.
Soon, IBM will fully support the use of RPG modules with JNI, but until then, C is an excellent alternative.
Don Denoncourt
C Data Type ILE RPG Comments
Data Type
int 10I 0 A signed 4-byte binary long [int] integer with 32-bit capacity
short [int] 5I 0 A signed 2-byte binary integer with 16-bit capacity
unsigned [int] 10U 0 An unsigned 4-byte binary unsigned long[int] integer with 32-bit capacity
unsigned short [int] 5U 0 An unsigned 2-byte binary integer with 16-bit capacity
char A (or blank) A single-byte character in C unless followed by an *, as in char *, in which case it is a pointer to a null-terminated character string
void * * A pointer to an unidentified data type
data type * * A pointer cast to point to a particular data type, unless void is used (In RPG, a pointer can point to any data type; no compiler checking prevents runtime errors.)
float 4F (no A float data type can be a equivalent float or a double; double and prior to long double are the same for V3R7) ILE RPG
double 8F (no equivalent prior to V3R7)
long [double] 8F (no equivalent prior to V3R7)
Figure 1: C-to-ILE RPG data type mapping
double atof( const char * );
int atoi( const char * );
Figure 2: C function prototypes for atof() and atoi()
Return Value Function Name Parameters or Arguments
Passed
double atof ( const char * )
int atoi ( const char * )
Figure 3: C function parts table for atof() and atoi()
*===================================================================
* Place in a source file called SYSINC with the member name of ATOFH
* and include with /Copy statement:
* Atof - Character String to Float Conversion
*
* Usage for Atof():
* EVAL StringAmt = Atof(%ADDR(StringAmtIn))
*===================================================================
** Compiler Include Directives
*/If Not Defined( AtofHCopied )
/Define AtofHCopied
/Else
/Eof
/EndIf
D Atof PR 8f ExtProc('atof')
D PtrToString * Value
**===================================================================
* Place in a source file called SYSINC with the member name of ATOIH
* and include with /Copy statement:
* Atoi - Character String to Float Conversion
*
* Usage for Atoi():
* EVAL StringNbr = Atoi(%ADDR(StringNbrIn))
*===================================================================
** Compiler Include Directives
*/If Not Defined( AtoiHCopied )
/Define AtoiHCopied
/Else
/Eof
/EndIf
D Atoi PR 10i 0 ExtProc('atoi')
D PtrToString * Value
*-
Figure 4: ILE RPG atof() and atoi() function prototypes
Case 1: Call strtonv parm('-980.50')
Results:
DSPLY String As Amount: -9.804999999999999E+002
*N
DSPLY -980.50 is less than 100.00
Case 2: Call strtonv parm('101')
Results:
DSPLY String As Number: 101
*N
DSPLY 101 is greater than 100
Figure 5: Sample of StrToNV run results
*=================================================================
* To compile:
*
* CRTBNDRPG PGM(XXX/STRTONV) SRCFILE(XXX/QRPGLESRC)
(A) * DFTACTGRP(*NO) ACTGRP(QILE) BNDDIR(QC2LE)
*
* -or-
*
* Take option 14 from PDM source member and prompt with F4
* to change the Binding directory parameter and the Default
* Activation Group parameter.
*
*=================================================================
*****************************************************************
* Function prototype includes.
*****************************************************************
(B) /COPY SYSINC,AtofH
/COPY SYSINC,AtoiH
*****************************************************************
* STANDALONE - Miscellaneous Standalone fields.
*****************************************************************
D FndDecAt S 5i 0 INZ(0)
(C) D TPakFloat S 8f INZ
D StrInFlt S 8f INZ
D TZonInt S 10i 0 INZ
D StrInInt S 10i 0 INZ
D MyPackedAmt S 13p 2 INZ(100.00)
D MyZonedNbr S 13s 0 INZ(100)
D StringToCvt S 20a
D CmpPhrase S 25a
D Message S 52a
*****************************************************************
* CONSTANTS
*****************************************************************
D StringAsAmount C Const('String As Amount:')
D StringAsNumber C Const('String As Number:')
C**********************************************************************
C *ENTRY PLIST
C PARM StringToCvt
(D) C EVAL FndDecAt = %SCAN('.':StringToCvt)
C SELECT
*C WHEN FndDecAt <>*ZERO
*(E) C EVAL TPakFloat = %FLOAT(MyPackedAmt)
(F) C EVAL StrInFlt = Atof(%ADDR(StringToCvt))
C StringAsAmountDSPLY StrInFlt *
C SELECT
(G) C WHEN StrInFlt = TPakFloat
C EVAL CmpPhrase = 'is equal to 100.00'
C WHEN StrInFlt > TPakFloat
C EVAL CmpPhrase = 'is greater than 100.00'
C OTHER
C EVAL CmpPhrase = 'is less than 100.00'
C ENDSL
*C OTHER
*(H) C EVAL TZonInt = %INT(MyZonedNbr)
(I) C EVAL StrInInt = Atoi(%ADDR(StringToCvt))
C StringAsNumberDSPLY StrInInt
*
C SELECT
C WHEN StrInInt = TZonInt
C EVAL CmpPhrase = 'is equal to 100'
C WHEN StrInInt > TZonInt
C EVAL CmpPhrase = 'is greater than 100'
C OTHER
C EVAL CmpPhrase = 'is less than 100'
C ENDSL
*
C ENDSL *
(J) C EVAL Message = %TRIM(StringToCvt) + ' ' +
C CmpPhrase
C Message DSPLY
C EVAL *INLR = *ON
C RETURN
C*********************************************************************
Figure 6: Source for StrToNV
Service Program General Purpose
QC2UTIL1 Math and memory APIs, where you will find the atof() and atoi() functions
QC2UTIL2 Bulk of standard C library of functions, where you will find the strcat() and strcpy() functions
QC2IO Record input/output functions, where you will find the _Rreadk() function
QC2SYS Consists of the system() function (Using this function would be similar to calling QCMDEXC with a command string.)
Figure 7: Bulk of standard C library and math functions
LATEST COMMENTS
MC Press Online