Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
2 views

Java Fundamentals Tutorial_ Java Native Interface (JNI)

The document provides an overview of the Java Native Interface (JNI), which allows Java to interact with code written in other languages, primarily C/C++. It covers the components of JNI, development processes for both Java and C, type conversions, and methods for accessing properties and throwing exceptions from native code. Additionally, it explains how to handle arrays and native method arguments, emphasizing the importance of proper naming and library management to avoid common errors.

Uploaded by

Rizz Pappy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Java Fundamentals Tutorial_ Java Native Interface (JNI)

The document provides an overview of the Java Native Interface (JNI), which allows Java to interact with code written in other languages, primarily C/C++. It covers the components of JNI, development processes for both Java and C, type conversions, and methods for accessing properties and throwing exceptions from native code. Additionally, it explains how to handle arrays and native method arguments, emphasizing the importance of proper naming and library management to avoid common errors.

Uploaded by

Rizz Pappy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

Java Fundamentals Tutorial: Java Native Interface (JNI)

ProTech Home  Java Fundamentals Tutorial: Java Native Interface (JNI)


Prev Next

16. Java Native Interface (JNI)


16.1. JNI Overview
An interface that allows Java to interact with code written in another language
Motivation for JNI

Code reusability

Reuse existing/legacy code with Java (mostly C/C++)


Performance

Native code used to be up to 20 times faster than Java, when running in interpreted mode
Modern JIT compilers (HotSpot) make this a moot point
Allow Java to tap into low level O/S, H/W routines
JNI code is not portable!

Note
JNI can also be used to invoke Java code from within natively-written applications - such as those
written in C/C++.

In fact, the java command-line utility is an example of one such application, that launches Java code in
a Java Virtual Machine.

16.2. JNI Components


javah - JDK tool that builds C-style header les from a given Java class that includes native methods

Adapts Java method signatures to native function prototypes


jni.h - C/C++ header le included with the JDK that maps Java types to their native counterparts

javah automatically includes this le in the application header les

16.3. JNI Development (Java)


Create a Java class with native method(s): public native void sayHi(String who, int times);
Load the library which implements the method: System.loadLibrary("HelloImpl");
Invoke the native method from Java

For example, our Java code could look like this:


package com.marakana.jniexamples;

public class Hello {


public native void sayHi(String who, int times); //

static { System.loadLibrary("HelloImpl"); } //

public static void main (String[] args) {


Hello hello = new Hello();
hello.sayHi(args[0], Integer.parseInt(args[1])); //
}
}

The method sayHi will be implemented in C/C++ in separate le(s), which will be compiled into a library.

The library lename will be called libHelloImpl.so (on Unix), HelloImpl.dll (on Windows) and
libHelloImpl.jnilib (Mac OSX), but when loaded in Java, the library has to be loaded as HelloImpl .

16.4. JNI Development (C)


We use the JDK javah utility to generate the header le package_name_classname.h with a function
prototype for the sayHi method: javac -d ./classes/
./src/com/marakana/jniexamples/Hello.java Then in the classes directory run: javah -jni
com.marakana.jniexamples.Hello to generate the header le com_marakana_jniexamples_Hello.h
We then create com_marakana_jniexamples_Hello.c to implement the
Java_com_marakana_jniexamples_Hello_sayHi function

The le com_marakana_jniexamples_Hello.h looks like:

...
#include <jni.h>
...
JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi
(JNIEnv *, jobject, jstring, jint);
...

The le Hello.c looks like:

#include <stdio.h>
#include "com_marakana_jniexamples_Hello.h"

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi(JNIEnv *env, jobject obj,


jint i;
jboolean iscopy;
const char *name;
name = (*env)->GetStringUTFChars(env, who, &iscopy);
for (i = 0; i < times; i++) {
printf("Hello %s\n", name);
}
}

16.5. JNI Development (Compile)


We are now ready to compile our program and run it

The compilation is system-dependent 


