|
Calling RPG Native Methods from Java
by Kevin Vandever
[The code for this article is available for download.]
The last time I heard the term native used in connection with the AS/400 was way back when I was
converting programs written in RPG II, running in the System/36 environment, to native RPG/400
programs. Well, native strikes again! This time in the form of RPG native methods, which are RPG-
based subprocedures compiled into ILE service programs, yet defined and called from Java as native
methods. "Hogwash!" you say? "Can't be done!" Well it can. And it has. And you can thank the Java Native
Interface (JNI) for the pleasure.
So What Gives?
In my article "Prototyping and Calling Java Methods from RPG," I explained half the story; that is,
accessing Java methods from RPG. I showed you the new O data type to signify a Java object, and
illustrated how to use objects as input parameters for Java method calls, as well as return values from Java
method calls. I also showed you the new parameters on the EXTPROC keyword that are used when
prototyping Java methods. I then combined the techniques to convert RPG data types to Java, and back
again, and to call Java methods from the comfort of your own RPG program. Now it's time for the second
half of the story: Calling RPG native methods from Java. And everything you learned from the first article
can be applied here.
An Old Friend
I have provided a simple source member called CUSTOMER (customer.txt) and a separate source
member containing the prototype (customerpr.txt), used to validate a
customer ID. First, let's take a look at the prototype (customerpr.txt). You may recognize the syntax from
my previous article. I am prototyping a Java method called checkCust, which returns an indicator value,
data type N, stating whether or not the customer ID is valid. I am passing the subprocedure an 8-byte alpha
customer ID. The Java method prototype is accomplished by using the new enhancements to the keyword
ExtProc. The three new parameters are *JAVA, to signify that you are prototyping a Java method; the fully
qualified class that contains the method, ValCust; and the name of the method, checkCust. The name of the
prototype does not have to match the name of the Java method, but if the Java method name is descriptive
enough, I will name my prototype the same.
The RPG Native Method
Now let's take a look at the CUSTOMER source code (customer.txt). The checkCust subprocedure simply
accepts the 8-byte customer ID, chains to the CstMst file, and returns the indicator, the %Found built-in
function, stating whether or not the customer was found. As a side note, I have coded the C-specs using the
new free-form RPG. You don't have to use this technique, but it is available with OS/400 V5R1 and is very
powerful. (For more information on free-from C-specs, check out my article "Let Your Hair Down with Free-
Formed C-Specs.")
There are three things required to make this a native method. First, it must be a subprocedure that is
compiled into a service program. Second, you must use the keyword EXPORT on the first P-spec that
begins the subprocedure definition. This will allow the subprocedure to be used outside of the
CUSTOMER module. Last, you must make this module thread-safe. You do this by inserting an H-spec
and specifying the Thread(*SERIALIZE) keyword/parameter combination. Java runs in a multi-threaded
environment, within the same job, as opposed to multiple jobs, as most of us are used to. These threads can
cause serious trouble if they call the same module simultaneously within the same job; therefore, you must
somehow protect your module's static storage in some manner, and even though RPG is not yet a multi-
thread-capable language, you can still make it thread-safe by serializing the calls. Basically, this means that
if two Java threads call the same RPG module within the same job, Thread(*SERIALIZE) states that the
first call to a module must completely finish before the second one is called. If the module were not made
thread-safe in this manner, bad things would happen to static storage. So this module contains all the
requirements necessary for a native method. Now it's time to compile. Remember to create a physical file
called CSTMST that contains an 8-byte customer ID so that these examples will compile cleanly.
First, you create an RPG module, using option 15 from PDM or the following command:
CRTRPGMOD MODULE(Your_Lib/CUSTOMER) SRCFILE(Your_Source_File/QRPGLESRC)
Then you must create a service program from the module you just created. You can accomplish this with
the following command:
CRTSRVPGM SRVPGM(Your_Lib/CUSTOMER) EXPORT(*ALL)
The interesting thing is that, now that you've done all this RPG coding, the native method will actually be
defined and called from a Java class, not from another ILE object. Let's see how it's done.
The Java App
There are three areas of the Java source code (valcust.java) that I want to cover: the
defining of the native method, the JNI load of the ILE service program, and the actual method call to the
native method.
Let's start with the native method definition. The following line of code in the Java class is used to define
the native method as part of the ValCust class:
public native boolean checkCust (byte custID[]);
Let's break the line down a little, shall we? The method name is checkCust, and it accepts a byte array as its
parameter. Think of a byte array as an alpha field in RPG-speak; in fact, if you look back at the prototype
for this native method, you'll see it is defined to accept an 8-byte alpha field. Directly to the left of the
method name is the data type for the return value. In my example, I want the checkCust method to return a
Boolean value, to tell me whether or not the customer ID I passed is valid. If you have no return value in
your native method, you would use void where I used boolean. The other two entries,
public and native, further define the method. Public allows the method to be called by other classes,
and native tells Java that this is a native method, meaning that it was created in a language other
than Java. Now that we've defined the method and made it a part of the ValCust class, let's look at the next
JNI-related logic:
static
{
System.loadLibrary("CUSTOMER");
}
The loadLibrary method loads the ILE service program, CUSTOMER, into Java. The service program must
exist within your library list for the above command to work. If it can't find the service program, or if you
already know that it won't exist in the library list, you can qualify it as follows:
static
{
System.loadLibrary("QSYS.lib/YOUR_LIB.lib/CUSTOMER.srvpgm");
}
Now you're pretty well set. I've shown you how to define the native method to be part of the ValCust class
and also how to load the service program, from which the native method will draw its logic. All that's left is
to call the native method. To do that, you employ the following code:
boolean found;
String custID = new String(argv[0]);
ValCust vc = new ValCust();
found = vc.checkCust (custID.getBytes());
The first line defines a Boolean variable called found. This will be used to house the return value. The
second line defines a string called custID, which is initialized with the first parameter of the byte array data
passed into this Java class when it was called from the command line. The next line creates an object
instance of our ValCust class, called vc. This is done because the ValCust class is not a static class,
meaning that it cannot be directly accessed; only objects created from the class can be accessed. It's kind of
like a blueprint of a house. You can't live in the blueprint. It's only when you build a house (object) from
the blueprint (class) that you can then move on in. Finally, the native method, checkCust, is a called,
passing byte data converted from the custID string, using the getBytes method, and returning a Boolean
indicator in the found variable.
Putting It All Together
Now you have a Java application that you can call, passing an 8-byte customer ID, and it will print a
message stating whether or not that customer ID is valid. The logic for the customer lookup exists in the
ILE service program, but that service program is loaded into Java, and its associated subprocedure is
defined as a native method within the ValCust class. Then it can be called by Java using a standard Java
method call. The following figure illustrates how to call the Java class and what happens when I pass valid
and invalid customer IDs:
I have glossed over most of the Java code, but you should take a look at it and use it on your own. You can
also check out my article "Java Concepts for iSeries Programmers," to get a better understanding of what the Java
is doing.
Use with Caution
I have given you a high-level look at JNI and how to use it to call RPG from Java using native method
calls. However, now that I have given you this awesome tool, I will also tell you that there are pros and
cons to using it. One pro is simply that JNI is the standard way to call native methods and doesn't require
any extra classes or JAR files outside of the standard Java Development Kit. Among the cons is
performance hits, because of the JNI resolution that has to be done each time the native method is called.
There is also a potential performance hit with the synchronization of module calls on the ILE side.
Remember, RPG is thread-safe, but it is not thread-enabled. OS/400 will handle the synchronization
of multiple threads, provided you coded Thread(*SERIALIZE) in your service program. This means that
simultaneous calls to the same module, within a single job, cause an increase in synchronization, which will
subsequently slow down each thread's completion time. Remember when I stated that JNI still might not be
the best way to access RPG from Java? Well, IBM
recommends using JNI only when there is no alternative. Or, if you have to use it, it is best used when the
service program has to do a considerable amount of work and will not be called all that often. So if you
can't use the iSeries Toolbox
for Java or JDBC to accomplish your task, and rewriting your business logic in Java is out of the
question, JNI is just the ticket.
Kevin Vandever is a lead IT engineer at Boise Cascade Office Products in Itasca, Illinois, and a co-
editor of Midrange Programmer, OS/400 Edition. He can be reached at kvandever@itjungle.com.
|