Using JNI on HP-UX

 

The Java Native Interface, typically referred to as JNI, provides for two different capabilities. It provides for a Java main program to call C and C++ code and it provides for C and C++ main programs to call Java code. The HP-UX implementation of JNI does not differ significantly from that of the JavaSoft implementation on Solaris. The online JavaSoft documentation tree for JNI is located here: (java.sun.com) The examples below detail the differences that you need to be aware of when using native methods on HP-UX. In particular interfacing with HP’s C++ language requires slightly different handling.

 

 

Java Calling a Native Method

 

The first capability that JNI provides is to allow Java code running under the Java virtual machine to make native method calls. This allows the Java programmer to supply a native implementation instead of a Java implementation for any method in any user defined class. The Java keyword native is used in the declaration of the method and no body or definition is supplied in the Java source code.

 

native void sample(int x, int y);

 

The above declares a non-static native method named "sample" which returns a void and it accepts two integers as parameters.

 

The native method must be implemented in C or C++. When using C++ native methods special care must be taken to properly initialize the C++ runtime before transferring control to any C++ native method. For the HP C++ compiler this means that the routine _main must be called before transferring control to any C++ method. In the examples below we will detail a mechanism for ensuring that the C++ runtime is properly initialized.

 

All Java native methods must reside in a shared library. This implies that the native methods must be built using the +z or +Z compiler options. These options cause the compiler to generate PIC (Position-Independent-Code). The shared library containing the native method implementation is dynamically loaded during execution of the Java program using the Java method: System.loadLibrary(). The implementation of this function with the Java VM on HP-UX relies upon the HP-UX runtime routine shl_load.

 

The alignment requirements for Java data structures allocated in the Java VM are not the same as those found in the standard PA-RISC calling conventions. The Java VM requires that class instance data members that are 64-bits in size (longs and doubles) be allocated at the next available 4-byte boundary. The standard PA-RISC calling conventions normally require that such object be aligned on the next 8-byte boundary. If a C or C++ native method were to access such a Java class instance data member that was not properly aligned on a 8-byte boundary a SIGBUG signal would be delivered to the program and this would terminate the Java VM. To prevent this situation we recommend that the "+u4" option to the C and aCC compilers be used when compiling native code that will directly access Java data members. This command-line option instructs the compiler to use special code when dereferencing pointers to the 64-bit types. The special sequence correctly handles the cases in which a 64-bit type is misaligned.

Implementers of native methods called by Java main programs should take care to insure that their code does not dereference null pointers. The Java VM is linked using the -z option, which is discussed in the man page for the C compiler. If a native method does deference a null pointer a SIGSEGV will be delivered to the Java VM and this will cause the VM to terminate.

 

HP provides the following three examples on how to call C and C++ code from Java code using the standard JNI calling mechanism.

  

Sample Native Method Implementation in C

 

Here is the sample Java program that will call a native method, which will have a C implementation:

 //

// File TestJavaCallingNative.java

//

class TestJavaCallingNative {

native static void sayHelloWorld();

public static void main(String args[])

{

String libname = "cImpl";

  try {

System.loadLibrary(libname);

System.out.println("Library " + libname +

" successfully loaded");

}

catch (UnsatisfiedLinkError Err) {

System.out.println("error: " + Err);

return;

}

  System.out.println("Calling sayHelloWorld");

sayHelloWorld();

System.out.println("All done");

}

}

 

Compile this class:

 $ javac TestJavaCallingNative.java

 Output:

 TestJavaCallingNative.class

 

Generate the JNI header file for this class. You must have the current directory in your CLASSPATH for the javah command to find your newly compiled class file.

$ javah –jni TestJavaCallingNative

Output:

TestJavaCallingNative.h

 

Here is the sample C native method implementation for sayHelloWorld:

 

/*

* File cImpl.c

*/

#include "TestJavaCallingNative.h"

#include <stdio.h>

 

JNIEXPORT void JNICALL

Java_TestJavaCallingNative_sayHelloWorld(JNIEnv *env,

jclass class)

{

printf("C says HelloWorld via stdio\n");

}

 

Compile this C source file:

 $ cc –Ae +u4 +z –c cImpl.c \

–I/opt/java/include -I/opt/java/include/hp-ux

Output:

 cImpl.o 

Note that you are required to supply either -Aa or -Ae as acommand line option to the C compiler. The Java header files that you must include require support for ANSI C prototypes.

Create the shared library containing the native method implementation:

 $ ld –b –o libcImpl.sl cImpl.o

 Output:

 libcImpl.sl

 

