The Java Native Interface The JNIEnv argument | CodeGuru

The Java Native Interface The JNIEnv argument

Bruce Eckel’s Thinking in Java Contents | Prev | Next JNI is a fairly rich programming interface that allows you to call native methods from a Java application. It was added in Java 1.1, maintaining a certain degree of compatibility with its Java 1.0 equivalent, the native method interface (NMI). NMI has design characteristics that […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
10 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

JNI


is a fairly rich programming interface that allows you to call native methods


from a Java application. It was added in Java 1.1, maintaining a certain degree


of compatibility with its Java 1.0 equivalent, the

native
method interface (NMI). NMI has design characteristics that make it unsuitable
for adoption in all virtual machines. For this reason, future versions of the
language might no longer support NMI, and it will not be covered here.

Currently,


JNI is designed to interface with native methods written only in

C
or C++. Using JNI, your native methods can:
  • Create,
    inspect, and update Java objects (including arrays and
    Strings)
  • Call
    Java methods
  • Catch
    and throw exceptions
  • Load
    classes and obtain class information
  • Perform
    runtime type checking

Thus,


virtually everything you can do with classes and objects in ordinary Java you


can also do in native methods.


Calling
a native method

We’ll


start with a simple example: a Java program that calls a native method, which


in turn calls the Win32


MessageBox( )

API function to display a graphical text box. This example will also be used


later with J/Direct. If your platform is not Win32, just replace the C header


include:

#include
<windows.h>

with

#include
<stdio.h>

and


replace the call to


MessageBox( )

with a call to


printf( )

.

The


first step is to write the Java code declaring a native method and its arguments:

class ShowMsgBox {
  public static void main(String [] args) {
    ShowMsgBox app = new ShowMsgBox();
    app.ShowMessage("Generated with JNI");
  }
  private native void ShowMessage(String msg);
  static {
    System.loadLibrary("MsgImpl");
  }
}

The


native method declaration is followed by a


static

block that calls


System.loadLibrary( )

(which you could call at any time, but this style is more appropriate).


System.loadLibrary( )

loads a DLL in memory and links to it. The DLL must be in your system path or


in the directory containing the Java class file. The file name extension is


automatically added by the JVM depending on the platform.


The
C header file generator: javah

Now


compile your Java source file and run

javah
on the resulting
.class
file.
Javah
was present in version 1.0, but since you are using Java 1.1 JNI you must
specify the
–jni
switch:
javah
–jni ShowMsgBox
Javah

reads the Java class file and for each native method declaration it generates a


function prototype in a C or C++ header file. Here’s the output: the


ShowMsgBox.h

source file (edited slightly to fit into the book):

/* DO NOT EDIT THIS FILE
   - it is machine generated */
#include &lt;jni.h&gt;
/* Header for class ShowMsgBox */
 
#ifndef _Included_ShowMsgBox
#define _Included_ShowMsgBox
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ShowMsgBox
 * Method:    ShowMessage
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_ShowMsgBox_ShowMessage
  (JNIEnv *, jobject, jstring);
 
#ifdef __cplusplus
}
#endif
#endif

As


you can see by the


#ifdef
__cplusplus

preprocessor directive, this file can be compiled either by a C or a C++


compiler. The first


#include

directive includes


jni.h

,


a header file that, among other things, defines the types that you can see used


in the rest of the file.

JNIEXPORT
and
JNICALL
are macros that expand to match platform-specific directives;
JNIEnv,
jobject
and
jstring
are JNI data type definitions.


Name
mangling and function signatures

JNI


imposes a naming convention (called


name
mangling

)


on native methods; this is important, since it’s part of the mechanism by


which the virtual machine links Java calls to native methods. Basically, all


native methods start with the word “Java,” followed by the name of


the class in which the Java native declaration appears, followed by the name of


the Java method; the underscore character is used as a separator. If the Java


native method is overloaded, then the function signature is appended to the


name as well; you can see the native signature in the comments preceding the


prototype. For more information about name mangling and native method


signatures, please refer to the JNI documentation.


Implementing
your DLL

At


this point, all you have to do is write a C or C++ source file that includes


the javah-generated header file and implements the native method, then compile


it and generate a dynamic link library. This part is platform-dependent, and


I’ll assume that you know how to create a DLL. The code below implements


the native method by calling a Win32 API. It is then compiled and linked into a


file called


MsgImpl.dll

(for “Message Implementation”).

#include &lt;windows.h&gt;
#include "ShowMsgBox.h"
 
BOOL APIENTRY DllMain(HANDLE hModule,
  DWORD dwReason, void** lpReserved) {
  return TRUE;
}
 
JNIEXPORT void JNICALL
Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv,
  jobject this, jstring jMsg) {
  const char * msg;
  msg = (*jEnv)-&gt;GetStringUTFChars(jEnv, jMsg,0);
  MessageBox(HWND_DESKTOP, msg,
    "Thinking in Java: JNI",
    MB_OK | MB_ICONEXCLAMATION);
  (*jEnv)-&gt;ReleaseStringUTFChars(jEnv, jMsg,msg);
}

