Foreign Function Interface: JNI

burgerraraSoftware and s/w Development

Nov 18, 2013 (3 years and 6 months ago)

59 views

Foreign Function Interface: JNI



Justin Catterson




Abstract


Most languages contain a foreign function interface. T
he purpose of this is to reuse libraries that
other languages have already established, access legacy code written in other languages, and to
give programmers the ability to use multiple languages for a program to achieve better
performance. Java is a pre
ferred programming language because it is simple
to write and has a
large library, but performance issues and code that have proven the test of ti
me has prevented
Java from becoming

the language of choice. The Java Native Interface (JNI) was developed
to
resolve these problems. My paper will discu
ss when the JNI should be used,
some examples that
explain how it works,
problems with the JNI, typical coding errors that may occur while
programming, and possible applications.





Introduction


Early in the de
velopment stages of Java, it was realized that
Java

would need a way to interface
with native languages. The first release of Java’s Development Kit (JDK 1.0) included a way to
communicate with C and C++. Libraries such as

Java.lang

,

Java.io

, and

Java.net


all used
the native interface to communicate with the host operating system. The orig
inal foreign
function interface did not take into account how different Java Virtual Machines (JVM) handle
memory and different garbage collection schemes. The

Java Native Interface (JNI) was
developed in 1997 with the goal of being compatible across all JVM’s.

Other concerns while
developing the JNI was to be as efficient as possible
,

to allow the support of time
-
critical
systems, and allow Java Virtual Machin
e’s to be embedded into native languages.

The JNI has
become the standard for communicating with native interfaces in Java, and

all libraries that
communicate

with native

languages have been re
-
written to follow the JNI standard.

Figure 1

shows where the

JNI fits into the Java Virtual Machine, if Java calls a native functions, JNI calls
the dynamically linked library that contains the native method. If the native application is calling
a library in Java, JNI will allow the native application to create a
Java Virtual Machine.


2




Figure 1: How JNI communicates with both native apps and the JVM



Determining to
use the
JNI


Before deciding you are going to use the JNI, you should first examine your other options.

Using the JNI will add
complexity to your
program; it will make your program more difficult to
debug, you will also have to consider memory leaks
, you will be more vulnerable to security
issues
, and it can be expensive to make the call through JNI
.

If you decide to use JNI, you will
lose one of J
ava’s advantages, being portable
;

all code that is implemented in a native language
will not be portable and will have to be re
-
written to support other operating systems. F
or all
these reasons, it is obvious you should first review your other options
before investing the effort
into developing a solution throu
gh the JNI.
One way to get around using the
JNI

is through data
communication, such as a TCP/IP connection.
In this case, the programmer will need to set
-
up a
client
-
server type system, and the
server will need to determine how to interoperate the data.
Using a TCP/IP connection does come with its own risks, but this is out of scope for this paper.

Another possible solution is to use a JDBC (Java Database Connectivity)

this solution simply
conn
ects Java to a database. The native application would need also to connect to the database,
and the two programs would communicate information through the databa
se. One risk you run
with using a database is cost, if the database is not designed properly,

maintaining the system
may become expensive.
Another alternative to using the JNI is by using distributed object
technology such as the Java IDL API.

An IDL (Interface Definition Language) can be used to
find objects, call methods, and get returned data

while on a remote system,
using

a TCP/IP
connection.


Although there are added complexities in using the Java Native Interface, there are some
acceptable reasons for deciding to use it.
All of the alternatives expect that the nati
ve part of the
program
and the J
ava part of the program can exist on separate processes, and sometimes on a
different computer entirely. All of the cases in which it is acceptable to use the JNI, the native
code and the Java code exist on the same process. Separating the progr
am into processes may
cause a “memory footprint” which may be problematic. It takes less memory to load a native
library than to start a new process.
Sometimes

an application will need to use a feature that is

not
supported in the
language;

if such a case

rises
,

the JNI may be the only solution. It is also
accepted to use the JNI when you want to access an existing library from a native language. In
the field of software engineering,
you
want to
try to use already existing code; there is no sense
in re
-
i
nventing the wheel
. Code that has been
rigorously

tested
should be used rather than
risking creating new
code that has not been tested
on
-
site or in the field in which the code will be
3


used.

Java’s design concentrated on reliability, for example,
J
ava en
sures that the
indices

of

an
array

are within range. Java’s concern of reliability did come with a tradeoff, the cost of
execution time. Typically, when dealing with real
-
time embedded systems

we have to deal with
time
-
critical sections. Because Java typ
ically cannot execute as fast as some native languages
such as C, Java does not get considered as a possible solution for embedded systems or in any
situations where time is the primary concern
. With the JNI, Java could be used as a possible
solution for
embedded systems; we will examine this possibility later in this paper. Using the
Java Native Interface

