JNI programming examples for Linux on POWER

In a few simplified examples, this paper describes key Java™ Native Interface (JNI) programming concepts and highlights Linux™ on POWER™-specific, as well as common, programming pitfalls, where appropriate.

Nikolay Yevik, Linux on POWER Technical Consultant, IBM

Nikolay Yevik, a Linux on POWER consultant in IBM’s Solutions Enablement group, has more than 5 years of experience working on UNIX platforms, performing development work in C, C++ and Java. He has Masters degrees in Petroleum Engineering and Computer Science.



11 February 2005

Introduction

This article is useful to developers who are new to Java Native Interface programming and want to test their JNI programming skills on Linux for POWER distributions, such as Red Hat Enterprise Linux 3 and SUSE LINUX Enterprise Linux V9.


Compilers

The simplified source code shown in the examples in this article was compiled on SLES 9 and RHEL AS 3 Update 3 using the latest Service Refresh of the 64-bit version of IBM Java SDK 1.4.2 and IBM XL C/C++ Advanced Edition V7.0 for Linux. Note that using IBM Java SDK and IBM XL C/C++ compiler is not mandatory, but is highly preferable. Feel free to use alternatives for compiling your Java and JNI C/C++ code on Linux for POWER distributions.

The IBM XL C/C++ compiler can produce binaries highly optimized for performance on POWER™ processors, which, in most cases, can dramatically increase the performance of any C/C++ code. The IBM XL C/C++ compiler for Linux is highly compatible with GNU C/C++ compilers and also includes gxl* compiler variants that translate GNU C/C++ compiler flags into a corresponding command for IBM XL C/C++ compiler and then invoke it. This significantly simplifies the portability of a Makefile written for GNU compilers when transitioning to the IBM XL C/C++ compiler.


Examples

This section contains examples that are based on commonly used and publicly available sample JNI code and shows you how to compile these examples on Linux on POWER. Additional information about JNI programming can be found in the following developerWorks articles: Java environments for Linux on POWER architecture (October 2004) and JNI Programming on AIX (March 2004).

Calling native function from Java code example

Listing 1. HelloWorld.java
class HelloWorld {
   /* Native method declaration */
   public native void displayHelloWorld();
     /* Use static intializer */
      static {
         System.loadLibrary("hello");
      }
      /* Main function calls native method*/
      public static void main(String[] args) {
        new HelloWorld().displayHelloWorld();
      }
}
Listing 2. HelloWorld.c
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>

JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
{
    printf("Hello world!\n");
    return;
}

Compiling

  1. Compile Java code to produce HelloWorld.class file:

    javac HelloWorld.java
  2. Generate HelloWorld.h file with javah utility:

     javah –jni HelloWorld

    Listing 3. HelloWorld.h
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class HelloWorld */
    
    #ifndef _Included_HelloWorld
    #define _Included_HelloWorld
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     HelloWorld
     * Method:    displayHelloWorld
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
  3. Compile native C code:

    xlc_r –q64 –qarch=auto –qmkshrobj HelloWorld.c –o libhello.so
Running
java –cp . HelloWorld
Output
HelloWorld!

Notes

  1. All native JNI methods should be declared in the Java code.
  2. The System.loadLibrary() call should be made from a static block.
  3. If any changes are made to the code that change the function's signature, javah needs to be rerun.
  4. In this example, we create a 64-bit shared library to be loaded into 64-bit JVM address space by passing a –q64 flag to the thread-safe reentrant compiler variant xlc_r. For gcc compiler, pass the –m64 flag. Please note that you must create a 64-bit shared library to be loaded into 64-bit JVM address space. Should you use 32-bit JVM, create a 32-bit shared library. Trying to load a 64-bit object into 32-bit address space, or vice versa, leads to a java.lang.UnsatisfiedLinkError.
  5. The option –qarch=auto generates instructions that will run on the hardware platform on which the program is compiled. There are many general and hardware-specific compiler optimizations available on the IBM XL C/C++ compiler that allow you to produce highly optimized code.
  6. The option –qmkshrobj produces a shared object; the corresponding option for gcc is –shared.
  7. It might be necessary to set the LD_LIBRARY_PATH global variable to point to the location of the newly created shared library.

Chaining JNI shared objects example

Let’s modify the first example to have displayHelloWorld() call functions from several object files. The Java portion of the code, the HelloWorld.java file, remains the same as in the previous example, but we modify the native code.

Listing 4. HelloWorld.c
#include <jni.h>
#include "HelloWorld.h"
#include stdio.h>

extern void func1(), func2(),func3();

JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
{
    printf("Hello world!\n");
    func1();
    func2();
    func3();
    return;
}
Listing 5. share1.c
/************
* share1.c: *
*************/

#include <stdio.h>

void func1()
{
   printf("func1 called\n");
}

void func2()
{
   printf("func2 called\n");
}
Listing 6. share2.c
/************
* share2.c  *
*************/
void func3 ()
 {
   printf("func3 called\n");
 }

Compiling

The following Makefile shows the compilation process. Type make to run.

Listing 7. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH.
# You can change this to refer to a specific directory
JAVA_HOME=/opt/IBMJava2-ppc64-142

libhello.so: shrsub.o HelloWorld.h
        xlc_r -M -I. -I$(JAVA_HOME)/include  -q64 -qarch=auto -
        qmkshrobj HelloWorld.c shrsub.o -o libhello.so

HelloWorld.h: HelloWorld.class
        $(JAVA_HOME)/bin/javah -jni HelloWorld

HelloWorld.class:
        $(JAVA_HOME)/bin/javac  HelloWorld.java

shrsub.o: c.o
        xlc_r -qmkshrobj -q64 -o shrsub.o share1.o share2.o

c.o:
        xlc_r -q64 -c share1.c
        xlc_r -q64 -c share2.c
Running
 java –cp . HelloWorld
Output
Hello world!
func1 called
func2 called
func3 called

Example of using JNI invocation API with static compile-time linking

This C++ example shows how to use JNI Invocation API -- that is, embedding JVM into native C/C++ code and then invoking it to execute portions of applications written in Java.

Listing 8. invoke.cpp
#include <jni.h>
#include <stdlib.h>
#include <iostream.h>

int main() {
    JNIEnv *env;
    JavaVM *jvm;
    JavaVMInitArgs vm_args;
    JavaVMOption options[5];
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jobjectArray args;


     options[0].optionString = "-Xms4M";
     options[1].optionString = "-Xmx64M";
     options[2].optionString = "-Xss512K";
     options[3].optionString = "-Xoss400K";
     options[4].optionString = "-Djava.class.path=.";

     vm_args.version = JNI_VERSION_1_4;
     vm_args.options = options;
     vm_args.nOptions = 5;
     vm_args.ignoreUnrecognized = JNI_FALSE;

    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args);
    if (res < 0) {
        cerr<< "Can't create Java VM" << endl;
        goto destroy;
    }
    cls = env->FindClass("Prog");
    if (cls == 0) {
        cerr << "Can't find Prog class" << endl;
        goto destroy;
    }
    mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");
    if (mid == 0) {
        cerr<< "Can't find Prog.main" << endl;
        goto destroy;
    }
    jstr = env->NewStringUTF(" from C++!");
    if (jstr == 0) {
        cerr << "Out of memory" << endl;
        goto destroy;
    }
    args = env->NewObjectArray( 1,
                        env->FindClass("java/lang/String"), jstr);
    if (args == 0) {
        cerr << "Out of memory" << endl;
        goto destroy;
    }
    env->CallStaticVoidMethod( cls, mid, args);

destroy:
    if (env->ExceptionOccurred()) {
       env->ExceptionDescribe();
    }
    jvm->DestroyJavaVM();

}
Listing 9. Prog.java
public class Prog {
        public static void main(String[] args) {
               System.out.println("Hello World" + args[0]);
        }
}

Compiling

The following Makefile shows the compilation process. Type make to run it.

Listing 10. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH.
# You can change this to refer to a specific directory
JAVA_HOME=/opt/IBMJava2-ppc64-142


all:    invoke Prog.class


invoke:
        xlC -q64 -I$(JAVA_HOME)/include –
        L$(JAVA_HOME)/jre/bin/classic -L$(JAVA_HOME)/jre/bin -ljvm -o
         invoke invoke.cpp

Prog.class:
        $(JAVA_HOME)/bin/javac Prog.java

clean:
        rm -f *.class invoke *.o
Running
./invoke
Output
Hello World from C++!

