Dec 1 ’10
The Java Native Interface on z/OS
Modernization efforts have encouraged mainframe developers to use functions that exist on non-z/OS platforms. Java, one of the more modern programming languages, is in wide use on several computing platforms. Its write-once, run-anywhere ability makes it suitable to run on z/OS. Application developers can use the Java Native Interface (JNI) to execute Java methods from other languages, or invoke non-Java code from Java. Using the JNI is complicated and using it on z/OS adds another level of complexity. Part of this comes from the interesting split in z/OS, where we have the traditional MVS environment and the newer UNIX System Services (USS) environment. Also, there are many program creation and execution options that have an impact on using the JNI on z/OS.
Many Websites and manuals describe using the JNI, but none provide a complete example of its use on z/OS. The code needs to be explained as well as the Job Control Language (JCL) to compile and execute the modules and the compile and run-time options.
This article is the result of research performed prior to rewriting a significant portion of an existing product and additional work performed while involved in a modernization project. The initial work was Java-to-C-to-Assembler. The example here goes in the other direction. The main takeaway for readers is an approach to combining existing code to new development. This can be valuable in situations where you have applications running in more than one environment, but use platform-specific development methods. Any new code can be written in Java and existing programs can use the JNI to invoke the new functionality.
A compressed file is available at http://home.comcast.net/~wffalby/JNI_on_zOS_files.zip. It contains the program source files, the JCL to create and run the executable modules, a script file to compile the Java program, and text files showing the output from the programs, the Java Virtual Machine (JVM), and the Java class disassembler. The programming examples show a z/OS assembler program calling into a C module that invokes Java methods. The glue code—the C module—runs on z/OS. The Java code runs under USS. You should have these files handy as you read the rest of this article.
To get the most out of this article, readers should have experience writing code in the z/OS environment, be familiar with the three languages used in the examples, have experience with inter-language communications on z/OS, be cognizant of USS, and have the necessary permissions and resources to compile and run the Java code.
Let’s begin with a brief definition of the JNI. Everything described here is documented in several manuals and on various Websites. We’ve pulled it together and provided working examples, as many of us understand concepts better when we see them in practice.
What is JNI?
According to the Java Website (http://java.sun.com/j2se/1.5.0/docs/guide/jni/index.html), the JNI “is a standard programming interface for writing Java native methods and embedding the Java virtual machine into native applications. The primary goal is binary compatibility of native method libraries across all Java virtual machine implementations on a given platform.” JNI is the glue between Java and non-Java applications. To create this glue, you use C or C++ as the intermediate language between Java and whatever other language you’re using.
The Assembler Code
The Assembler program creates the High-Level Language (HLL) environment, calls into the C module to create the JVM, invokes a Java method, calls into the C module to destroy the JVM, and then terminates the HLL environment.
The code in Figure 1 calls into the pre-initialization routine to create the HLL environment. The variables used in the call are:
- INITSUB: The value 3 indicates this is a request to initialize the environment to execute subroutines multiple times.
- ADRPITBL: This contains the address of a table that contains the name of the glue code load module.
- ADRSRTNS: This contains the address of the service routine vector. A zero value indicates there’s no service routine vector.
- RTOPTS: This is a fixed-length field containing the run-time options.
- TOKEN: This value is used to represent the newly created environment. It’s used in subsequent calls.
The table that contains the name of the glue code module, known as the preinit table, is created using three macros. The operands on the CEEXPITY macro are the name of the glue code module and its entry point. A zero value says the module is to be dynamically loaded. The first entry in the table has an index of 0. A second entry would have an index of 1. The sequence of index numbers continues as you’d expect. This index value is used in subsequent calls.
The code in Figure 2 calls into the C module to create the JVM. The variables used in the call are:
- CALLSUB is a call to invoke a subroutine, as indicated by the value 4.
- PITBLIDX is the index into the preinit table for the module that’s called.
- TOKEN is the value used to represent the run-time environment and was initialized by the INITSUB call.
- PARMPTR is a pointer to a parameter list.
- PARMSTR contains character test data.
- PARMNMBR contains the function code used by the C module.
- RETCODE is the subroutine return code.
- REASCODE is the subroutine reason code.
- FDBKCODE is feedback information when the environment ends. The information here is useful in determining the cause of an unexpected termination.
The PARMVCTR variable has the address of a string. This is followed by the address of the C module function code, PARMNMBR. The PARMVCTR address is placed into the PARMPTR variable. This is the standard OS linkage in the mainframe environment. The parameter list is a list of addresses.
The code in Figure 3 terminates the HLL environment. The variables used in the call are:
- TERM is a request to terminate the environment, as indicated by the value 5.
- TOKEN is a value used to represent the existing environment that’s to be terminated.
- RETCODE is the subroutine return code.
The C Code
This is a simple module that highlights particular points about the JNI. It creates the JVM, gets handles to the Java class and its methods, calls those methods, and destroys the JVM:
#pragma linkage(PRODC2J, fetchable)
The C module must be able to be fetched so the HLL pre-initialization can load it. For more information, see the z/OS Language Environment Programming Reference and XL C/C++ Language Reference.
The code in Figure 4 creates the JVM. The value in the pathStr variable is the classpath for the jar file containing the Java code. You must use your own directory. The value in the cmd1Str variable contains options useful in problem determination. Because the C code is running on the native MVS side of z/OS, the literal values must be translated to ASCII by the __etoa function calls.
Once the JVM is successfully created, you get a handle to the Java class that contains the methods you want to invoke (see Figure 5). The class name must be translated. The JNI calls are fully described on the Java Website.
You get handles to the Java methods once the Java class has been found, as shown in Figure 5. The method signature is the input to the method and what it returns. You use the javap program to get method signatures; this is described later. You can learn more at http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javap.html.
The code in Figure 5 shows how the input to the Java method is created and how the method is called. The call into NewStringUTF creates a Java string object using UTF-8 encoding. The call into CallStaticObjectMethod invokes the Java method, test, identified by the variable workMethod. The call into ExceptionCheck tests to see if an exception occurred in the Java method. The call into GetStringUTFChars returns the string in UTF-8 encoding. The call into ReleaseStringUTFChars tells the JVM the string is no longer needed.
The Java Code
This is a simple class that exposes a few methods that the C module invokes. Note the static keyword on the method declarations. The C module uses the static versions of the JNI calls. There’s nothing more in the Java code that needs to be called out.
Java Class File Disassembler
The javap command disassembles a class file. The output depends on the options selected. Here, we wanted the method signatures. The C module needs the signatures so it can get the method handles. The command and its disassembled output appear in the compressed file referenced previously. The dot (.) in the classpath option says to look for the Java class in the current directory. The –s option says to produce method signatures. The signature gives the data types of the method inputs and its return value. You must use the signatures exactly as shown in the output of the javap program in the JNI calls to get the method IDs.
C Module Compilation
There are several interesting items in this JCL. The compile parameter, OPTFILE, indicates where to look for additional compile options. The LANGLVL option is set to allow the use of C-type comments. That is, the /* -- */ format. The XPLINK option specifies use of the Extra Performance Linkage. According to the z/OS Language Environment Programming Guide, XPLINK “has the potential for a significant performance increase when used in an environment of frequent calls between small functions or subprograms.” The SEARCH option says where to look for additional includes. The SYSIN for the BIND step says to include the libjvm side-deck. This contains the exported functions in the libjvm.so. This shared object implements the calls to create and destroy the JVM. (To learn more, refer to the XL C/C++ User’s Guide.) Programs using XPLINK must reside in a Partitioned Data Set Extension (PDSE) or the Hierarchical File System (HFS). We’re interested only in a PDSE.
Running the Samples
Consider the options in the CEEOPTS Data Definition (DD) statement. XPLINK is turned on. This is required because we compiled the C module using XPLINK. Also, the ENVAR option indicates where to look for environment variables. In this case, we set the LIBPATH to the Java executables. You can learn more from the z/OS Language Environment Customization manual. The output appears in a file named output.txt in the compressed file you downloaded. What’s shown is the output of the Write To Operator (WTO) macros from the Assembler module and the output from the C module. A portion of the output from the JVM verbose option is also listed.
In the CEEOPTS DD statement specified in the execution JCL, you can specify the following options: TRAP(ON,NOSPIE),TERM(UAIMM). The TRAP option specifies how LE handles abends. The ON suboption fully enables the Language Environment (LE) condition handler. The NOSPIE suboption says that LE won’t issue the ESPIE macro. The TERM option is shorthand for “Terminating Thread Actions.” This option sets the information level when LE percolates an error condition. The UAIMM suboption tells LE to request a system dump of the original abend.
This article provided working examples of Assembler-to-Java using the JNI—a good starting point for developers wanting to take advantage of functionality available in Java code. The write-once, run-anywhere paradigm comes to z/OS.
Several items are left to the reader. The Assembler code isn’t re-entrant. The C code modifies character literals. There’s no discussion about permissions or defining users on USS. Performance techniques haven’t been employed in any of the code. The Java and C code use static calls, so this isn’t an example of creating an object instance using JNI. Even with these limitations, readers will have a leg up when writing their first programs using JNI on z/OS.
- z/OS Language Environment Programming Guide
- z/OS: Language Environment Run-Time Messages
- Language Environment Writing Interlanguage Communication Applications
- XL C/C++ Programming Guide
- XL C/C++ Language Reference
- XL C/C++ User’s Guide
- z/OS Language Environment Customization manual
- Java Stand-alone Applications on z/OS Volume II
- Petrolino, Thomas. “The Ins and Outs of Language Environment’s CEEPIPI Service.” SHARE, August 2009.
- Java Diagnostics information center: http://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp
- IBM Developer Kit and Runtime Environment, Java 2 Technology Edition, Version 5.0 Diagnostics Guide
- Best practices for JNI: ibm.com/developerworks/java/library/j-jni
- Hints and tips for Java on z/OS: www-03.ibm.com/systems/z/os/zos/tools/java/faq/javafaq.html.