Using JNI to Call RPG from Java

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Wouldn’t it be nice to dump all your RPG and C code and develop all your applications in Java? That suggestion should raise a few eyebrows from discerning developers. The fact is that it’s not a completely “caffeinated” world yet and it’s not likely to be for quite some time. Many programmers still prefer a native 5250 user interface over a Web browser or client/server user interface, and others are chained to an OS/400 version that does not have a Java Virtual Machine (JVM). So, in order to be competitive in these markets (assuming this is your concern), you must provide support for these options for a prospective customer. Yet it could become cost-prohibitive to maintain two separate versions of your software. Additionally, until the recent release (V4R4), Java performance precluded developing serious (competitive-edge) applications. So what is a developer to do?

Ever Heard of Java Native Interface?

A little jewel known as Java Native Interface (JNI) enables you to reuse procedural code written in languages such as RPG IV or C as native methods in Java classes. That is to say, you can use RPG subprocedures as plug-ins for Java functions. The native methods, however, must be developed as ILE service program functions or procedures to be accessible using JNI. A service program is essentially a Dynamic Link Library (DLL) on the AS/400. All JNI services are provided via an AS/400 service program QJVAJNI in the QSYS library, with roughly 216 functions. But why choose JNI over dynamic program calls and a pure Java solution?

The advantage of using JNI is that both the calling program and the called program run in the same process (job) on the AS/400 system while the other methods start a new process (job). This makes JNI calls faster at startup time and less resource-intensive. However, because Java applications run in the Technology Independent Machine Interface (TIMI) and user native methods require a user address space to run, some overhead is required initially to create a user environment that uses 16-byte address pointers instead of the 8-byte pointers used below TIMI. So what does this all mean? It simply means that your reasons for using JNI should be based on more than performance.

Your reason may be the need to preserve a large investment in legacy code. However, a Java toolbox class will enable a dynamic program call, right? And, as stated,


JNI requires the native procedures to be in a service program before they can be addressed as native methods, meaning that you might have to do some reengineering of your existing code to translate it into a service program consisting of a set of subprocedures. How much reengineering you’ll have to do will be based on the modularity of your existing code. So where is the preservation?

Why Use JNI?

At this point, you should be asking yourself, “Why use JNI if I have to rework my code anyway? Why not just do a Java Toolbox program call?” There are two reasons why you would want to use JNI over a pure Java solution and a Java Toolbox program dynamic call. For one, the function required does not exist as a bindable Java option, and the Java Toolbox program call does not yield the required level of performance. (JNI does create some runtime overhead, but it is minimal compared to a Java Toolbox program call.) Another reason is that you must provide “caffeine-free” access to your application to be competitive in serving less developed IS shops (i.e., you must be able to provide a marketable solution where Java is not an accepted standard). In today’s AS/400 applications market, that means being able to provide a user interface access paradigm that can be adapted to both the older green-screen style of access and the newer browser-based access method.

However, you can’t afford to develop core functionality in some ILE native methods and then turn around and develop the same code in Java. Given that you should be addressing the same back-end database using either method, you can ensure better data integrity by being consistent in addressing and handling data with the same data access functions. Keeping features in sync between two releases is difficult enough without having multiple versions (one pure Java and one totally “caffeine-free”) of the same product. JNI allows you to code your back-office code in RPG to be accessed directly from both a Java front-end and an RPG front-end.

What’s the Plan?

You should review the following factors that will determine the amount of effort required to reengineer your existing code for JNI native methods:

• How modular is your code?

• What is the lowest level of OS/400 you are required to support?

• How much data must be shuffled back and forth?

• What is the required response time of the application?

• Must results be real-time, or is batch update allowed?

Answering these questions will determine whether only JNI-level access is needed, client/server data queues or Sockets are required (with the data queue or Socket server task running in background), or batch update is necessary. In truth, your solution will probably lie in the combination of all of these methodologies.

Constructing a Java Shell

An example application I’ve provided takes a customer number passed as an argument and uses back-end native methods (implemented as RPG subprocedures) to retrieve information from a customer master file. Figure 1 shows sample output from the execution of the Java class running in Qshell (QSH) that I will present later. My example Java application uses five native methods, developed using RPG IV, to perform necessary functions that, you