will enable programmers to enjoy
some of
the advantages
that other
languages have to offer

such as execution time.



Java to C
++

Tutorial


To fully
understand how the JNI works and how it could possibly be used
,

we must run through
some tutorials. In this section, I will instruct you how
to
use the Java Native Interface to call a
native function through Java.

My tutorial was executed using the Windo
ws 7 operating system,
Java Development Kit (JDK) version 1.6_20 and a copy of Microsoft Visual Studio 2010.
Although my tutorial was executed with Windows OS, JDK 1.6_20, and Visual Studio 2010, you
do not need
to do
the
same. You will need at least JDK

1.2 and some way to compile the C++
code into a DLL.

This program will read in a file from myfile.txt that I do not provide, just make
one.

myfile.txt


should include a list of numbers less than 10 digits with each number separated
by one
new line
character feed.

Comment out the
call to

divideByZero

, this was used to
demonstrate a crash
.


1.

Add to your system variable path the location of your JDK bin folder. In my case,
C:
\
Program Files (x86)
\
Java
\
jdk1.6.0_20
\
bin

2.

Write the
Java class

jniBubbleSo
rt


seen on the right side

figure 1.1, save the file as

jniBubbleSort.java


3.

Compile the Java code with javac jniBubbleSort.java

4.

Create the header file for C++ with javah jniBubbleSort

5.

Write the native C++ code seen in figure 1.1, save the file as jniBubbl
eSort.cpp

6.

Execute the vcvarsall.bat in the vc directory of Visual Studio

7.

Compile the native file into a dll using
using cl
-
Iinclude
-
Iinclude
\
win32
-
MD

LD

jniBubbleSort
.cpp
-
Fejni
jniBubbleSort
.dll
. The

Iinclude and

Iinclude
\
win32 is where
the linker for jni.h and jni_md.h files
exists

respectively. The

MD links the
jniBubbleSort.dll with the Win32 multithreaded C library.

8.

Now you should be able to execute the program using java jniBubbleSort



4




Figure 2: Java to C++
using the JNI


From the C++ code, you might be wondering what the extra parameters, JNIEnv * and the
jobject are for. The JNIEnv pointer points to the list of JNI functions that are available for the
C++ code to use. The JNIEnv can be used to access Java

classes, methods, and fields

that are a
part of the Java Virtual Machine,

but it is not used in this tutorial. In a later tutorial I will be
using the JNIEnv to access a field in a Java class. For a list of JNI functions please visit
http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html
. The jobject field
is
a reference to the Java object in which the native method belongs to.

You can think of the
jobject field passed as the reserved word ‘this’ in C++.

If any parameters were intended to be
passed, they
would

appear after the

two parameters that are generated from

the JNI.

Earlier in this paper,
“D
etermining
t
o
u
se the JNI

,

it was stated that it is acceptable to use the
JNI

to use libraries previously written. In order to use previously used libraries, you will need to
make a wrapper class due to the code generated by the JNI. To use a library previously written,
create a
Java class that will contain all the functions in the legacy library. For each native
method created, you will then be able to call the native function you desire and return any data
desired.



C++ to Java: Embedding a JVM Tutorial


As shown in figure 1.0, the JNI may also be used to embed a Java Virtual Machine within a
native application.
In order to embed a JVM into a native application, you must add to the
5


system path environment variable
to
the file location of where the JVM.dll

exists. In my case I
found the jvm.dll file in the folder location “
C:
\
Program Files
(x86)
\
Java
\
jdk1.6.0_20
\
jre
\
bin
\
client
”.
Without the jvm.dll folder location in the environment
variable path, you will be unable
to
initialize an instance of the Java V
irtual Machine.
To
compile this program as
an executable use
cl
-
Iinclude
-
Iinclude
\
win32
-
MD Sample2.cpp
-
FeRunMain.exe
-
link "C:

\
Program Fil
es (x86)
\
Java
\
jdk1.6.0_20
\
lib
\
jv
m.lib"
.
In this tutorial
created by IBM developerWorks, we will create a JVM an
d call a Java method to square the
number 5. We will first create the JVM,
and then

find the class that contains the method to
square our number, sample2.java. To find the Java class, we will use FindClass(“sample2”).
After finding the Java class we wil
l find the
Method ID of the static method intMethod.
To find
the static method’s Method ID we use getStaticMethodID passing three parameters
.




Figure 3: C++ to Java using the JNI


1.


the Java class that was found using Find
Class

2.

T
he name of the method we

are

trying to find, intMethod

3.

L
astly a string to represent the signature of the parameter that will be passed into the
method and the return type

For

intMethod

, there is only one parameter, an integer,

the method also returns an
integer, so
the third parameter of

getStaticMethodID


looks like, “(I)I”. For a full list of signature types
please visit
http://download.oracle.com/javase/1.5.0/docs/
guide/jni/spec/types.html
.