This will create libHelloImpl.so , HelloImpl.dll , libHelloImpl.jnilib (depending on the O/S)
Set LD_LIBRARY_PATH to point to the directory where the compiled library is stored
Run your Java application

For example, to compile com_marakana_jniexamples_Hello.c in the "classes" directory (if your .h le and .c
le are there) on Linux do:

gcc -o libHelloImpl.so -lc -shared \


-I/usr/local/jdk1.6.0_03/include \
-I/usr/local/jdk1.6.0_03/include/linux com_marakana_jniexamples_Hello.c

On Mac OSX :

gcc -o libHelloImpl.jnilib -lc -shared \


-I/System/Library/Frameworks/JavaVM.framework/Headers com_marakana_jniexamples_Hello.c

Then set the LD_LIBRARY_PATH to the current working directory:

export LD_LIBRARY_PATH=.

Finally, run your application in the directory where your compiled classes are stored ("classes" for example):

java com.marakana.jniexamples.Hello Student 5


Hello Student
Hello Student
Hello Student
Hello Student
Hello Student

Note
Common mistakes resulting in java.lang.UnsatisfiedLinkError usually come from incorrect
naming of the shared library (O/S-dependent), the library not being in the search path, or wrong
library being loaded by Java code.

16.6. Type Conversion


In many cases, programmers need to pass arguments to native methods and they do also want to receive
results from native method calls
Two kind of types in Java:

Primitive types such as int , float , char , etc


Reference types such as classes, instances, arrays and strings (instances of java.lang.String class)


However, primitive and reference types are treated di erently in JNI

Mapping for primitive types in JNI is simple

Table 3. JNI data type mapping in variables:

Java Language TypeNative TypeDescription


boolean jboolean 8 bits, unsigned

byte jbyte 8 bits, signed

char jchar 16 bits, unsigned

double jdouble 64 bits

oat j oat 32 bits

int jint 32 bits, signed

long jlong 64 bits, signed

short jshort 16 bits, signed

void void N/A

Mapping for objects is more complex. Here we will focus only on strings and arrays but before we dig
into that let us talk about the native methods arguments

JNI passes objects to native methods as opaque references


Opaque references are C pointer types that refer to internal data structures in the JVM
Let us consider the following Java class:

package com.marakana.jniexamples;

public class HelloName {


public static native void sayHelloName(String name);

static { System.loadLibrary("helloname"); }

public static void main (String[] args) {


HelloName hello = new HelloName();
String name = "John";
hello.sayHelloName(name);
}
}

The .h le would look like this:

...
#include <jni.h>
...
JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName
(JNIEnv *, jclass, jstring);
...


A .c le like this one would not produce the expected result:

#include <stdio.h>
#include "com_marakana_jniexamples_HelloName.h"

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *e


printf("Hello %s", name);
}

16.7. Native Method Arguments


All native method implementation accepts two standard parameters:

JNIEnv *env : Is a pointer that points to another pointer pointing to a function table (array of pointer).
Each entry in this function table points to a JNI function. These are the functions we are going to use for
type conversion
The second argument is di erent depending on whether the native method is a static method or an
instance method

Instance method: It will be a jobject argument which is a reference to the object on which the
method is invoked
Static method: It will be a jclass argument which is a reference to the class in which the method
is de ne

16.8. String Conversion


We just talked about the JNIEnv *env that will be the argument to use where we will nd the type
conversion methods
There are a lot of methods related to strings:

Some are to convert java.lang.String to C string: GetStringChars (Unicode format),


GetStringUTFChars (UTF-8 format)
Some are to convert java.lang.String to C string: NewString (Unicode format), NewStringUTF
(UTF-8 format)
Some are to release memory on C string: ReleaseStringChars , ReleaseStringUTFChars
Note
Details about these methods can be found at
http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html

If you remember the previous example, we had a native method where we wanted to display "Hello name":