should assume for purposes of this example, could not be accomplished in Java with the same required performance or functionality. Figure 2 shows the Java class CustProcs and the five native methods (Section B). This simple application takes only one argument (custid, the customer number), parses out the integer value of this argument using parseInt (Section C), creates a new instance of CustProcs class (Section D), and finally calls each of the native methods in turn to get the required customer information that each function supplies. Note that, before any native functions can be called, the service program that contains the native methods is loaded with the System.loadLibrary method (Section A). The loadLibrary method is usually invoked in the class initializer because, much like the *INZSR in RPG, the class initializer is automatically called the first time the class is accessed. Methods are bound dynamically on the first call, and subsequent calls are as fast as statically bound calls. Note also that the object cp presented in Figure 2 (Section D) can be reused as many times as required, so you need not create a new object each time you wish to call one of the native methods again. Actually, the object cp is really not required in the example application—because there are no member variables or methods, just the static native methods I have defined—but I have included it here to avoid confusion. Those of you who have been working with Java will have already realized this. However, for purposes of demonstration, you should assume the existence of other member variables and functions requiring an object instantiation. In Figure 2, Section E, the native method calls are made inside the System.out.println function to execute and display the results returned by each of those functions. Finally, System.exit(0) shuts down the CustProcs application.

What Does the Native Code Look Like?

Figure 3 shows a code snippet of the logic necessary to implement the native functionality required by CustProcs. For example, look at the getName function.

I should first point out that you must code the NoMain H-spec keyword in Section F to avoid initialization code from being generated for your service programs. Next, I have coded a compiler directive in Section G that will enable me to use the same source code to generate both a threadsafe version of the service program and a single-threaded version for pure native applications. This is done to enable Java threads of execution to be serialized for each call instance of your native methods. (That is, program variable instances will be marked for a particular thread.) Yet you will want to avoid serialization when other RPG programs call your service program. All that is required to generate the two different binary versions is to pass the javaIncluded text for the DEFINE parameter of the Create RPG Module (CRTRPGMOD) command to generate the threadsafe version and leave it off for the pure native version. Each threadsafe binary has the same name as the single-threaded binary plus an “_r” using the same notation that IBM uses to denote threadsafe C functions. This enables both versions to reside in the same library for distribution. Service program documentation instructs programmers on the proper way to create these binaries.

Note that getName takes an unsigned integer as an input argument (Section I) to represent the customer number in the customer master file. It returns a 30-byte alpha field (Section H) that will contain the name as retrieved from the customer master file. This would be fine if not for a couple of things. For one, JNI cannot call the native methods in this manner and provide access in the other direction (access to Java classes and objects from native methods) without passing additional arguments not required by the native functions. Also, Java applications use a data encoding method (called Universal Translation Format-8 [UTF-8]) that is closer to ASCII than it is to EBCDIC. So, for a native method to understand text data passed to it, a translation from ASCII to EBCDIC must occur. Likewise, any text data (referred to as a String object) returned to the Java application must be encoded using the UTF-8 or Unicode data encoding format. So how do you handle these requirements for Java applications but not impose any superfluous requirements on your pure native applications?


Wrap It Up

Recall the reason(s) why you are using JNI. The idea is to preserve native functionality with as little modification for the JNI interface as possible. To do this, you must create a shell or JNI wrapper for the native functions. One reason for doing this is that the JVM passes two arguments that your native methods will not require when running in pure native mode: 1) a pointer to the JNI function table (which contains an array of pointers to JNI functions) and 2) a reference to a class (if the method is called from a class-static method) or a reference to an object (if the method is called from an instance of a class). After that, any arguments the native method requires are specified. Another reason for the wrapper is to provide the necessary services of data extraction or encapsulation (as in the case of a String object) and translation for textual data passed to (ASCII to EBCDIC) and returned from (EBCDIC to ASCII) native methods. Because running in pure native mode does not require data extraction (encapsulation) or translation, the wrapper serves to encapsulate only the functionality that is required for the Java application. Figure 4 shows the wrapper (CustProc_J service program) required for the getName function.