If


you have no interest in Win32, just skip the


MessageBox( )

call; the interesting part is the surrounding code. The arguments that are


passed into the native method are the gateway back into Java. The first, of type


JNIEnv

,


contains


all the hooks that allow you to call back into the JVM. (We’ll look at


this in the next section.) The second argument has a different meaning


depending on the type of method. For non-


static

methods like the example above (also called


instance
methods

),


the second argument is the equivalent of the “this” pointer in C++


and similar to


this

in Java: it’s a reference to the object that called the native method. For


static

methods, it’s a reference to the


Class

object where the method is implemented.

The


remaining arguments represent the Java objects passed into the native method


call. Primitives are also passed in this way, but they come in by value.

In


the following sections we’ll explain this code by looking at how to


access and control the JVM from inside a native method.


Accessing
JNI functions:

The
JNIEnv argument

JNI


functions are those that the programmer uses to interact with the JVM from


inside a native method. As you can see in the example above, every JNI native


method receives a special argument as its first parameter: the


JNIEnv

argument, which is a pointer to a special JNI data structure of type


JNIEnv_

.


One element of the JNI data structure is a pointer to an array generated by the


JVM; each element of this array is a pointer to a JNI function. The JNI


functions can be called from the native method by dereferencing these pointers


(it’s simpler than it sounds). Every JVM provides its own implementation


of the JNI functions, but their addresses will always be at predefined offsets.

Through


the


JNIEnv

argument, the programmer has access to a large set of functions. These


functions can be grouped into the following categories:

  • Obtaining
    version information
  • Performing
    class and object operations
  • Handling
    global and local references to Java objects
  • Accessing
    instance fields and static fields
  • Calling
    instance methods and static methods
  • Performing
    string and array operations
  • Generating
    and handling Java exceptions

The


number of JNI functions is quite large and won’t be covered here.


Instead, I’ll show the rationale behind the use of these functions. For


more detailed information, consult your compiler’s JNI documentation.

If


you take a look at the


jni.h

header file, you’ll see that inside the


#ifdef
__cplusplus

preprocessor conditional, the


JNIEnv_

structure is defined as a class when compiled by a C++ compiler. This class


contains a number of inline functions that let you access the JNI functions


with an easy and familiar syntax. For example, the line in the preceding example

(*jEnv)->ReleaseStringUTFChars(jEnv,
jMsg,msg);

can


be rewritten as follows in C++:

jEnv->ReleaseStringUTFChars(jMsg,msg);

You’ll


notice that you no longer need the double dereferencing of the


jEnv

pointer, and that the same pointer is no longer passed as the first parameter


to the JNI function call. In the rest of these examples, I’ll use the C++


style.


Accessing
Java Strings

As


an example of accessing a JNI function, consider the code shown above. Here, the


JNIEnv

argument


jEnv

is used to access a Java


String

.


Java


String

s


are in Unicode format, so if you receive one and want to pass it to a


non-Unicode function (


printf( )

,


for example), you must first convert it into ASCII characters with the JNI


function


GetStringUTFChars( )

.


This function takes a Java


String

and converts it to UTF-8 characters. (These are 8 bits wide to hold ASCII


values or 16 bits wide to hold Unicode. If the content of the original string


was composed only of ASCII, the resulting string will be ASCII as well.)

GetStringUTFChars

is the name of one of the fields in the structure that


JNIEnv

is indirectly pointing to, and this field in turn is a pointer to a function.


To access the JNI function, we use the traditional C syntax for calling a


function though a pointer. You use the form above to access all of the JNI


functions.


Advertisement

Passing
and using Java objects

In


the previous example we passed a


String

to the native method. You can also pass Java objects of your own creation to a


native method. Inside your native method, you can access the fields and methods


of the object that was received.

To


pass objects, use the ordinary Java syntax when declaring the native method. In


the example below,


MyJavaClass

has one


public

field and one


public

method. The class


UseObjects

declares


a native method that takes an object of class


MyJavaClass

.


To see if the native method manipulates its argument, the


public

field of the argument is set, the native method is called, and then the value


of the


public

field is printed.

class MyJavaClass {
  public void divByTwo() { aValue /= 2; }
  public int aValue;
}
 
public class UseObjects {
  public static void main(String [] args) {
    UseObjects app = new UseObjects();
    MyJavaClass anObj = new MyJavaClass();
    anObj.aValue = 2;
    app.changeObject(anObj);
    System.out.println("Java: " + anObj.aValue);
  }
  private native void
  changeObject(MyJavaClass obj);
  static {
    System.loadLibrary("UseObjImpl");
  }
}

After


compiling the code and handing the


.class

file


to


javah

,


you can implement the native method. In the example below, once the field and


method ID are obtained, they are accessed through JNI functions.