#include <stdio.h>
#include "com_marakana_jniexamples_HelloName.h"

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *e


printf("Hello %s", name); //
}

This example would not work since the jstring type represents strings in the Java virtual machine. This
is di erent from the C string type ( char * )


Here is what you would do, using UTF-8 string for instance:

#include <stdio.h>
#include "com_marakana_jniexamples_HelloName.h"

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *e


const jbyte *str;
str = (*env)->GetStringUTFChars(env, name, NULL); //
printf("Hello %s\n", str);
(*env)->ReleaseStringUTFChars(env, name, str); //
}

This returns a pointer to an array of bytes representing the string in UTF-8 encoding (without making a
copy)
When we are not making a copy of the string, calling ReleaseStringUTFChars prevents the memory
area used by the string to stay "pinned". If the data was copied, we need to call
ReleaseStringUTFChars to free the memory which is not used anymore
Here is another example where we would construct and return a java.lang.String instance:

#include <stdio.h>
#include "com_marakana_jniexamples_GetName.h"

JNIEXPORT jstring JNICALL Java_com_marakana_jniexamples_ReturnName_GetName(JNIEnv *en


char buffer[20];
scanf("%s", buffer);
return (*env)->NewStringUTF(env, buffer);
}

16.9. Array Conversion


Here we are going to focus on primitive arrays only since they are di erent from objects arrays in JNI
Arrays are represented in JNI by the jarray reference type and its "subtypes" such as jintArray ⇒A
jarray is not a C array!
Again we will use the JNIEnv *env parameter to access the type conversion methods

Get<Type>ArrayRegion : Copies the contents of primitive arrays to a preallocated C bu er. Good to


use when the size of the array is known
Get<Type>ArrayElements : Gets a pointer to the content of the primitive array
New<Type>Array : To create an array specifying a length

We are going to see an example of how to read a Java primitive array in the native world


First, this would be your Java program:

package com.marakana.jniexamples;