Executing the Java program

 You must set the SHLIB_PATH environment variable to contain the location of the directory that contains libcImpl.sl. SHLIB_PATH is the HP-UX name for LD_LIBRARY_PATH and can contain a list of directories each separated by colons.

 $ export SHLIB_PATH=$(/bin/pwd):$SHLIB_PATH

$ java TestJavaCallingNative

Library cImpl successfully loaded

Calling sayHelloWorld

C says HelloWorld via stdio

All done

 

 

Sample Native Method Implementation in C++ (cfront)

 

Here is the sample Java program that will call a native method, which will have a C++ implementation. This C++ example will use the cfront based C++ compiler. HP has two different C++ compilers, and older cfront based product and a newer ANSI C++ compiler. You can tell which C++ compiler you are using by the name of the driver. The older cfront based product uses CC as the driver, while the newer ANSI C++ compiler uses aCC as the name of the driver. The official HP product names for these two C++ compilers are HP C++ for the cfront based product and HP aC++ for the new ANSI C++ compiler.

//

// File TestJavaCallingNative.java

//

class TestJavaCallingNative {

  native static void initialize();

native static void sayHelloWorld();

  public static void main(String args[])

{

String libname = "CCImpl";

  try {

System.loadLibrary(libname);

System.out.println("Library " + libname +

" successfully loaded");

}

catch (UnsatisfiedLinkError Err) {

System.out.println("error: " + Err);

return;

}

System.out.println("initialize C++ runtime");

initialize();

  System.out.println("Calling sayHelloWorld");

sayHelloWorld();

System.out.println("All done");

}

}

 

The above example is very similar to the C example, but for the C++ examples you need to initialize the C++ runtime data structures before transferring control to any C++ code. We accomplish this by adding a second native method called initialize() which will perform the necessary initialization step.

 

Compile this class:

$ javac TestJavaCallingNative.java

Output:

TestJavaCallingNative.class

 

Generate the JNI header file for this class. You must have the current directory in your CLASSPATH for the javah command to find your newly compiled class file.

$ javah –jni TestJavaCallingNative

Output:

TestJavaCallingNative.h

 

Here is the sample C++ native method implementation for sayHelloWorld:

//

// File CCImpl.C

//

#include "TestJavaCallingNative.h"

#include <iostream.h>

 

extern "C" {

void _main();

}

 

JNIEXPORT void JNICALL

Java_TestJavaCallingNative_initialize(JNIEnv *, jclass)

{

_main();

}

 

JNIEXPORT void JNICALL

Java_TestJavaCallingNative_sayHelloWorld(JNIEnv *, jclass)

{

cout << "C++ (cfront) says HelloWorld via iostreams"

<< endl;

}

 

In the above example you can see the additional native method initialize() simply calls the routine _main(). Since the Java Native interface does allow us to call a routine name _main directly we have to write this wrapper function to allow us to call _main indirectly. Also note that we need to use extern "C" to direct the C++ compiler not to perform name mangling on this routine. The entry point for _main is found in the C++ runtime support library libC.sl.

 

Compile this C++ source file:

 $ CC –ext +z –c CCImpl.c \

–I/opt/java/include -I/opt/java/include/hp-ux

Output:

CCImpl.o

 

In the above, the –ext flag directs the C++ compiler to allow extensions. The Java JNI interface requires support for 64-bit integer types. In C++ 64-bit integers are declared using a new type: "long long". This C++ compiler requires that you add –ext to the command line to enable support for this feature. If you have –ext on your command line and you still get the warning two long declarators (162) then you will require an update to the C++ compiler (PHSS_10767). You can ignore the warning about "signed" not implemented, as signed is the default behavior for data types in C++.

 

Create the shared library containing the native method implementation:

 $ CC –b –o libCCImpl.sl CCImpl.o -lC

 Output:

libCCImpl.sl

 

Note that the C++ driver program CC must be used to create the shared library. Also note that an explicit dependency on libC.sl needs to be specified via –lC. This will cause the linker to record the fact that whenever libCCImpl.sl is loaded into memory the dependant library libC.sl also must be loaded. This is necessary since libC.sl contains the entry-point _main as well as all the other necessary runtime support routines required by every C++ method.

 

Executing the Java program

 You must set the SHLIB_PATH environment variable to contain the location of the directory that contains libCCImpl.sl. SHLIB_PATH is the HP-UX name for LD_LIBRARY_PATH and can contain a list of directories each separated by colons.

 $ export SHLIB_PATH=$(/bin/pwd):$SHLIB_PATH