Note in Figure 4 that the name of the wrapper function for the getName function (Section J) is Java_PentaSafeSysPkg_CustProcs_getName. This four-part name (the parts are separated by a single underscore) is required by JNI and consists of Java designated as the language library, the name of the package (in my example, PentaSafeSysPkg, as shown in the first statement in Figure 2) containing the referenced native method, the name of the class (CustProcs) declaring the native methods, and, finally, the name of the native method to be called (getName).

The first argument passed (Figure 4, Section J) to the getName function is JNIEnv, followed by classParm typecast as a jobject. After these two required parameters, any native method arguments are specified. In the case of getName, all that is required is the custid typecast as a jint. For a complete list of Java reference types used in JNI, refer to the copy member JniMD_h included in the downloadable material for this article. (You can download the specified code at www.midrangecomputing.com/mc.)

What about the return value for getName? In the procedure prototype (Section J) and procedure interface (Section K) lines, I defined the return value as a jstring type. This will actually be a “reference” to the String object created with a call to the NewString JNI function. Now take a look at the procedural section. The first thing it does is assign the pointer to the JNI function table that was passed from the JVM to the basing pointer JNIEnv@ (Section M), which is associated with the Java_JNI_Env data structure in the jni_h copy member shown in the snippet in Figure 5. (The full source code, along with the SQL necessary to create and load the customer file, is downloadable from the MC Web site at www.midrangecomputing.com/mc.) You’ll need at least one pointer out of the array of pointers that is passed to create a new String object that will contain the name of the customer retrieved from getName. The first pointer in this array points to an array of pointers that point to each of the 216 functions in the QJVAJNI service program.

Before you can create the String object, you must first call the wrappered native function getName (Figure 4, Section N) and convert the returned text from EBCDIC to Unicode as required by the JNI function NewString (Section O). To accomplish this conversion, simply use the RPG IV built-in function %UCS2 that was added in V4R4. (If you are not at V4R4, you can convert the text to a straight ASCII, null-terminated string and use NewStringUTF instead; however, this does not accommodate all national languages as Unicode does.) The Unicode data is placed in a UCS2-compliant field named dNameOut (Section L) defined as a c data type. Finally, after creating the String object, you return a reference to this new object to the CustProcs Java application as shown in Section
P.


You’ll Want to Read This

There are many other JNI functions besides the ones presented in this article. For example, the GetStringChars and GetStringUTFChar JNI methods enable your native methods to extract a Unicode and UTF-8 character set string, respectively, from a String object passed to them. Integers and floats are primitives and can be passed as such to native methods. (All that you need to know is whether they are short or long.) Zoned and packed numeric fields must be converted, using a related helper class (in the AS/400 Java Toolbox), to integers and floats of the same magnitude, since zoned and packed data types are not supported by Java.

Prior to my experience with JNI, I had been warned often by many that JNI was too difficult to be implemented by mortal man. Yet I found it extremely easy. Programming by nature is a detail-oriented process, and JNI is just another tool that can be exploited to accomplish the magic you are always expected to conjure for your users. In that endeavor, you will find on the AS/400 Redbooks Web site (www.redbooks.ibm.com) a Redbook entitled Building AS/400 Applications with Java. This book was invaluable in my quest to utilize JNI to call RPG IV native methods on the AS/400. It also gives some tips on establishing your environment for compiling and running Java applications. (See my sidebar “I Was Just Following Orders” at www.midrangecomputing.com/mc.) I strongly recommend you download the PDF version of the Redbook and additional related materials and upload the save file containing a complete functioning application implementing JNI. I challenge you to take the techniques from this article and IBM’s Redbook and further explore the functionality provided by JNI on the AS/400.

References and Related Materials

• Building AS/400 Applications with Java, Redbook (SG24-2163-02)

• Essential JNI: Java Native Interface. Rob Gordon. Colorado Springs, Colorado: Prentice- Hall, 1998

• IBM AS/400 Developer Kit for Java Web site: www.as400.ibm.com/developer/java/devkit/rzaha.htm