public class ArrayReader {


private static native int sumArray(int[] arr); //
public static void main(String[] args) {
//Array declaration
int arr[] = new int[10];
//Fill the array
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
ArrayReader reader = new ArrayReader();
//Call native method
int result = reader.sumArray(arr); //
System.out.println("The sum of every element in the array is " + Integer.toSt
}
static {
System.loadLibrary("arrayreader");
}
}

This method will return the sum of each element in the array

After running javah , create your .c le that would look like this:

#include <stdio.h>
#include "com_marakana_jniexamples_ArrayReader.h"

JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_ArrayReader_sumArray(JNIEnv *env


jint *native_array;
jint i, result = 0;
native_array = (*env)->GetIntArrayElements(env, array, NULL); /* */
if (native_array == NULL) {
return 0;
}
for (i=0; i<10; i++) {
result += native_array[i];
}
(*env)->ReleaseIntArrayElements(env, array, native_array, 0);
return result;
}

We could also have used GetIntArrayRegion since we exactly know the size of the array

16.10. Throwing Exceptions In The Native World


We are about to see how to throw an exception from the native world
Throwing an exception from the native world involves the following steps:

Find the exception class that you want to throw


Throw the exception
Delete the local reference to the exception class


We could imagine a utility function like this one:

void ThrowExceptionByClassName(JNIEnv *env, const char *name, const char *message) {


jclass class = (*env)->FindClass(env, name); //
if (class != NULL) {
(*env)->ThrowNew(env, class, message); //
}
(*env)->DeleteLocalRef(env, class); //
}

Find exception class by its name

Throw the exception using the class reference we got before and the message for the exception

Delete local reference to the exception class

Here would be how to use this utility method:

ThrowExceptionByClassName(env,"java/lang/IllegalArgumentException","This exception is

16.11. Access Properties And Methods From Native Code


You might want to modify some properties or call methods of the instance calling the native code
It always starts with this operation: Getting a reference to the object class by calling the GetObjectClass
method
We are then going to get instance eld id or an instance method id from the class reference using
GetFieldID or GetMethodID methods
For the rest, it di ers depending on whether we are accessing a eld or a method


From this Java class, we will see how to call its methods or access its properties in the native code:

package com.marakana.jniexamples;

public class InstanceAccess {


public String name; //

public void setName(String name) { //


this.name = name;
}

//Native method
public native void propertyAccess(); //
public native void methodAccess(); //

public static void main(String args[]) {


InstanceAccess instanceAccessor = new InstanceAccess();
//Set the initial value of the name property
instanceAccessor.setName("Jack");
System.out.println("Java: value of name = \""+ instanceAccessor.name +"\"");
//Call the propetyAccess() method
System.out.println("Java: calling propertyAccess() method...");
instanceAccessor.propertyAccess(); //
//Value of name after calling the propertyAccess() method
System.out.println("Java: value of name after calling propertyAccess() = \""+
//Call the methodAccess() method
System.out.println("Java: calling methodAccess() method...");
instanceAccessor.methodAccess(); //
System.out.println("Java: value of name after calling methodAccess() = \""+ i
}

//Load library
static {
System.loadLibrary("instanceaccess");
}
}

Name property that we are going to modify along this code execution

This method will be called by the native code to modify the name property

This native method modi es the name property by directly accessing the property

This native method modi es the name property by calling the Java method setName()


This would be our C code for native execution:

#include <stdio.h>
#include "com_marakana_jniexamples_InstanceAccess.h"

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_InstanceAccess_propertyAccess(JN


jfieldID fieldId;
jstring jstr;
const char *cString;

/* Getting a reference to object class */


jclass class = (*env)->GetObjectClass(env, object); /* */

/* Getting the field id in the class */


fieldId = (*env)->GetFieldID(env, class, "name", "Ljava/lang/String;"); /* */
if (fieldId == NULL) {
return; /* Error while getting field id */
}

/* Getting a jstring */
jstr = (*env)->GetObjectField(env, object, fieldId); /* */

/* From that jstring we are getting a C string: char* */


cString = (*env)->GetStringUTFChars(env, jstr, NULL); /* */
if (cString == NULL) {
return; /* Out of memory */
}
printf("C: value of name before property modification = \"%s\"\n", cString);
(*env)->ReleaseStringUTFChars(env, jstr, cString);

/* Creating a new string containing the new name */


jstr = (*env)->NewStringUTF(env, "Brian"); /* */
if (jstr == NULL) {
return; /* Out of memory */
}
/* Overwrite the value of the name property */
(*env)->SetObjectField(env, object, fieldId, jstr); /* */
}

JNIEXPORT void JNICALL Java_com_marakana_jniexamples_InstanceAccess_methodAccess(JNIE


jclass class = (*env)->GetObjectClass(env, object); /* */
jmethodID methodId = (*env)->GetMethodID(env, class, "setName", "(Ljava/lang/Stri
jstring jstr;
if (methodId == NULL) {
return; /* method not found */
}
/* Creating a new string containing the new name */
jstr = (*env)->NewStringUTF(env, "Nick"); /* */
(*env)->CallVoidMethod(env, object, methodId, jstr); /* */
}


This is getting a reference to the object class

Gets a eld Id from the object class, specifying the property to get and the internal type. you can nd
information on the jni type there:
http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html
This will return the value of the property in the native type: here a jstring

We need to convert the jstring to a C string

This creates a new java.lang.String that is going be use to change the value of the property

This sets the property to its new value

Gets a method id from the object class previously obtained, specifying the name of the method along
with its signature. There is a very useful java tool that you can use to get the signature of a method:
javap -s -p ClassName for instance javap -s -p InstanceAccess
This creates a new java.lang.String that we are going to use as an argument when calling the java method
from native code
Calling CallVoidMethod since the Java method return type is void and we are passing the previously
created jstring as a parameter

Prev Next
Home | ToC

You might also like