In this example, most of the code is for initializ
ing the Java Virtual Machine. My tutorial has the C code
and the Java code in separate directories, so I must set an option to point to the Sample2.class directory,
-
Djava.class
.path=”c:/Users/Justin/Desktop/SeniorSem/cToJava/JavaCode”.



Mapping Types


6


Because Java and native languages such as C treat types differently, we must investigate how the
Java Native Interface maps types. For example, Java treats arrays as objects and

C treats arrays
as

pointers to memory locations. In the JNI, it treats types as either

a primitive type or a
reference type. Primitive types are defined as a specific number of bits

and are either signed or
unsigned. An example of a primitive type is a char. A character in Java is an unsigned 16
-
bit
Unicode character.

The Java character is mapped to an unsig
ned short in C called a jchar. In
most cases, you can treat the jchar as
a
char in C
, but note that the character set in Java can be
twice as large as the ASCII table supported in C (8 bits)
.

Reference types include everything that
does not have a pre
-
de
fined bit size. The JNI splits reference types into a few
different class
es
,
ea
ch which inherit from jObject

see figure
4
.

When using Java reference types, you cannot use
the same syntax as you would if the type was native to C. For example, you cannot use
myClass.doIt() to call the objects myClass’s doIt method. If you woul
d like to call myClass’s
doIt method
you must find the class of the o
bject and the method id to call the method using the
JNI functions. In a later tutorial I will be showing this.

There is also added syntax for accessing
arrays. To access elements of a

Java array, you must
either
copy over the elements
of the array
into a C array

or retrieve a pointer to which the Java
array exists
. If the array size is small or you
only want a small section of an array, it is typical to copy the elements into a C
array with
Get<Type>ArrayRegion, but if the array size is large it may get expensive to make a C copy of
the array.


Within the different
classes

of references, they categorize these objects by how they
can

be
removed from the virtual machi
ne.

There are local references, global referen
ces, and weak
global references. Local references are objects that have been new’d by a native method and
only exist during the execution of the native met
hod. Local references are common because
there is no
thing that needs to be done to specify that the object is a local reference. If you want
to make an object C static within a method, meaning there is only one refere
nce that exists to the
object within memory, you should not use

a local reference. If a static object is declared local, it
will not exist after the execution of the native method, and later calls to the native method could
cause memory corruption or could cause the program to crash.

To allow the C program to
declar
e objects static, the programmer must
use newGlobalRef or newWeakGlobalRef

see figure
5

(code taken from
#1
)

on the declarations
.
Global references exist

until the programmer
speci
fies to delete

the object using DeleteGlobalRef.

Weak references do not ne
ed to be
specified to be deleted from memory, they can be garbage collected, but they may also be deleted
by the programmer using
deleteWeakGlobalRef.


Figure 1.2

7




Figure 4: JNI objects




Figure 5: Code using a global reference



JNI Issues


As stated in
“Determining to use the JNI”, there are many issues that are related to using the JNI.
In this section we will investigate the security problem
, memory management issues, and

check
out the overhead cost of using the Java Native Interface.

By u
sing the J
NI, it becomes very easy
to read/write to data is Java you should not be able to. Because of the JNIEnv pointer, you have
access to all classes and fields. So long as the native language knows about a class, it will be
able to read/write to
private field
s

and overwrite the values of constants.

See figure 1.4 for code
I wrote that exposes this issue

and many other issues
. This is not the only problem related to
native languages accessing memory in Java’s heap. Typically for accessing large arrays

that are
a part of the Java’s heap because it can be expensive to make a copy of the array in the native
language, you
get a pointer to the array instead. With a pointer to the array, it is easy to go
outside the bounds of the array and accidently read/o
verwrite data in Java’s heap not part of the
8


array.

When using the JNI, you will be responsible for memory management on the native
language side. Although in C, this is nothing new, you will need to mange memory for the Java
Virtual Machine. In my exam
ple, figure
6
, I am not only accessing a private field, I have a
memory leak.

The memory leak exists because my call getStringUTFChars does not have a
corresponding releaseStringUTFChars. Be very
conscious

about all JNI calls you make and
ensure that you

do not need to make a corresponding call to release memory.

This example is
also missing error checks. For every call from the native language to Java, you will need to
check that errors have not occurred because the native language will not throw the e
xception.

Figures

3

and
5

are using error checking

for finding the classes and finding the method id’s. In
figure

6
, it probably should check that square has a value.

Returning reference types from native
languages can also cause problems. Let’s say yo
u wanted to return an UnsafePerson seen in
example 1.4 in a native function. The return type for this native function would be a jObject, so
it would be really easy to return a different jObject

than what the Java code was expecting.




Figure 6: JNI
coding issue example