• Java 2 SDK, Standard Edition, Documentation, Web site: java.sun.com/products/jdk/1.2/download-docs.html

> java PentaSafeSysPkg.CustProcs 133568

Customer Number: 133568

Name: Smith Mfg.

City/State: Portland, OR

Credit Limit: $1500

Discount: 5.0%

Account Status: Active

$

Figure 1: Java applications can benefit from legacy “native” application code.


package PentaSafeSysPkg;

import java.io.*;
import java.lang.*;

class CustProcs {

static
{
try {
// Load service program for dynamic binding

System.loadLibrary("CUSTPROC_J");
}

catch(Error e) { System.out.println("CUSTPROC_J not loaded!");
}

}

// Native methods contained in CustProcs Service program

static native String getName(int custid);
static native String getAddress(int custid);
static native int getCreditLimit(int custid);
static native float getDiscount(int custid);
static native String getStatus(int custid);

public static void main(String[] parameters)
{
// Parse out custid - integer
int custid = Integer.parseInt(parameters[0], 10);
// Create a new CustProcs instance

CustProcs cp = new CustProcs();
// Call each native function to get customer information

System.out.println("Name: " + cp.getName(custid));
System.out.println("City/State: " +
cp.getAddress(custid));
System.out.println("Credit Limit: $" +
cp.getCreditLimit(custid));
System.out.println("Discount: " +
cp.getDiscount(custid) + "%");
System.out.println("Account Status: " +
cp.getStatus(custid));

System.exit(0);
}

}


A

B

C

D

E

Figure 2: You can use RPG subprocedures to provide code implementations for Java methods.

H NoMain

** Compiler Include Directives
*/If Defined( javaIncluded )
H Thread(*Serialize)

/EndIf

FCustomer if e k disk Rename(Customer:CustRec)
F UsrOpn :

:

*P getName b Export

*D pi 30a
D custno 10u 0 Value

** Local function variables and return value definition.
*D custid s 7p 0

*C Open Customer
C Eval custid = custno
C Custid Chain Customer
C Close Customer
C If ( Not %Error And %Found )
C Return Name
C Else
C Return '*Error - No Name found'
C EndIf

*P getName e

*-


F

G I

H

Figure 3: Modularized back-office RPG code can be used by both Java and RPG front-ends.

H NoMain Thread(*Serialize) :

:

** Prototype for getName_().
*D getName_ pr ExtProc(
D 'Java_PentaSafeSysPkg_CustProcs+
D _getName')
D Like(jstring)
D JNIEnv * Value
D classParm Value Like(jobject)

*

D custid Value Like(jint)

*P getName_ b Export

*D pi Like(jstring)
D jniEnv * Value
D classParm Value Like(jobject)
D custid Value Like(jint)

** Local function variables and return value definition.
*D dName s 30a
D dNameOut s 31c
D nameObj s Like(jstring)

*C EVAL JNIEnv@ = jniEnv
C EVAL MyClass = classParm

C Eval dNameOut = %Ucs2(getName(custid))
C + %Ucs2(x'00')
C Eval nameObj = NewString(JNIEnv@
C : %Addr(dNameOut)
C : %Size(dName))

C Return nameObj

*P getName_ e*---------------------------------------------------------------

J

K

L

P

M

N

O

Figure 4: Each RPG subprocedure requires a wrapper to convert EBCDIC to Java data types.

D Java_JNI_Env DS BASED(JNIEnv@)

D Java_JNI_Functions...

D *

D Java_JNI_Env_filler_1... :

:

D Java_Function_Pointers...

D DS BASED(Java_JNI_Functions) :

:

D NewString@...

D * PROCPTR

:

:

D*D* jstring (*NewString)

D* (JNIEnv *env, const jchar *unicode, jsize len);

D*D NewString PR LIKE(jstring) EXTPROC(NewString@)

D env LIKE(JNIEnv@) VALUE

D unicode * VALUE

D len LIKE(jsize) VALUE


Figure 5: To use the JNI environment, you’ll need access to a copy book of data structures.


Wouldn’t it be nice to dump all your RPG and C code and develop all your applications in Java? That suggestion should raise a few eyebrows from discerning developers. The fact is that it’s not a completely “caffeinated” world yet and it’s not likely to be for quite some time. Many programmers still prefer a native 5250 user interface over a Web browser or client/server user interface, and others are chained to an OS/400 version that does not have a Java Virtual Machine (JVM). So, in order to be competitive in these markets (assuming this is your concern), you must provide support for these options for a prospective customer. Yet it could become cost-prohibitive to maintain two separate versions of your software. Additionally, until the recent release (V4R4), Java performance precluded developing serious (competitive-edge) applications. So what is a developer to do?

Ever Heard of Java Native Interface?

A little jewel known as Java Native Interface (JNI) enables you to reuse procedural code written in languages such as RPG IV or C as native methods in Java classes. That is to say, you can use RPG subprocedures as plug-ins for Java functions. The native methods, however, must be developed as ILE service program functions or procedures to be accessible using JNI. A service program is essentially a Dynamic Link Library (DLL) on the AS/400. All JNI services are provided via an AS/400 service program QJVAJNI in the QSYS library, with roughly 216 functions. But why choose JNI over dynamic program calls and a pure Java solution?

The advantage of using JNI is that both the calling program and the called program run in the same process (job) on the AS/400 system while the other methods start a new process (job). This makes JNI calls faster at startup time and less resource-intensive. However, because Java applications run in the Technology Independent Machine Interface (TIMI) and user native methods require a user address space to run, some overhead is required initially to create a user environment that uses 16-byte address pointers instead of the 8-byte pointers used below TIMI. So what does this all mean? It simply means that your reasons for using JNI should be based on more than performance.

Your reason may be the need to preserve a large investment in legacy code. However, a Java toolbox class will enable a dynamic program call, right? And, as stated,


JNI requires the native procedures to be in a service program before they can be addressed as native methods, meaning that you might have to do some reengineering of your existing code to translate it into a service program consisting of a set of subprocedures. How much reengineering you’ll have to do will be based on the modularity of your existing code. So where is the preservation?

Why Use JNI?

At this point, you should be asking yourself, “Why use JNI if I have to rework my code anyway? Why not just do a Java Toolbox program call?” There are two reasons why you would want to use JNI over a pure Java solution and a Java Toolbox program dynamic call. For one, the function required does not exist as a bindable Java option, and the Java Toolbox program call does not yield the required level of performance. (JNI does create some runtime overhead, but it is minimal compared to a Java Toolbox program call.) Another reason is that you must provide “caffeine-free” access to your application to be competitive in serving less developed IS shops (i.e., you must be able to provide a marketable solution where Java is not an accepted standard). In today’s AS/400 applications market, that means being able to provide a user interface access paradigm that can be adapted to both the older green-screen style of access and the newer browser-based access method.

However, you can’t afford to develop core functionality in some ILE native methods and then turn around and develop the same code in Java. Given that you should be addressing the same back-end database using either method, you can ensure better data integrity by being consistent in addressing and handling data with the same data access functions. Keeping features in sync between two releases is difficult enough without having multiple versions (one pure Java and one totally “caffeine-free”) of the same product. JNI allows you to code your back-office code in RPG to be accessed directly from both a Java front-end and an RPG front-end.

What’s the Plan?

You should review the following factors that will determine the amount of effort required to reengineer your existing code for JNI native methods:

• How modular is your code?

• What is the lowest level of OS/400 you are required to support?

• How much data must be shuffled back and forth?

• What is the required response time of the application?

• Must results be real-time, or is batch update allowed?

Answering these questions will determine whether only JNI-level access is needed, client/server data queues or Sockets are required (with the data queue or Socket server task running in background), or batch update is necessary. In truth, your solution will probably lie in the combination of all of these methodologies.

Constructing a Java Shell

An example application I’ve provided takes a customer number passed as an argument and uses back-end native methods (implemented as RPG subprocedures) to retrieve information from a customer master file. Figure 1 shows sample output from the execution of the Java class running in Qshell (QSH) that I will present later. My example Java application uses five native methods, developed using RPG IV, to perform necessary functions that, you


should assume for purposes of this example, could not be accomplished in Java with the same required performance or functionality. Figure 2 shows the Java class CustProcs and the five native methods (Section B). This simple application takes only one argument (custid, the customer number), parses out the integer value of this argument using parseInt (Section C), creates a new instance of CustProcs class (Section D), and finally calls each of the native methods in turn to get the required customer information that each function supplies. Note that, before any native functions can be called, the service program that contains the native methods is loaded with the System.loadLibrary method (Section A). The loadLibrary method is usually invoked in the class initializer because, much like the *INZSR in RPG, the class initializer is automatically called the first time the class is accessed. Methods are bound dynamically on the first call, and subsequent calls are as fast as statically bound calls. Note also that the object cp presented in Figure 2 (Section D) can be reused as many times as required, so you need not create a new object each time you wish to call one of the native methods again. Actually, the object cp is really not required in the example application—because there are no member variables or methods, just the static native methods I have defined—but I have included it here to avoid confusion. Those of you who have been working with Java will have already realized this. However, for purposes of demonstration, you should assume the existence of other member variables and functions requiring an object instantiation. In Figure 2, Section E, the native method calls are made inside the System.out.println function to execute and display the results returned by each of those functions. Finally, System.exit(0) shuts down the CustProcs application.

What Does the Native Code Look Like?

Figure 3 shows a code snippet of the logic necessary to implement the native functionality required by CustProcs. For example, look at the getName function.

I should first point out that you must code the NoMain H-spec keyword in Section F to avoid initialization code from being generated for your service programs. Next, I have coded a compiler directive in Section G that will enable me to use the same source code to generate both a threadsafe version of the service program and a single-threaded version for pure native applications. This is done to enable Java threads of execution to be serialized for each call instance of your native methods. (That is, program variable instances will be marked for a particular thread.) Yet you will want to avoid serialization when other RPG programs call your service program. All that is required to generate the two different binary versions is to pass the javaIncluded text for the DEFINE parameter of the Create RPG Module (CRTRPGMOD) command to generate the threadsafe version and leave it off for the pure native version. Each threadsafe binary has the same name as the single-threaded binary plus an “_r” using the same notation that IBM uses to denote threadsafe C functions. This enables both versions to reside in the same library for distribution. Service program documentation instructs programmers on the proper way to create these binaries.

Note that getName takes an unsigned integer as an input argument (Section I) to represent the customer number in the customer master file. It returns a 30-byte alpha field (Section H) that will contain the name as retrieved from the customer master file. This would be fine if not for a couple of things. For one, JNI cannot call the native methods in this manner and provide access in the other direction (access to Java classes and objects from native methods) without passing additional arguments not required by the native functions. Also, Java applications use a data encoding method (called Universal Translation Format-8 [UTF-8]) that is closer to ASCII than it is to EBCDIC. So, for a native method to understand text data passed to it, a translation from ASCII to EBCDIC must occur. Likewise, any text data (referred to as a String object) returned to the Java application must be encoded using the UTF-8 or Unicode data encoding format. So how do you handle these requirements for Java applications but not impose any superfluous requirements on your pure native applications?


Wrap It Up

Recall the reason(s) why you are using JNI. The idea is to preserve native functionality with as little modification for the JNI interface as possible. To do this, you must create a shell or JNI wrapper for the native functions. One reason for doing this is that the JVM passes two arguments that your native methods will not require when running in pure native mode: 1) a pointer to the JNI function table (which contains an array of pointers to JNI functions) and 2) a reference to a class (if the method is called from a class-static method) or a reference to an object (if the method is called from an instance of a class). After that, any arguments the native method requires are specified. Another reason for the wrapper is to provide the necessary services of data extraction or encapsulation (as in the case of a String object) and translation for textual data passed to (ASCII to EBCDIC) and returned from (EBCDIC to ASCII) native methods. Because running in pure native mode does not require data extraction (encapsulation) or translation, the wrapper serves to encapsulate only the functionality that is required for the Java application. Figure 4 shows the wrapper (CustProc_J service program) required for the getName function.