Notes

  1. Since we are using 64-bit JVM, we must compile the C++ invocation code as 64-bit, as well. There is no way to load a 64-bit object into 32-bit address space, or the other way around.
  2. We are using JNI_VERSION_1_4(0x00010004). It is no longer possible in latest JDKs to call JNI_CreateJavaVM() and pass it a version of JNI_VERSION_1_1(0x00010001). The versions that can be passed are JNI_VERSION_1_2(0x00010002) or JNI_VERSION_1_4(0x00010004).
  3. When creating a JVM through JNI Invocation API, it is possible to control JVM parameters instead of relying on default values by passing JVM parameters to the embedded JVM being invoked. For example, use:
    -Xms<size> to set initial Java heap size;
    -Xmx<size> to set maximum Java heap;
    -Xoss<size> to set maximum Java stack size for any thread;
    -Xss<size> to set maximum native stack size for any thread.
    Please note that the -X options are non-standard and subject to change without notice.
  4. It is important not to forget to link to the JVM libraries from both ../jre/bin/classic and ../jre/bin directories.
  5. Be aware of setting a SETUID bit on a root-owned file with native code that uses JNI Invocation API. If non-root starts such a root-owned SETUID bit-set executable, LD_LIBRARY_PATH is cleared for security and necessary libraries are therefore not found.
  6. It might be necessary to set the LD_LIBRARY_PATH global variable to point to the location of the newly created shared library and JVM libraries in ../jre/bin/classic and ../jre/bin directories.

Using JNI Invocation API with dynamic compile-time linking example

This C code example shows how to use JNI Invocation API with dynamic loading of libraries through the use of dlopen() and dlsym() calls. The Java code of the Prog.java file remains the same as in the previous example.

Listing 11. invoke.c
#include <jni.h>
#include <stdlib.h>
#include <dlfcn.h>

main() {
    JNIEnv *env;
    JavaVM *jvm;
    JavaVMInitArgs vm_args;
    JavaVMOption options[5];
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jobjectArray args;
    char classpath[1024];

    vm_args.version = 0x00010004;
    vm_args.options = options;
    vm_args.nOptions = 5;
    vm_args.ignoreUnrecognized = JNI_FALSE;

    options[0].optionString = "-Xms4M";
    options[1].optionString = "-Xmx64M";
    options[2].optionString = "-Xss512K";
    options[3].optionString = "-Xoss400K";
    options[4].optionString = "-Djava.class.path=.";


    void* lib_handle = 0;
    void* (*lib_func)() = 0;
    lib_handle = dlopen("/opt/IBMJava2-ppc64-
    142/jre/bin/classic/libjvm.so", RTLD_NOW);
    if (!lib_handle)
    {
       fprintf(stderr, "dlopen failed\n");
       goto destroy;
    }

    lib_func = (void*(*)())dlsym(lib_handle, 
    "JNI_GetDefaultJavaVMInitArgs");

    lib_func(&vm_args);

    lib_func = (void*(*)())dlsym(lib_handle, "JNI_CreateJavaVM");
    lib_func(&jvm,&env,&vm_args);

    cls = (*env)->FindClass(env, "Prog");
    if (cls == 0) {
        fprintf(stderr, "Can't find Prog class\n");
        goto destroy;
    }

    mid = (*env)->GetStaticMethodID(env, cls, "main", 
    "([Ljava/lang/String;)V");
    if (mid == 0) {
        fprintf(stderr, "Can't find Prog.main\n");
        goto destroy;
    }

    jstr = (*env)->NewStringUTF(env, " from C!");
    if (jstr == 0) {
        fprintf(stderr, "Out of memory\n");
        goto destroy;
    }
    args = (*env)->NewObjectArray(env, 1,
                        (*env)->FindClass(env, "java/lang/String"), jstr);
    if (args == 0) {
        fprintf(stderr, "Out of memory\n");
        goto destroy;
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    destroy:
    if ((*env)->ExceptionOccurred(env)) {
         (*env)->ExceptionDescribe(env);
    }

    (*jvm)->DestroyJavaVM(jvm);
}

Compiling

The following Makefile shows the compilation process. Type make to run it.

Listing 12. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH.
# You can change this to refer to a specific directory
JAVA_HOME=/opt/IBMJava2-ppc64-142

all:    invoke Prog.class


invoke:
        xlc -I$(JAVA_HOME)/include -L$(JAVA_HOME)/jre/bin/classic 
       -L$(JAVA_HOME)/jre/bin -q64 -qarch=auto -o invoke invoke.c -ljvm

Prog.class:
       $(JAVA_HOME)/bin/javac Prog.java

clean:
        rm -f *.class invoke *.o
Running
./invoke
Output
Hello World from C!

Notes

  1. The same considerations as in the previous example with compile-time linking apply.
  2. To compile the same example using gcc, the following command would need to be issued, where JAVA_HOME is your JDK installation home directory:

    gcc -I$(JAVA_HOME)/include  
    -L$(JAVA_HOME)/jre/bin/classic -L$(JAVA_HOME)/jre/bin 
    -m64 -rdynamic -o invoke invoke.c -ldl –ljvm

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Linux on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=47131
ArticleTitle=JNI programming examples for Linux on POWER
publish-date=02112005