Programmming in C/C++ with the Java Native Interface

Matthew Mead

Contents

Educational goals
Introduction
Glossary
JNI overview
Example
Exercises
References

Educational goals

Introduction

The Java Native Interface, JNI, is just that; an interface. It's an API that allows Java code to interact with code written in another language, typically C or C++. Because the JNI is an interface, JVM vendors are free to implement the virtual machine as they see fit. As long as the JVM follows the specification of the JNI, all native code written to the specification should work with that JVM.

Each software project has its own requirements and constraints, so the decision to use native code with Java is highly dependent on the particular situation. Although there may be several reasons that one would want to use native code, two common justifications are code reuse and performance.

  • Code Reusability. It may be argued that (superficially) Java is a better C++ (maybe in the way that C++ is a better C). However, the benefits of using Java at this level do not outweigh the overwhelming task of re-implementing currently tested and debugged C++ code. Given large amounts of proven C++ code, it may be more cost-effective to incorporate its functionality into a Java program, rather than to port (re-implement/test/debug) the C++ functionality in Java.

  • Performance. Given that Java is interpreted (more or less), one would assume that compiled native code would perform much better. It may be useful to "hand-code" native methods where speed is critical. However, the rise in JIT compilers and other performance-enhancing technologies may render complaints of Java's poor performance invalid. Only time will tell.
  • Glossary

    Please follow the link to the glossary page.

    JNI overview

    The Java Development Kit (JDK) provides tools and library routines that help the Java programmer interface with native code. Microsoft and Sun both provide their own implementations of the JDK, and are incompatible with each other. All of the examples and discussions use Sun's implementation. Later in this lesson, we will explore the differences in Microsoft's implementation and understand the pros and cons of their approach. Incidentally, Microsoft refers to their implementation as the Raw Native Interface, RNI.

    javah

    javah is a useful tool that creates a C-style header file from a given class. The resulting header file describes the class file in C terms. Although it is possible to manually create the header file, this is almost always a bad idea. javah knows exactly how Java types and objects map into C types. For example, an int in Java maps to a long in C, and a long in Java maps to a 64-bit value, _int64, in native code.

    jni.h

    jni.h is a C/C++ header file that is included with the JDK. This file defines all of the necessary data types. It contains mostly #defines and typedefs that hide the complexity of mapping Java types to native types. It also #includes many other platform-specific header files. Since javah automatically includes this header file, you as the programmer usually do not have to explicity include this file.

    JNI data types

    It is important that the data types that are passed between Java and native code have the same properties. The easiest and safest way is to use the table below when mapping between Java and native code. As you can see, with the exception of void, the native types simply have a j prepended to the Java type, which makes it easy to remember.

    Java TypeNative TypeDescription
    booleanjboolean8 bits, unsigned
    bytejbyte8 bits, signed
    charjchar16 bits, unsigned
    doublejdouble64 bits
    floatjfloat32 bits
    intjint32 bits, signed
    longjlong64 bits, signed
    shortjshort16 bits, signed
    voidvoidN/A

    JNI type signatures

    In native code, Java type signatures are encoded using the mappings shown below. The exact use of these mappings will be shown in Exercise #2.

    Java TypeSignature
    booleanZ
    byteB
    charC
    doubleD
    floatF
    intI
    longJ
    voidV
    objectLfully-qualified-class;
    type[][type
    method signature( arg-types) ret-type

    Examples:

    MethodSignature
    void f1()()V
    int f2(int, long)(IJ)I
    boolean f3(int[])([I)B
    double f4(String, int)(Ljava/lang/String;I)D
    void f5(int, String [], char)(I[Ljava/lang/String;C)V

    Example

    One of the best ways to learn a new programming skill is by example. In keeping with tradition, we present the canonical programming example that simply prints the words "Hello World!" to the display. The twist is that Java code will invoke a native function to do the actual printing via the C library function, printf. A more detailed explanation of this example can be found here.

    1. Create the Java files. First, create the two Java files as shown below.

    HelloWorld.java
    class HelloWorld 
    {
      public native void displayMessage();
      static 
      {
        System.loadLibrary("HelloWorldImp"); 
      }
    }
    
    Main.java
    class Main 
    {
      public static void main(String[] args) 
      {
        HelloWorld hello = new HelloWorld();
        hello.displayMessage();
      }
    }
    
    2. Compile the Java files.
    javac HelloWorld.java
    javac Main.java
    
    3. Create the header file. Uses the .class file created previously to create HelloWorld.h. (Note the -jni argument to javah.)
    javah -jni HelloWorld
    
    4. Create the C++ file. This example shows the native code with a .cpp extension, but this file could just have easily had a .c extension since there is nothing "C++" about it.
    HelloWorld.cpp
    #include <stdio.h>
    #include "HelloWorld.h"   // this header file was generated by javah
    JNIEXPORT void JNICALL Java_HelloWorld_displayMessage(JNIEnv *env, jobject obj) 
    {
      printf("Hello World!\n");
    }
    
    5. Build the shared library from native code. This is where some of the differences show up. Make sure that the search paths are correct and that the file extensions are what the compiler expects. Also, the convention in Unix is to prepend lib onto the names of library files and to append .so onto the names of shared library files.
    # This works on sirius.cs.pdx.edu, note .C extension
    g++ -G -I/pkgs/jdk1.1.1/include -I/pkgs/jdk1.1.1/include/solaris HelloWorld.C -o libHelloWorldImp.so
    
    : This should work under NT. Replace the include paths to match your environment
    : and be sure to include Sun's JDK not Microsoft's!
    cl -Im:\jdk1.1.5\include -Im:\jdk1.1.5\include\win32 -LD HelloWorld.cpp -FeHelloWorldImp.dll
    
    6. Execute the program. On Unix, you may have to move the shared library into a directory that's in your search path, or add the current directory to your path. Under Windows NT, the current directory is searched by default.
    java Main
    
    Hello World!  <--- this is displayed on the screen
    

    Exercises

    Please follow the link to the exercises page.

    References

    Websites and online documents: Books: Other related work:

    Copyright © 1998 Sergio Antoy. All Rights Reserved
    February 99, version 020199