Note in Figure 4 that the name of the wrapper function for the getName function (Section J) is Java_PentaSafeSysPkg_CustProcs_getName. This four-part name (the parts are separated by a single underscore) is required by JNI and consists of Java designated as the language library, the name of the package (in my example, PentaSafeSysPkg, as shown in the first statement in Figure 2) containing the referenced native method, the name of the class (CustProcs) declaring the native methods, and, finally, the name of the native method to be called (getName).

The first argument passed (Figure 4, Section J) to the getName function is JNIEnv, followed by classParm typecast as a jobject. After these two required parameters, any native method arguments are specified. In the case of getName, all that is required is the custid typecast as a jint. For a complete list of Java reference types used in JNI, refer to the copy member JniMD_h included in the downloadable material for this article. (You can download the specified code at www.midrangecomputing.com/mc.)

What about the return value for getName? In the procedure prototype (Section J) and procedure interface (Section K) lines, I defined the return value as a jstring type. This will actually be a “reference” to the String object created with a call to the NewString JNI function. Now take a look at the procedural section. The first thing it does is assign the pointer to the JNI function table that was passed from the JVM to the basing pointer JNIEnv@ (Section M), which is associated with the Java_JNI_Env data structure in the jni_h copy member shown in the snippet in Figure 5. (The full source code, along with the SQL necessary to create and load the customer file, is downloadable from the MC Web site at www.midrangecomputing.com/mc.) You’ll need at least one pointer out of the array of pointers that is passed to create a new String object that will contain the name of the customer retrieved from getName. The first pointer in this array points to an array of pointers that point to each of the 216 functions in the QJVAJNI service program.

