Using the RUNJVA command to call Java was straightforward but somewhat awkward because a mechanism, like a data queue, had to be used to pass back information from Java. Calling RPG from Java using the Toolbox classes required no effort on the RPG side but considerable coding on the Java side.
New in V5R1
In V5R1, it is now simple for RPG programmers to use the JNI, assuming the RPG programmer is somewhat familiar with Java. In this article, I assume you have basic knowledge of Java.
The JNI allows a Java class to declare that a method is a native method--a procedure written in a non-Java language that is native to the operating system. The most common JNI languages are C and C++, but any language supporting bound procedures can use JNI. The JNI also allows other languages to start the Java Virtual Machine (JVM) and call Java methods directly.
Using the new RPG support, RPG programmers can write native methods and call Java methods almost exactly the same way they write ordinary subprocedures and call other procedures. In fact, you can't detect that a call is to a Java method or that a subprocedure is actually a Java native method. You can only detect this information from the prototype. The daysBetween subprocedure in Figure 1 is an example of an RPG native method.
|
Figure 1: The daysBetween RPG subprocedure can be used as a Java method.
A new object data type has been added to RPG. You define an object by coding the type O and the CLASS(*JAVA : classname) keyword. You can't do very much with an object type. You can assign one object to another, compare one object to another for equality, and pass objects as parameters and return them from procedures and native methods.
A new form of the EXTPROC keyword takes three parameters:
- *JAVA
- The name of the Java class (for example, 'java.lang.String' or 'MyClass')
- The name of the Java method (for example, 'getBytes' or 'myMethod')
If the method is a class constructor, the special word *CONSTRUCTOR is used. If the method is a static method, the STATIC keyword is used for the prototype.
Calling RPG from Java
Figure 2 shows a small Java class. The purpose of this class is to allow Java to use some of the date-time-timestamp functions of RPG.
|
Figure 2: This Java class loads an RPG service program.
The first thing in the class is a static block that uses the loadLibrary method from the System class to locate the service program containing the RPG native methods. When the Java class is run, the service program DTZSRV must be found in the library list. The rest of the class is straightforward, except the two native methods getCurDtz and chkDtzType. These are native methods, indicated by the native keyword. Native methods have no method body. When a native method is invoked by a Java class, the JNI is used to make a call outside of Java to a procedure in the service program. The Java class calls the native method chkDtzType in the constructor, and it calls the native method getCurDtz in main.
Figure 3 shows an RPG module containing the native methods used by the Java class.
|
Figure 3: This RPG module interacts with a Java class.
Notice the THREAD(*SERIALIZE) keyword. This keyword should always be used when RPG is interacting with Java because Java uses threads, so any code called by Java should be threadsafe. (Note that coding THREAD(*SERIALIZE) does not guarantee thread safety, but without it, your RPG code is dangerously thread-unsafe.) This source would be used to create the service program DTZSRV. If you use binder language when creating your service program, use DSPMOD to see exactly what names are exported from the module. The names will not be simply the method names; instead, they will be in the form Java_classname_methodname. These are the names you must code in your binder language. For example, native method getCurDtz in class Dtz will be exported from the module as Java_Dtz_getCurDtz.
This module uses two different Java classes: your own Dtz class and the Java String class. The RPG module defines prototypes both for the native methods and for any other Java methods it needs. Notice there is no special keyword to indicate that a Java method is a native method. When RPG finds a subprocedure definition for a Java-method prototype, it knows that the subprocedure is really a native method.
RPG programmers have several choices when they want to pass or receive character parameters from Java. To make it very simple on the Java side, the Java side can use String objects. This means that the RPG side must call the String.getBytes method to retrieve the string data, and it must call the String constructor to return string data. This is how the return value from getCurDtz method is handled. The Java method can use the returned value directly. Another possibility is to define the character data as alpha or Universal Multiple-Octet Coded Character Set-2 (UCS-2) data in the RPG module and to define it as an array of byte or array of char in the Java class. This is how the parameter for chkDtzBytes is handled. When defining character or UCS-2 data in RPG, it is usually a good idea to use the VARYING keyword or OPTIONS(*VARSIZE), since Java does not have any notion of fixed-length strings. However, there may be cases when you know that a particular string will always be 10 bytes long. In that case, 10A is fine.
Since getCurDtz is not a static method, it is working on behalf of a specific Dtz object. The first thing getCurDtz has to determine is whether the object is representing a date, time, or timestamp. It does this by calling the getDtzType method. The prototype for this method has no parameters, but since it is an instance method, an object instance must be passed as the first parameter. RPG must pass the same instance that it is working on. To get the object instance, RPG uses the new %THIS built-in function. This built-in function is only valid in non-static native methods.
When getCurDtz has calculated the value it wants to return, it must then create a Java String object. It does this by calling the String constructor, called newString in this module.
The chkDtzType method is very straightforward. Notice that the return value is defined as Boolean in the Java class and defined as an indicator in the RPG module. See Figure 4 for a table of type equivalents between RPG and Java. When you are defining prototypes for Java methods, you must be careful to match RPG types correctly to Java types.
Java Type | RPG Type(s) |
Boolean | indicator |
byte | 1A |
3I 0 | |
byte[] | nA |
nA VARYING | |
3I 0 DIM(x) | |
1A DIM(x) | |
D any format | |
T any format | |
Z | |
char | 1C |
char[] | nC |
1C DIM(x) | |
short | 5I 0 |
int | 10I 0 |
long | 20I 0 |
float | 4F |
double | 8F |
Object of any class | O CLASS(*JAVA:'classname') |
Array of any type | type DIM(x) |
Byte data in Java can be either numeric data or character data. When it is character data, the character representation is ASCII. With RPG IV, on the AS/400, the character representation is EBCDIC. When RPG uses the alphanumeric (A) type for the Java byte type, RPG performs translation between ASCII and EBCDIC. When the RPG uses the 1 byte integer type for the Java byte type, RPG does not perform this translation. |
Figure 4: RPG and Java use different names for the same data types.
Calling Java from RPG
Calling a Java method from RPG is no different from making any other prototyped call, with one exception: When you call an instance method (a nonstatic method), you must provide the instance that the method is to use. In RPG, the instance is passed as the first parameter. In Java, you code obj.method(parm1,parm2). In RPG, you code method(obj:parm1:parm2). This extra parameter does not appear in the prototype (PR) or the procedure interface (PI). For example, in Figure 5, the prototype for getDtzType has no parameters, but the call to getDtzType has one parameter.
|
Figure 5: An RPG program that needs more than 30 digits of precision can use Java's BigDecimal class.
Figure 5 shows an example of RPG using Java's BigDecimal class to do a numeric calculation that has a result with more than 30 digits. This calculation would require the use of a floating-point result in RPG, but the result would not be exact. Floating-point variables only have about 17 digits of precision. The BigDecimal class does not have any limit on precision.
Figure 5 uses two classes: String and BigDecimal. The BigDecimal class has a static method that creates a BigDecimal object from a long value and a scale. This method is used to create two BigDecimal objects.
Then the multiply method is used to multiply the values. Notice that the prototype for multiply has only one parameter, while the call has two parameters. Since multiply is not a static method, the first parameter is the object instance for the method to work with.
Finally, the toString method of BigDecimal is used to get the String representation of the result of the multiplication. To get the actual value of this string, the getBytes method of the String class is used. Again, since toString and getBytes are instance methods, an extra parameter must be passed on each call.
Calling Your Own Java Classes from RPG
When the first Java method is called, RPG will start the JVM if it has not already been started. RPG uses the CLASSPATH environment variable when it starts the JVM, so make sure you have set the environment variable using ADDENVAR before the JVM is created if you want to use your own classes:
ADDENVVAR CLASSPATH '/home/bmorris'
Additional Required Coding to Call Java
The new Java support makes it very easy to call Java methods. However, Java is not the same as other ILE languages, and to use the JNI correctly, there is a little additional programming you must do.
For example, when you call Java from an ordinary RPG procedure (not from a native method), any objects you create are not subject to Java's garbage collection until you explicitly free them. Conversely, when you call a native method from Java, any objects that are created while the native method is running are destroyed when the native method returns. If you want the objects to be available to RPG later, you must prevent Java from freeing the objects. If you are calling Java from RPG, RPG will start the JVM for you if it is not already started, but it is up to you to end the JVM.
You can read about this additional coding in the Java chapter of the V5R1 ILE RPG Programmer's Guide. This guide tells you what you have to do and has sample procedures that show how to do it. You will also find it worthwhile to read about JNI programming at http://java.sun.com/docs/books/tutorial/native1.1/index.html.
What RPG Doesn't Support
The RPG support only allows you to call Java methods. The JNI also gives you access to the data members (fields) in a Java class. If you want to do more JNI programming than RPG allows, you can do it yourself by calling all the JNI functions directly, using the /COPY file JNI in file QSYSINC/QRPGLESRC. There is a small example of this in the V5R1 ILE RPG Programmer's Guide.
Barbara Morris joined IBM in 1989 after graduating from the University of Alberta with a degree in computing science. Within IBM, she has always worked on the RPG Compiler team. You can contact Barbara at
LATEST COMMENTS
MC Press Online