Jni Tutorial
Jni Tutorial
Table of Contents
If you're viewing this document online, you can click any of the topics below to link directly to that section.
This tutorial deals with the two most common applications of JNI: calling C/C++ code from
Java programs, and calling Java code from C/C++ programs. We'll cover both the essentials
of the Java Native Interface and some of the more advanced programming challenges that
can arise.
All the examples use Java, C, and C++ code, and are written to be portable to both Windows
and UNIX-based platforms. To follow the examples, you must have some experience
programming in the Java language. In addition, you will also need some experience
programming in C or C++. Strictly speaking, a JNI solution could be broken down between
Java programming tasks and C/C++ programming tasks, with separate programmers doing
each task. However, to fully understand how JNI works in both programming environments,
you'll need to be able to understand both the Java and C/C++ code.
We'll also cover a number of advanced topics, including exception handling and
multithreading with native methods. To get the most out of this part of the tutorial, you should
be familiar with the Java platform's security model and have some experience in
multithreaded application development.
The section on Advanced topics on page 19 is separate from the more basic step-by-step
introduction to JNI. Beginning Java programmers may benefit from taking the first two parts
of the tutorial now and returning to the advanced topics at a later time.
See Resources on page 25 for a listing of tutorials, articles, and other references that expand
upon the material presented here.
• Library files and native header files that define JNI. The jni.h C header file, jvm.lib, and
jvm.dll or jvm.so files all ship with the SDK.
• A C and C++ compiler that can create a shared library. The two most common C
compilers are Visual C++ for Windows and cc for UNIX-based systems.
Although you may use any development environment you like, the examples we'll work with
in this tutorial were written using the standard tools and components that ship with the SDK.
See Resources on page 25 to download the SDK, complete source files, and other tools
essential for the completion of this tutorial.
Scott has a Bachelor of Science degree in Computer Science from the University of
Cincinnati. He is a Sun Certified Java 2 Programmer and Developer. Scott may be reached
at [email protected].
• You have legacy code or code libraries that you want to access from Java programs.
• You need platform-dependent features not supported in the standard Java class library.
1. Write the Java code. We'll start by writing Java classes to perform three tasks: declare
the native method we'll be calling; load the shared library containing the native code; and
call the native method.
2. Compile the Java code. We must successfully compile the Java class or classes to
bytecode before we can use them.
3. Create the C/C++ header file. The C/C++ header file will declare the native function
signature that we want to call. This header will then be used with the C/C++ function
implementation (see Step 4) to create the shared library (see Step 5).
4. Write the C/C++ code. This step consists of implementing the function in a C or C++
source code file. The C/C++ source file must include the header file we created in Step 3.
5. Create the shared library file. We'll create a shared library file from the C source code
file we created in Step 4.
6. Run the Java program. We'll run the code and see if it works. We'll also go over some
tips for dealing with the more commonly occurring errors.
• On line 10 we load the shared library file containing the implementation for these native
methods. (We'll create the shared library file when we come to Step 5.)
• Finally, in lines 12 through 15 we call the native methods. Note that this operation is no
different from the operation of calling non-native Java methods.
javac Sample1.java
The third step is to create a C/C++ header file that defines native function signatures. One
way to do this is to use the native method C stub generator tool, javah.exe, which comes with
the SDK. This tool is designed to create a header file that defines C-style functions for each
native method it finds in a Java source code file. The command to use here is:
javah Sample1
The parameter lists of all these functions have a pointer to a JNIEnv and a jobject, in
addition to normal parameters in the Java declaration. The pointer to JNIEnv is in fact a
pointer to a table of function pointers. As we'll see in Step 4, these functions provide the
various faculties to manipulate Java data in C and C++.
The jobject parameter refers to the current object. Thus, if the C or C++ code needs to
refer back to the Java side, this jobject acts as a reference, or pointer, back to the calling
Java object. The function name itself is made by the "Java_" prefix, followed by the fully
qualified class name, followed by an underscore and the method name.
1. #include "Sample1.h"
2. #include <string.h>
3.
4. JNIEXPORT jint JNICALL Java_Sample1_intMethod
5. (JNIEnv *env, jobject obj, jint num) {
6. return num * num;
7. }
8.
9. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
10. (JNIEnv *env, jobject obj, jboolean boolean) {
11. return !boolean;
12. }
13.
14. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
15. (JNIEnv *env, jobject obj, jstring string) {
16. const char *str = (*env)->GetStringUTFChars(env, string, 0);
17. char cap[128];
18. strcpy(cap, str);
19. (*env)->ReleaseStringUTFChars(env, string, str);
20. return (*env)->NewStringUTF(env, strupr(cap));
21. }
22.
23. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
24. (JNIEnv *env, jobject obj, jintArray array) {
25. int i, sum = 0;
26. jsize len = (*env)->GetArrayLength(env, array);
27. jint *body = (*env)->GetIntArrayElements(env, array, 0);
28. for (i=0; i<len; i++)
29. { sum += body[i];
30. }
31. (*env)->ReleaseIntArrayElements(env, array, body, 0);
32. return sum;
33. }
34.
35. void main(){}
1. #include "Sample1.h"
2. #include <string.h>
3.
4.JNIEXPORT jint JNICALL Java_Sample1_intMethod
5. (JNIEnv *env, jobject obj, jint num) {
6. return num * num;
7. }
8.
9. JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
10. (JNIEnv *env, jobject obj, jboolean boolean) {
11. return !boolean;
12. }
13.
14. JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
15. (JNIEnv *env, jobject obj, jstring string) {
16. const char *str = env->GetStringUTFChars(string, 0);
17. char cap[128];
18. strcpy(cap, str);
19. env->ReleaseStringUTFChars(string, str);
20. return env->NewStringUTF(strupr(cap));
21. }
22.
23. JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
24. (JNIEnv *env, jobject obj, jintArray array) {
25. int i, sum = 0;
26. jsize len = env->GetArrayLength(array);
27. jint *body = env->GetIntArrayElements(array, 0);
28. for (i=0; i<len; i++)
29. { sum += body[i];
30. }
31. env->ReleaseIntArrayElements(array, body, 0);
32. return sum;
33. }
34.
35. void main(){}
java Sample1
When we run the Sample1.class program, we should get the following result:
PROMPT>java Sample1
intMethod: 25
booleanMethod: false
stringMethod: JAVA
intArrayMethod: 33
PROMPT>
Troubleshooting
You can run into many problems when using JNI to access native code from Java programs.
The three most common errors you'll encounter are:
• The shared library file cannot be found. When you load the library file using the file
name with the System.loadLibrary(String libname) method, make sure that the
file name is spelled correctly and that you do not specify the extension. Also, make sure
that the library file is accessible to the JVM by ensuring that the library file's location is in
the classpath.
• A method with the specified signature cannot be found. Make sure that your C/C++
function implementation has a signature that is identical to the function signature in the
header file.
Conclusion
Calling C or C++ native code from Java, while not trivial, is a well-integrated function in the
Java platform. Although JNI supports both C and C++, the C++ interface is somewhat
cleaner and is generally preferred over the C interface.
As you have seen, calling C or C++ native code requires that you give your functions special
names and create a shared library file. When taking advantage of existing code libraries, it is
generally not advisable to change the code. To avoid this, it is common to create proxy code,
or a proxy class in the case of C++, that has the specially named functions required by JNI.
These functions, then, can call the underlying library functions, whose signatures and
implementations remain unchanged.
• You want to implement platform-independent portions of code for functionality that will be
used across multiple platforms.
• You have code or code libraries written in the Java language that you need to access in
native applications.
• You want to take advantage of the standard Java class library from native code.
1. Write the Java code. This step consists of writing the Java class or classes that
implement (or call other methods that implement) the functionality you want to access.
2. Compile the Java code. The Java class or classes must be successfully compiled to
bytecode before they can be used.
3. Write the C/C++ code. This code will create and instantiate a JVM and call the correct
Java methods.
4. Run the native C/C++ application. We'll run the application to see if it works. We'll also
go over some tips for dealing with common errors.
9. }
10. }
Note that Sample2.java implements two static Java methods, intMethod(int n) and
booleanMethod(boolean bool) (lines 3 and 7 respectively). static methods are class
methods that are not associated with object instances. It is easier to call static methods
because we do not have to instantiate an object to invoke them.
javac Sample1.java
We'll start with a look at the complete code for both the C and C++ applications, then
compare the two.
1. #include <jni.h>
2.
3. #ifdef _WIN32
4. #define PATH_SEPARATOR ';'
5. #else
6. #define PATH_SEPARATOR ':'
7. #endif
8.
9. int main()
10. {
11. JavaVMOption options[1];
12. JNIEnv *env;
13. JavaVM *jvm;
14. JavaVMInitArgs vm_args;
15. long status;
16. jclass cls;
17. jmethodID mid;
18. jint square;
19. jboolean not;
20.
21. options[0].optionString = "-Djava.class.path=.";
22. memset(&vm_args, 0, sizeof(vm_args));
23. vm_args.version = JNI_VERSION_1_2;
24. vm_args.nOptions = 1;
25. vm_args.options = options;
26. status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
27.
28. if (status != JNI_ERR)
29. {
30. cls = (*env)->FindClass(env, "Sample2");
31. if(cls !=0)
32. { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
33. if(mid !=0)
34. { square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
35. printf("Result of intMethod: %d\n", square);
36. }
37.
38. mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z");
39. if(mid !=0)
40. { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1);
41. printf("Result of booleanMethod: %d\n", not);
42. }
43. }
44.
45. (*jvm)->DestroyJavaVM(jvm);
46. return 0;
47/ }
48. else
49. return -1;
50. }
1. #include <jni.h>
2.
3. #ifdef _WIN32
4. #define PATH_SEPARATOR ';'
5. #else
6. #define PATH_SEPARATOR ':'
7. #endif
8.
9. int main()
10. {
11. JavaVMOption options[1];
12. JNIEnv *env;
13. JavaVM *jvm;
14. JavaVMInitArgs vm_args;
15. long status;
16. jclass cls;
17. jmethodID mid;
18. jint square;
19. jboolean not;
20.
21. options[0].optionString = "-Djava.class.path=.";
22. memset(&vm_args, 0, sizeof(vm_args));
23. vm_args.version = JNI_VERSION_1_2;
24. vm_args.nOptions = 1;
25. vm_args.options = options;
26. status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
27.
28. if (status != JNI_ERR)
29. {
30. cls = (*env)->FindClass(env, "Sample2");
31. if(cls !=0)
32. { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
33. if(mid !=0)
34. { square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
35. printf("Result of intMethod: %d\n", square);
36. }
37.
38. mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z");
39. if(mid !=0)
40. { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1);
41. printf("Result of booleanMethod: %d\n", not);
42. }
43. }
44.
45. (*jvm)->DestroyJavaVM(jvm);
46. return 0;
47. }
48. else
49. return -1;
50. }
#include <jni.h>
The jni.h file contains all the type and function definitions we need for JNI on the C side.
JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
Notes:
• JavaVM jvm is a pointer to the JVM. We use this primarily to create, initialize, and destroy
the JVM.
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;
options[0].optionString = "-Djava.class.path=.";
// same text as command-line options for the java.exe JVM
This method returns zero if successful or JNI_ERR if the JVM could not be created.
The cls variable stores the result of the FindClass() function. If the class is found, the
cls variable represents a handle to the Java class. If the class cannot be found, cls will be
zero.
The mid variable stores the result of the GetStaticMethodID() function. If the method is
found, the mid variable represents a handle to the method. If the method cannot be found,
mid will be zero.
Remember that in this example, we are calling static Java methods. That is why we're
using the GetStaticMethodID() function. The GetMethodID() function does the same
thing, but it is used to find instance methods.
If we were calling a constructor, the name of the method would have been "<init>". To learn
more about calling a constructor, see Error handling on page 21 . To learn more about the
code used to specify parameter types and about how JNI types map to the Java primitive
types, see Appendices on page 28 .
You will also run across methods of the types CallStaticXXXMethod() and
CallXXXMethod(). These call static methods and member methods, respectively,
replacing the variable (XXX) with the return type for the method (for example, Object,
Boolean, Byte, Char, Int, Long, and so on).
PROMPT>Sample2
Result of intMethod: 25
Result of booleanMethod: 0
PROMPT>
Troubleshooting
JNI's Invocation API is somewhat cumbersome because it is defined in C, a language with
minimal object-oriented programming support. As a result, it is easy to run into problems.
Below is a checklist that may help you avoid some of the more common errors.
• Always ensure that references are properly set. For example, when creating a JVM with
the JNI_CreateJavaVM() method, make sure it returns a zero. Also make sure that
references set with the FindClass() and GetMethodID() methods are not zero before
you use them.
• Check to see that your method names are spelled correctly and that you properly mangled
the method signature. Also be sure that you use CallStaticXXXMethod() for static
methods and CallXXXMethod() for member methods.
• Make sure you initialize the JVM with any special arguments or options your Java class
may need. For example, if your Java class requires a great deal of memory, you may need
to increase the maximum heap size option.
• Always be sure to set the classpath properly. A native application using an embedded JVM
must be able to find the jvm.dll or jvm.so shared library.
Conclusion
Calling Java methods from C is relatively straightforward for experienced C programmers,
although it does require fairly advanced quasi-object-oriented programming techniques.
Although JNI supports both C and C++, the C++ interface is slightly cleaner and is generally
preferred over the C interface.
One important point to remember is that a single JVM can be used to load and execute
multiple classes and methods. Creating and destroying a JVM every time you interact with
Java from native code can waste resources and decrease performance.
Calling Java code from within a native program is also complicated. Because the Java
language is object-oriented, calling Java code from a native application typically involves
object-oriented techniques. In native languages that have no support or limited support for
object-oriented programming, such as C, calling Java methods can be problematic. In this
section, we'll explore some of the complexities that arise when working with JNI, and look at
ways to work around them.
Next, we'll look at the code to convert Java strings to C strings. Note the call to the
ReleaseStringUTFChars() function on line 5. You should use this function to release
Java strings when you're no longer using them. Be sure you always release your strings
when the native code no longer needs to reference them. Failure to do so could cause a
memory leak.
The C type jarray represents a generic array. In C, all of the array types are really just type
synonyms of jobject. In C++, however, all of the array types inherit from jarray, which in
turn inherits from jobject. See Appendix A: JNI types on page 28 for an inheritance diagram
of all the C type objects.
Next, you'll want to obtain a pointer to the array's elements. You can access elements in an
array using the GetXXXArrayElement() and SetXXXArrayElement() functions
(replace the XXX in the method name according to the type of the array: Object, Boolean,
Byte, Char, Int, Long, and so on).
When the native code is finished using a Java array, it must release it with a call to the
function ReleaseXXXArrayElements(). Otherwise, a memory leak may result. The code
snippet below shows how to loop through an array of integers and up all the elements:
This code is not valid because of line 10. mid is a methodID and GetStaticMethodID()
returns a methodID. The methodID returned is a local reference, however, and you should
not assign a local reference to a global reference. And mid is a global reference.
Error handling
Using native methods in Java programs breaks the Java security model in some fundamental
ways. Because Java programs run in a controlled runtime system (the JVM), the designers of
the Java platform decided to help the programmer by checking common runtime errors like
array indices, out-of-bounds errors, and null pointer errors. C and C++, on the other hand,
use no such runtime error checking, so native method programmers must handle all error
conditions that would otherwise be caught in the JVM at runtime.
For example, it is common and correct practice in Java programs to report errors to the JVM
by throwing an exception. C has no exceptions, so instead you must use the exception
handling functions of JNI.
The Throw() and ThrowNew() functions do not interrupt the flow of control in the native
method. The exception will not actually be thrown in the JVM until the native method returns.
In C you cannot use the Throw() and ThrowNew() functions to immediately exit a method
on error conditions, as you can in Java programs by using the throw statement. Instead, you
need to use a return statement right after the Throw() and ThrowNew() functions to exit
the native method at a point of error.
If you're catching exceptions, you may be handling exceptions, in which case you need to
clear out the exception in the JVM. You can do this using the ExceptionClear() function.
The ExceptionDescribed() function is used to display a debugging message for an
exception.
running on platforms that don't necessarily support multithreading; so the onus is on you to
ensure that your native functions are thread safe.
Using the synchronized keyword will ensure that whenever the native method is called
from a Java program, it will be synchronized. Although it is a good idea to mark
thread-safe native methods with the synchronized keyword, it is generally best to always
implement synchronization in the native method implementation. The primary reasons for this
are as follows:
• The C or C++ code is distinct from the Java native method declaration, so if the method
declaration changes (that is, if the synchronized keyword is ever removed) the method
may suddenly no longer be thread safe.
• If anyone ever codes other native methods (or other C or C++ functions) that use the
function, they may not be aware that native implementation isn't thread safe.
• If the function is used outside of a Java program as a normal C function it will not be thread
safe.
Using JNI almost always breaks the portability of your Java code. When calling native
methods from Java programs, you will need to distribute native shared library files for every
platform on which you intend to run your program. On the other hand, calling Java code from
native programs can actually improve the portability of your application.
Resources
Downloads
• If you're a Windows user, you'll likely use Visual C++ to compile your C/C++ code.
• If you're a UNIX user, you'll likely use cc to compile your C/C++ code. Of course, GCC is
an equally viable, open-source option.
• IBM's VisualAge for Java is a complete Java development package, including a C/C++
compiler.
• Further explore your options with developerWorks' listing of IBM developer kits for Java
technology.
• To learn more about the differences between programming in C/C++ and programming in
the Java language -- from a C/C++ programmer's perspective -- see the tutorial, "
Introduction to Java for C/C++ programmers" (developerWorks, April 1999)
• Learn more about the Java Native Interface, including enhancements to the JNI in the
Java 2 SDK.
• You'll find hundreds of articles about every aspect of Java programming in the IBM
developerWorks Java technology zone.
Recommended books
• To learn more about programming in C++, start with Bjarne Stroustrup's The C++
Programming Language, Third Edition (Addison-Wesley, 1996).
• Another good reference is Kris Jamsa and Lars Klander's Jamsa's C/C++ Programmer's
Bible (Jamsa Press, 1998).
• Ken Arnold and James Gosling wrote The Java Programming Language: Third Edition
(Addison-Wesley, 2000).
• Learn more about the Java Native Interface with Sheng Liang's Java Native Interface:
Programmer's Guide and Specification (Sun Microsystems Press, 1999).
• Also see Rob Gordon's Essential JNI: Java Native Interface (Prentice Hall, 1998).
• David Flanagan's Java in a Nutshell, Third Edition is essential reading for any Java
programmer (O'Reilly, 1999).
• Also see volumes I and II of the Core Java 2 series by Cay S. Horstmann and Gary Cornell
(Sun Microsystems Press, 2000).
• The Java 2 Developer's Handbook by Philip Heller and Simon Roberts is an excellent
resource (Sybex, 1999).
• To learn more about the Java platform's security model, see Scott Oaks's Java Security,
Second Edition (O'Reilly, 2001).
• For an in-depth look at Java data structures and algorithms, see Robert Lafore's Data
Structures & Algorithms in Java (Waite Group Press, 1998).
Feedback
Please send us your feedback on this tutorial. We look forward to hearing from you!
Section 6. Appendices
Appendix A: JNI types
JNI uses several natively defined C types that map to Java types. These types can be
divided into two categories: primitive types and pseudo-classes. The pseudo-classes are
implemented as structures in C, but they are real classes in C++.
The Java primitive types map directly to C platform-dependent types, as shown here:
The C type jarray represents a generic array. In C, all of the array types are really just type
synonyms of jobject. In C++, however, all of the array types inherit from jarray, which in
turn inherits from jobject. The following table shows how the Java array types map to JNI
C array types.
Here is an object tree that shows how the JNI pseudo-classes are related.
Notes:
• The semicolon at the end of the class type L expression is the terminator of the type
expression, not a separator between expressions.
• You must use a forward slash (/) instead of a dot (.) to separate the package and class
name. To specify an array type use an open bracket ([). For example, the Java method:
([Ljava/lang/Sting;I)Z
Colophon
This tutorial was written entirely in XML, using the developerWorks Toot-O-Matic tutorial
generator. The open source Toot-O-Matic tool is an XSLT stylesheet and several XSLT
extension functions that convert an XML file into a number of HTML pages, a zip file, JPEG
heading graphics, and two PDF files. Our ability to generate multiple text and binary formats
from a single source file illustrates the power and flexibility of XML. (It also saves our
production team a great deal of time and effort.)