Before you can create the String object, you must first call the wrappered native function getName (Figure 4, Section N) and convert the returned text from EBCDIC to Unicode as required by the JNI function NewString (Section O). To accomplish this conversion, simply use the RPG IV built-in function %UCS2 that was added in V4R4. (If you are not at V4R4, you can convert the text to a straight ASCII, null-terminated string and use NewStringUTF instead; however, this does not accommodate all national languages as Unicode does.) The Unicode data is placed in a UCS2-compliant field named dNameOut (Section L) defined as a c data type. Finally, after creating the String object, you return a reference to this new object to the CustProcs Java application as shown in Section
P.


You’ll Want to Read This

There are many other JNI functions besides the ones presented in this article. For example, the GetStringChars and GetStringUTFChar JNI methods enable your native methods to extract a Unicode and UTF-8 character set string, respectively, from a String object passed to them. Integers and floats are primitives and can be passed as such to native methods. (All that you need to know is whether they are short or long.) Zoned and packed numeric fields must be converted, using a related helper class (in the AS/400 Java Toolbox), to integers and floats of the same magnitude, since zoned and packed data types are not supported by Java.

Prior to my experience with JNI, I had been warned often by many that JNI was too difficult to be implemented by mortal man. Yet I found it extremely easy. Programming by nature is a detail-oriented process, and JNI is just another tool that can be exploited to accomplish the magic you are always expected to conjure for your users. In that endeavor, you will find on the AS/400 Redbooks Web site (www.redbooks.ibm.com) a Redbook entitled Building AS/400 Applications with Java. This book was invaluable in my quest to utilize JNI to call RPG IV native methods on the AS/400. It also gives some tips on establishing your environment for compiling and running Java applications. (See my sidebar “I Was Just Following Orders” at www.midrangecomputing.com/mc.) I strongly recommend you download the PDF version of the Redbook and additional related materials and upload the save file containing a complete functioning application implementing JNI. I challenge you to take the techniques from this article and IBM’s Redbook and further explore the functionality provided by JNI on the AS/400.

References and Related Materials

• Building AS/400 Applications with Java, Redbook (SG24-2163-02)

• Essential JNI: Java Native Interface. Rob Gordon. Colorado Springs, Colorado: Prentice- Hall, 1998

• IBM AS/400 Developer Kit for Java Web site: www.as400.ibm.com/developer/java/devkit/rzaha.htm