$ java TestJavaCallingNative

Library CCImpl successfully loaded

initialize C++ runtime

Calling sayHelloWorld

C++ (cfront) says HelloWorld via iostreams

All done

 

 

Sample Native Method Implementation in ANSI C++

 

Here is the sample Java program that will call a native method, which will have a C++ implementation. This C++ example will use the ANSI C++ compiler. HP has two different C++ compilers, and older cfront based product and a newer ANSI C++ compiler. You can tell which C++ compiler you are using by the name of the driver. The older cfront based product uses CC as the driver, while the newer ANSI C++ compiler uses aCC as the name of the driver. The official HP product names for these two C++ compilers are HP C++ for the cfront based product and HP aC++ for the new ANSI C++ compiler.

 

 

//

// File TestJavaCallingNative.java

//

class TestJavaCallingNative {

native static void initialize();

native static void sayHelloWorld();

  public static void main(String args[])

{

String libname = "aCCImpl";

  try {

System.loadLibrary(libname);

System.out.println("Library " + libname +

" successfully loaded");

}

catch (UnsatisfiedLinkError Err) {

System.out.println("error: " + Err);

return;

}

  System.out.println("initialize C++ runtime");

initialize();

  System.out.println("Calling sayHelloWorld");

sayHelloWorld();

System.out.println("All done");

}

}

 

The above example is very similar to the C example, but for the C++ examples you need to initialize the C++ runtime data structures before transferring control to any C++ code. We accomplish this by adding a second native method called initialize() which will perform the necessary initialization step.

 

Compile this class:

 $ javac TestJavaCallingNative.java

 Output:

 TestJavaCallingNative.class

  

Generate the JNI header file for this class. You must have the current directory in your CLASSPATH for the javah command to find your newly compiled class file.

 $ javah –jni TestJavaCallingNative

 Output:

TestJavaCallingNative.h

 

Here is the sample C++ native method implementation for sayHelloWorld:

 //

// File aCCImpl.C

//

#include "TestJavaCallingNative.h"

#include <iostream.h>

 

extern "C" {

void _main();

}

 

JNIEXPORT void JNICALL

Java_TestJavaCallingNative_initialize(JNIEnv *, jclass)

{

_main();

}

 

JNIEXPORT void JNICALL

Java_TestJavaCallingNative_sayHelloWorld(JNIEnv *, jclass)

{

cout << "ANSI C++ says HelloWorld via iostreams"

<< endl;

}

 

In the above example you can see the the additional native method initialize() simply calls the routine _main(). Since the Java Native interface does allow us to call a routine name _main directly we have to write this wrapper function to allow us to call _main indirectly. Also note that we need to use extern "C" to direct the C++ compiler not to perform name mangling on this routine. The entry point for _main is found in the C++ runtime support library libCsup.sl.

 

Compile this C++ source file:

 

$ aCC +z +u4 –c aCCImpl.c \

–I/opt/java/include -I/opt/java/include/hp-ux

Output:

aCCImpl.o

 

The ANSI C++ compiler has builtin support for 64-bit integers. In C++ 64-bit integers are declared using a new type: "long long".

  

Create the shared library containing the native method implementation:

 $ aCC –b –o libCCImpl.sl CCImpl.o \

–lCsup –lstream -lstd

 Output:

libaCCImpl.sl

 

Note that the C++ driver program aCC must be used to create the shared library. Also note that an explicit dependency on three C++ runtime libraries need to be specified. The ANSI C++ compiler has split the runtime support libraries into three separate shared libraries: libCsup.sl, libstream.sl and libstd.sl. All ANSI C++ programs require libCsub.sl. The other two libraries are required by a subset of C++ programs. In this example, we could omit libstd.sl since we are not using the Standard Template Library functionality. However is usually best to link your native method shared library against all three of these libraries. Linking a shared library against these other shared libraries will cause the linker to record the fact that whenever libaCCImpl.sl is loaded into memory the dependant libraries libCsup.sl, libstream.sl and libstd.sl. also must be loaded. This is necessary since libCsup.sl contains the entry-point _main as well as all the other necessary runtime support routines required by every C++ method.

 

Executing the Java program

 

You must set the SHLIB_PATH environment variable to contain the location of the directory that includes libaCCImpl.sl. SHLIB_PATH is the HP-UX name for LD_LIBRARY_PATH and can contain a list of directories each separated by colons.

 $ export SHLIB_PATH=$(/bin/pwd):$SHLIB_PATH

$ java TestJavaCallingNative

Library aCCImpl successfully loaded

initialize C++ runtime