JNIEXPORT void JNICALL
Java_UseObjects_changeObject(
  JNIEnv * env, jobject jThis, jobject obj) {
  jclass cls;
  jfieldID fid;
  jmethodID mid;
  int value;
  cls = env-&gt;GetObjectClass(obj);
  fid = env-&gt;GetFieldID(cls,
        "aValue", "I");
  mid = env-&gt;GetMethodID(cls,
        "divByTwo", "()V");
  value = env-&gt;GetIntField(obj, fid);
  printf("Native: %dn", value);
  env-&gt;SetIntField(obj, fid, 6);
  env-&gt;CallVoidMethod(obj, mid);
  value = env-&gt;GetIntField(obj, fid);
  printf("Native: %dn", value);
}

The


first argument aside, the C++ function receives a


jobject

,


which is the native side of the Java object reference we pass from the Java


code. We simply read


aValue

,


print it out, change the value, call the object’s


divByTwo( )

method, and print the value out again.

To


access a field or method, you must first obtain its identifier. Appropriate JNI


functions take the class object, the element name, and the signature. These


functions return an identifier that you use to access the element. This


approach might seem convoluted, but your native method has no knowledge of the


internal layout of the Java object. Instead, it must access fields and methods


through indexes returned by the JVM. This allows different JVMs to implement


different internal object layouts with no impact on your native methods.

If


you run the Java program, you’ll see that the object that’s passed


from the Java side is manipulated by your native method. But what exactly is


passed? A pointer or a Java reference? And what is the garbage collector doing


during native method calls?

The


garbage
collector continues to operate during native method execution, but it’s
guaranteed that your objects will not be garbage collected during a native
method call. To ensure this,
local
references

are created before, and destroyed right after, the native method call. Since
their lifetime wraps the call, you know that the objects will be valid
throughout the native method call.

Since


these references are created and subsequently destroyed every time the function


is called, you cannot make local copies in your native methods, in


static

variables. If you want a reference that lasts across function invocations, you


need a global reference. Global references are not created by the JVM, but the


programmer can make a global reference out of a local one by calling specific


JNI functions. When you create a global reference, you become responsible for


the lifetime of the referenced object. The global reference (and the object it


refers to) will be in memory until the programmer explicitly frees the


reference with the appropriate JNI function. It’s similar to


malloc( )

and


free( )

in C.


JNI
and Java exceptions

With


JNI,
Java exceptions can be thrown, caught, printed, and rethrown just as they are
inside a Java program. But it’s up to the programmer to call dedicated
JNI functions to deal with exceptions. Here are the JNI functions for exception
handling:
  • Throw( )

    Throws
    an existing exception object. Used in native methods to rethrow an exception.

  • ThrowNew( )

    Generates
    a new exception object and throws it.

  • ExceptionOccurred( )

    Determines
    if an exception was thrown and not yet cleared.

  • ExceptionDescribe( )

    Prints
    an exception and the stack trace.

  • ExceptionClear( )

    Clears
    a pending exception.

  • FatalError( )

    Raises
    a fatal error. Does not return.

Among


these, you can’t ignore


ExceptionOccurred( )

and


ExceptionClear( )

.


Most JNI functions can generate exceptions, and there is no language feature


that you can use in place of a Java try block, so you must call


ExceptionOccurred( )

after


each JNI function call to see if an exception was thrown. If you detect an


exception, you may choose to handle it (and possibly rethrow it). You must make


certain, however, that the exception is eventually cleared. This can be done in


your function using


ExceptionClear( )

or in some other function if the exception is rethrown, but it must be done.

You


must ensure that the exception is cleared, because otherwise the results will


be unpredictable if you call a JNI function while an exception is pending.


There are few JNI functions that are safe to call during an exception; among


these, of course, are all the exception handling functions.


Advertisement

JNI
and threading

Since


Java is a multithreaded language, several threads can call a native method


concurrently. (The native method might be suspended in the middle of its


operation when a second thread calls it.) It’s entirely up to the


programmer to guarantee that the native call is thread-safe, i.e. it does not


modify shared data in an unmonitored way. Basically, you have two options:


declare the native method as


synchronized

or implement some other strategy within the native method to ensure correct,


concurrent data manipulation.

Also,


you should never pass the


JNIEnv

pointer across threads, since the internal structure it points to is allocated


on a per-thread basis and contains information that makes sense only in that


particular thread.


Using
a pre-existing code base

The


easiest way to implement JNI native methods is to start writing native method


prototypes in a Java class, compile that class, and run the


.class

file through


javah

.


But what if you have a large, pre-existing code base that you want to call from


Java? Renaming all the functions in your DLLs to match the JNI name mangling


convention is not a viable solution. The best approach is to write a wrapper


DLL “outside” your original code base. The Java code calls


functions in this new DLL, which in turn calls your original DLL functions.


This solution is not just a work-around; in most cases you must do this anyway


because you must call JNI functions on the object references before you can use


them.


Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.