• Java 2 SDK, Standard Edition, Documentation, Web site: java.sun.com/products/jdk/1.2/download-docs.html

> java PentaSafeSysPkg.CustProcs 133568

Customer Number: 133568

Name: Smith Mfg.

City/State: Portland, OR

Credit Limit: $1500

Discount: 5.0%

Account Status: Active

$

Figure 1: Java applications can benefit from legacy “native” application code.


package PentaSafeSysPkg;

import java.io.*;
import java.lang.*;

class CustProcs {

static
{
try {
// Load service program for dynamic binding

System.loadLibrary("CUSTPROC_J");
}

catch(Error e) { System.out.println("CUSTPROC_J not loaded!");
}

}

// Native methods contained in CustProcs Service program

static native String getName(int custid);
static native String getAddress(int custid);
static native int getCreditLimit(int custid);
static native float getDiscount(int custid);
static native String getStatus(int custid);

public static void main(String[] parameters)
{
// Parse out custid - integer
int custid = Integer.parseInt(parameters[0], 10);
// Create a new CustProcs instance

CustProcs cp = new CustProcs();
// Call each native function to get customer information

System.out.println("Name: " + cp.getName(custid));
System.out.println("City/State: " +
cp.getAddress(custid));
System.out.println("Credit Limit: $" +
cp.getCreditLimit(custid));
System.out.println("Discount: " +
cp.getDiscount(custid) + "%");
System.out.println("Account Status: " +
cp.getStatus(custid));

System.exit(0);
}

}


A

B

C

D

E

Figure 2: You can use RPG subprocedures to provide code implementations for Java methods.

H NoMain

** Compiler Include Directives
*/If Defined( javaIncluded )
H Thread(*Serialize)

/EndIf

FCustomer if e k disk Rename(Customer:CustRec)
F UsrOpn :

:

*P getName b Export

*D pi 30a
D custno 10u 0 Value

** Local function variables and return value definition.
*D custid s 7p 0

*C Open Customer
C Eval custid = custno
C Custid Chain Customer
C Close Customer
C If ( Not %Error And %Found )
C Return Name
C Else
C Return '*Error - No Name found'
C EndIf

*P getName e

*-


F

G I

H

Figure 3: Modularized back-office RPG code can be used by both Java and RPG front-ends.

H NoMain Thread(*Serialize) :

:

** Prototype for getName_().
*D getName_ pr ExtProc(
D 'Java_PentaSafeSysPkg_CustProcs+
D _getName')
D Like(jstring)
D JNIEnv * Value
D classParm Value Like(jobject)

*

D custid Value Like(jint)

*P getName_ b Export

*D pi Like(jstring)
D jniEnv * Value
D classParm Value Like(jobject)
D custid Value Like(jint)

** Local function variables and return value definition.
*D dName s 30a
D dNameOut s 31c
D nameObj s Like(jstring)

*C EVAL JNIEnv@ = jniEnv
C EVAL MyClass = classParm

C Eval dNameOut = %Ucs2(getName(custid))
C + %Ucs2(x'00')
C Eval nameObj = NewString(JNIEnv@
C : %Addr(dNameOut)
C : %Size(dName))

C Return nameObj

*P getName_ e*---------------------------------------------------------------

J

K

L

P

M

N

O

Figure 4: Each RPG subprocedure requires a wrapper to convert EBCDIC to Java data types.

D Java_JNI_Env DS BASED(JNIEnv@)

D Java_JNI_Functions...

D *

D Java_JNI_Env_filler_1... :

:

D Java_Function_Pointers...

D DS BASED(Java_JNI_Functions) :

:

D NewString@...

D * PROCPTR

:

:

D*D* jstring (*NewString)

D* (JNIEnv *env, const jchar *unicode, jsize len);

D*D NewString PR LIKE(jstring) EXTPROC(NewString@)

D env LIKE(JNIEnv@) VALUE

D unicode * VALUE

D len LIKE(jsize) VALUE


Figure 5: To use the JNI environment, you’ll need access to a copy book of data structures.


BLOG COMMENTS POWERED BY DISQUS