Calling sayHelloWorld

ANSI C++ says HelloWorld via iostreams

All done

  

 

Non-Java Code Calling Java Methods

 

The second capability that JNI provides is to allow non-Java code to make calls into Java bytecode. This capability is provide via the C/C++ header file jni.h. Using this capability C and C++ programs can make calls into Java bytecode.

 

In JDK1.1, the Java Virtual Machine is shipped as a shared library. To utilize the Java VM in a native application you must link the application against libjava.sl. The JNI supports an Invocation API that allows you to create and initialize a new Java Virtual Machine. Using this newly created VM you can invoke various Java methods, create new Java objects or access Java objects created by the Java VM.

 

The Invocation API is provided by the C/C++ header file jni.h. It actually provides two different interfaces. It provides a standard C interface and an object based C++ interface.

 

Here is the sample Java class that we will call from C and C++ programs:

 //

// File TestNonJavaCallingJava.java

//

public class TestNonJavaCallingJava {

 

public static void printInt(int arg)

{

System.out.println("TestNonJavaCallingJava.printInt recieved: "

+ arg);

}

}

 

Compile the above Java file into bytecode:

 

$ javac TestNonJavaCallingJava.java

 

Output:

 

TestNonCallingJava.class

 

HP provides the following three examples on how to call C and C++ code from Java code using the standard JNI calling mechanism.

 

 

Sample Implementation in C

 

Here is the sample C program. This program will use the Java VM Invocation Interface to create a new Java VM, find a class named TestNonJavaCallingJava, and find a Java method named printInt, and invoke the method with a argument of 10.

 

/* File: c_main.c

*

* Example 1: C Source File as the Main

* create a new Java Virtual Machine

* and locate the class TestNonJavaCallingJava

* and invoke the static method printInt

*/

 

#include <jni.h>

 

main() {

JavaVM *jvm;

JNIEnv *env;

JDK1_1InitArgs vm_args;

 

const struct JNINativeInterface_ *jni;

const struct JNIInvokeInterface_ *vmi;

jclass cls;

jmethodID mid;

 

printf("beginning execution...\n");

 

vm_args.version = 0x00010001;

 

/* Get the default initialization args and

* set the class path

*/

JNI_GetDefaultJavaVMInitArgs(&vm_args);

 

/*

* Note if you have not set and exported the

* enironment variable CLASSPATH you may also

* need to add this:

* vm_args.classpath = ".:/opt/java/lib/classes.zip";

*/

 

/* load and initialize a java vm,

* return a JNI interface ptr in env

*/

JNI_CreateJavaVM(&jvm,&env,&vm_args);

jni = *env;

vmi = *jvm;

 

 

/* Find the class */

cls = jni->FindClass(env, "TestNonJavaCallingJava");

if (cls == 0) {

printf("Could not locate class TestNonJavaCallingJava"

" in your CLASSPATH.\n");

exit(1);

}

 

/* Find the method */

mid = jni->GetStaticMethodID(env, cls, "printInt", "(I)V");

if (mid == 0) {

printf("Could not locate method printInt with signature (I)V"

" in the class TestNonJavaCallingJava.\n");

exit(1);

}

 

/* Invoke the method */

jni->CallStaticVoidMethod(env, cls, mid, 100);

 

/* we are done */

vmi->DestroyJavaVM(jvm);

}

 

Compile this C source file:

 $ cc –g –c –Ae c_main.c \

–I/opt/java/include -I/opt/java/include/hp-ux

Output:

c_main.o

 

Create the main program and link against libjava.sl

$ cc –g –o c_main c_main.o \

–L/opt/java/lib/PA_RISC/green_threads/ \

-lc -ljava

Output:

 c_main

 The ordering of the two libraries, -lc and -ljava , on the link line is important. Do not change this ordering as the current HP-UX linker requires that these libraries be specified in this order.

Executing the C main program:

 $ ./c_main

 Prints on the display:

 TestNonJavaCallingJava.printInt received: 100

 

 

Sample Implementation in C++ (cfront)

 

Here is the sample C++ program. This program will use the C++ object-oriented interface provided in the Java VM Invocation Interface to create a new Java VM, find a class named TestNonJavaCallingJava, and find a Java method named printInt, and invoke the method with a argument of 10.

 

// File: CC_main.C

//

// Example 2: (cfront) C++ Source File as the Main

// create a new Java Virtual Machine,

// locate the class TestNonJavaCallingJava

// and invoke the static method printInt

 

#include <jni.h>

#include <iostream.h>

#include <stdlib.h>

 