In order to determine whether or not making use of the JNI for ti
me critical situations, we
investigate how much time it takes to make a call through the Java Native Interface.


Demetruis
L. Davis wrote bench
mark tests for two
algorithms, heap sort and Discrete Fast Fourier
transform
. The tests he ran were not optimized so these programs are not testing how good the
optimization is for the compilers. In his research he runs his tests with different array sizes to
check the imp
act of array sizes on Java and on C++. He also runs a number of iterations to
determine how expensive it is to make the call through the JNI.

The results are shown
in

three
graphs on figure
7

separated by array size.

This graph demonstrates, for this ca
se it is worth
making the call through the JNI to execute the C++ algorithm rather than executing the
equivalent Java implementation. The far right graph on figure
7

shows a large performance hit
for the JNI implementation, but this is not caused from

the

expense of the JNI. The author states
that this graph most likely is due to memory limitations on the computer used for testing.
For
mor
e statistical data test visit [4
]
. These

metric
s

will be better at determining how much
time

i
t
9


will cost for
different JNI operations and on different Just
-
In
-
Time compilers. For most JNI
operations, it takes a couple hundred nanoseconds, so typically it is fairly negligible.




Figure 7: JNI execution time vs
.

pure Java execution time


JNI Applications


Now that we have investigated when
we should
l

use the JNI and understand how
some of the
problems that can occur
, we can begin to look into
where it would be applicable to use the Java
Native Interface in industry.

C and assembly languages dominate the ma
rket for small
embedded systems because of its efficiency and its ease to communicate with other languages.
As embedded projects become larger and more complex, it becomes more difficult to maintain
and to develop. Java has become more attractive for dev
eloping large embedded projects
because there
are

a large number of developers that are familiar with the language and Java’s
large library
makes

it easier to develop than C/C++.

With the help of the JNI, Java could be use
d
to write the “upper stream” [7
]

and have the C code control the devices.

With Java being
independent from the host platform, the Java software could be reused on different systems,
reducing the cost of
distributing

the software on
different machines.


The Android phones follow this exact
development scheme. Applications such as contacts, e
-
mail, browser settings, and all applications that come with the Android package are written in
Java. Other programs that manage resources,
such as
voice application
s are mostly written in
Java. The Android does use C/C++ for CPU tasks and to drive other peripherals.

Even Androids
Virtual Machine, Dalvik Virtual Machine, is written in C.


10



Design Conclusions


This paper has shown you when it is acceptable to use the

Java Native Interface. Once it has
been decided to use the JNI, you must try to avoid some of the issues related. Designing a good
system will also help prevent some of the issues that may happen such as memory leaks and
keep
the overhead of the JNI min
imal. Try to keep the control flow simple, code that goes back and
forth between the JVM and native code, is probably designed
incorrectly
. Every time you make
calls back and forth you are wasting time. Keep the native code to a minimum, by doing this y
ou
will prevent memory leaks that could occur, and your code will be less complex. Separate your
Java code from your native code.
Native code is platform dependent, by separating the Java
code from the native code; you will have a porting layer.

A poss
ible application for the JNI is
for large embedded projects. Keep the microcontrollers written in C and have the “upper
stream” such as applications within the embedded system written in Java. If you are interested in
learning more about embedded develo
pment using the JNI, I would suggest
starting with
Android development.



References


[1]
Sheng Liang (June 1999).
The Java Native Interface Programmer’s Guide and Specification
.

Retrieved from
http://java.sun.com/docs/books/jni/download/jni.pdf


[2] Gang Tan; Andrew W. Appel; Srimat Chakradhar; Anand Raghunathan; Srivaths Ravi;
Daniel Wang (2006).
Safe Java Native Interface
.



Retrieved from
http://www.cs.princeton.edu/~appel/papers/safejni.pdf


[3] Scott
Stricker(March 2002).
Java Programming with JNI
.

Retrieved from
http://www.ibm.com/developerworks/java/tutorials/j
-
jni/


[4] Dawid Kurzyniec;
Vaidy Sunderam.
Efficient Cooperation between Java and Native

Codes
-

JNI Performance Benchmark
.

Retrieved from
http://janet
-
project.sourceforge.net/papers/jnibench.pdf


[5] Demetrius L. Davis.
To JNI or not to JNI?

Retrieved from
http://www.ewp.rpi.edu/hartford/~rhb/cs_seminar_2004/SessionC3/davis.pdf


[6] Preetham Chandrian (August 2011).
Efficient Java Native Interface for Android based
Mobile Devices.

Retrieved from
http://re
pository.asu.edu/items/9315


[7] Nguyen Thi Thu Trang; Tran Canh Toan; Nguyen Manh Tuan; Takenobu Aoshima


(October 31, 2007).
An experience in developing embedded software using JNI
.

Retrieved from
js.vnu.edu.vn/tn_2_08/b7.pdf