int main() {

JNIEnv *env;

JavaVM *jvm;

JDK1_1InitArgs vm_args;

cout << "beginning execution..." << endl;

vm_args.version = 0x00010001;

// Get the default initialization args and set

// the class path

JNI_GetDefaultJavaVMInitArgs(&vm_args);

//

// Note if you have not set and exported the

// environment variable CLASSPATH you may also

// need to add this:

// vm_args.classpath = ".:/opt/java/lib/classes.zip";

// create and initialize a new Java VM,

// return a JNI interface ptr in env

JNI_CreateJavaVM(&jvm, &env, &vm_args);

// Find the class

jclass cls=env->FindClass("TestNonJavaCallingJava");

if (cls == 0) {

cerr << "Could not locate class TestNonJavaCallingJava"

" in your CLASSPATH" << endl;

exit(1);

}

 

// Find the method

jmethodID mid=env->GetStaticMethodID(cls, "printInt", "(I)V");

if (mid == 0) {

cerr << "Could not locate method printInt with signature"

" (I)V in the class TestNonJavaCallingJava." << endl;

exit(1);

}

 

// Invoke the method

env->CallStaticVoidMethod(cls, mid, 100);

// we are done

jvm->DestroyJavaVM();

}

 

Compile this C++ source file:

 

$ CC –g –c –ext CC_main.C \

–I/opt/java/include -I/opt/java/include/hp-ux

You can ignore the warning about "signed" not implemented, as signed is the default behavior for data types in C++.

Output:

CC_main.o

 

Create the main program and link against libjava.sl

$ CC –g –o CC_main CC_main.o \

–L/opt/java/lib/PA_RISC/green_threads/ \

-lc -ljava

Output:

 CC_main

The ordering of the two libraries, -lc and -ljava , on the link line is important. Do not change this ordering as the current HP-UX linker requires that these libraries be specified in this order.

 

Executing the C main program:

 $ ./CC_main

 

Prints on the display:

 beginning execution...

TestNonJavaCallingJava.printInt received: 100

 

 

Sample Implementation in ANSI C++

 

Here is the sample ANSI C++ program. This program will use the C++ object-oriented interface provided in the Java VM Invocation Interface to create a new Java VM, find a class named TestNonJavaCallingJava, and find a Java method named printInt, and invoke the method with a argument of 10.

 

// File: aCC_main.C

//

// Example 3: ANSI C++ Source File as the Main

// create a new Java Virtual Machine,

// locate the class TestNonJavaCallingJava

// and invoke the static method printInt

 

#include <jni.h>

#include <iostream.h>

#include <stdlib.h>

 

int main() {

JNIEnv *env;

JavaVM *jvm;

JDK1_1InitArgs vm_args;

cout << "beginning execxution..." << endl;

vm_args.version = 0x00010001;

// Get the default initialization args and set

// the class path

JNI_GetDefaultJavaVMInitArgs(&vm_args);

//

// Note if you have not set and exported the

// environment variable CLASSPATH you may also

// need to add this:

// vm_args.classpath = ".:/opt/java/lib/classes.zip";

// create and initialize a new Java VM,

// return a JNI interface ptr in env

JNI_CreateJavaVM(&jvm, &env, &vm_args);

// Find the class

jclass cls=env->FindClass("TestNonJavaCallingJava");

if (cls == 0) {

cerr << "Could not locate class TestNonJavaCallingJava"

" in your CLASSPATH." << endl;

exit(1);

}

 

// Find the method

jmethodID mid=env->GetStaticMethodID(cls, "printInt", "(I)V");

if (mid == 0) {

cerr << "Could not locate method printInt with signature"

" (I)V in the class TestNonJavaCallingJava." << endl;

exit(1);

}

 

// Invoke the method

env->CallStaticVoidMethod(cls, mid, 100);

// we are done

jvm->DestroyJavaVM();

}

 

Compile this C++ source file:

 $ aCC –g –c –ext aCC_main.C \

–I/opt/java/include -I/opt/java/include/hp-ux

Output:

aCC_main.o

 

Create the main program and link against libjava.sl

$ aCC –g –o aCC_main aCC_main.o \

–L/opt/java/lib/PA_RISC/green_threads/ \

-lc -ljava

Output:

 aCC_main

 The ordering of the two libraries, -lc and -ljava , on the link line is important. Do not change this ordering as the current HP-UX linker requires that these libraries be specified in this order.

 

Executing the C main program:

 $ ./aCC_main

 Prints on the display:

beginning execution...

TestNonJavaCallingJava.printInt received: 100