The document discusses using the Java Native Interface (JNI) to call Java code from C++. It outlines the four main steps: 1) write the Java code, 2) compile the Java code, 3) write the C++ code to create a JVM, lookup and call the Java methods, and 4) run the native C++ application. Key aspects covered include using JNI data types, signatures for classes and methods, converting between Java and C strings, handling references and errors. Performance tips are also provided such as caching lookups and managing the JVM as a resource.
2. What is JNI ?
The Java Native Interface (JNI) is a native programming interface
that is part of the Java Software Development Kit (SDK).
Through the use of JNI, Java code can use C++ code, C++ code
can call Java code, and a variety of other languages too.
This tutorial deals with the most UNcommon applications of JNI:
calling Java code from C/C++ programs.
3. Why use JNI ?
JNI allows you to invoke Java class methods from within native code. Often, to
do this, you must create and initialize a JVM within the native code using the
Invocation API. The following are typical situations where you might decide to
call Java code from C/C++ code:
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 Java that you need to access in
native applications.
You want to take advantage of the standard Java class library from native
code.
4. Four Steps to Call Java from C++
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.
Compile the Java code. The Java class or classes must be successfully
compiled to bytecode before they can be used.
Write the C/C++ code. This code will create and instantiate a JVM and call
the correct Java methods.
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.
5. Step 1: Write the Java code
We start by writing the Java source code file, which will implement the
functionality we want to make available to the native C/C++ code.
public class Sample2
{
public static int intMethod(int n) {
return n*n;
}
public static boolean booleanMethod(boolean bool) {
return !bool;
}
}
Sample2.java implements two static Java methods; intMethod(int n) and
booleanMethod(boolean bool).
My example shows static methods because is easier to call them. We do not
have to instantiate an object over JNI to invoke them (that can be done, but it’s
more complex).
6. Step 2: Compile the Java code
Next, compile the Java
Make a JAR.
Place the JAR in your classpath **
** ( or cheat and put in ../Java/jre1.xx/lib/ext )
7. Step 3: Write the C/C++ code
All Java bytecode must be executed in a JVM, even when running in a native
application. So our C/C++ application must do the following;
1. Create a JVM
2. Lookup the Java class
3. Lookup the Java class method
4. Call the method
5. Release manually created Java resources (if any)
6. Trap exceptions, release them
We'll start with a look at the complete code for C++ applications.
8. #include <jni.h>
int main() {
JavaVMOption options[1];
JNIEnv *m_env;
JavaVM *m_java;
JavaVMInitArgs vm_args;
//constructor to create the JVM connection
JavaVMInitArgs vm_args;
JavaVMOption options[1];
long jvmStatus;
//================== prepare loading of Java VM ============================
options[0].optionString = "-Djava.class.path=. -Xms=50M -Xmx100M";
vm_args.version = JNI_VERSION_1_8; // minimum Java version
vm_args.nOptions = 1; // number of options
vm_args.options = options;
//=============== load and initialize Java VM and JNI interface =============
jvmStatus = JNI_CreateJavaVM(&m_java, (void**) &m_env, &vm_args);
Declare JVM variablesDeclare JVM variables
Set JVM optionsSet JVM options
Create the JVMCreate the JVM
9. if (status != JNI_ERR) {
jclass Sample2 = m_parser_env->FindClass("Sample2");
if (Sample2 != 0) {
jmethodID intID = m_env->GetStaticMethodID(Sample2, "intMethod", "(I)I");
jmethodID midID = m_env->GetStaticMethodID(Sample2, "booleanMethod", "(Z)Z");
if (intID != 0) {
int square = m_env->CallStaticIntMethod(Sample2, intID, 8);
cout<<"Result of intMethod: "<< square << endl;
}
if (midID != 0) {
bool not = m_env->CallStaticBooleanMethod( Sample2, midID, true);
cout<<"Result of booleanMethod: "<< not << endl;
}
}
}
m_env->DestroyJavaVM(m_java);
return 0;
} else
return -1;
}
Test for connectivityTest for connectivity
Lookup the Java classLookup the Java class
Lookup the class
method signatures
Lookup the class
method signatures
Call the methodCall the method
10. Native types supported by JNI
Native Java method parameter types are rendered, or mangled, into native code using
the encoding specified in the table below.
Java Type Code
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
class Lclassname;
11. Java class and method signatures
When looking up Java classes and methods over JNI (prior to execution), the
signature needs to match.
TIP: use javap –s CLASSNAME.class to lookup your class signatures.
C:...parser>javap -s MYObject.class
Compiled from "MYObject.java"
public abstract class xxx.yyy.zzz.MYObject {
public static java.lang.String VERSION;
Signature: Ljava/lang/String;
public int getM_iVersion();
Signature: ()I
public void setM_iVersion(int);
Signature: (I)V
public boolean isM_bBroadcast();
Signature: ()Z
public void setM_bBroadcast(boolean);
Signature: (Z)V
public int getPrecBitcode(java.lang.String);
Signature: (Ljava/lang/String;)I
12. Java strings versus C strings
Java strings are stored as sequences of 16-bit Unicode characters,
while C strings are stored as sequences of 8-bit null-terminated
characters. JNI provides several useful functions for converting
between and manipulating Java strings and C strings. The code
snippet below demonstrates how to convert C strings to Java strings,
and back.
/* convert a C string to a Java String */
char[] str = "To be or not to be.n";
jstring jstr = m_env- >NewStringUTF( str);
/* convert a Java String to a C string */
const char* msg_str = m_env- >GetStringUTFChars(jstr , 0);
13. Local versus Global references
When programming with JNI, you will be required to use references to Java
objects. By default JNI creates local references to ensure that they are liable for
garbage collection.
//populate the MYObject, saving the jMYObject to use again
object jMYObject = popMyObject(theMsgBytes);
object jGlobalMYObject = jni_env->NewGlobalRef(jMYObject );
…
//release JNI objects for garbage collection
m_parser_env->DeleteLocalRef(jMYObject);
m_parser_env->DeleteGlobalRef(jGlobalMYObject);
Use Global References to prevent objects that you reuse from being garbage
collected.
* Be sure that you delete all your Global References, otherwise your Java objects will
never be free. Delete your Local References to force garbage collection of them.
14. Error handling
Using native methods in Java programs breaks the Java security model.
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.
After you make each JNI call, test for Java exceptions. Exceptions need to be
cleared, otherwise the JVM can bind up and your C++ application WILL
segfault.
//file bytes
const vector<byte>* bytesIn;
//convert the passed vector of bytes to jbyteArray
jbyteArray tmpjMsgBytes;
tmpjMsgBytes = m_env->NewByteArray(bytesIn->size());
if (m_env->ExceptionCheck())
throw JNI_ERROR;
m_env->SetByteArrayRegion(tmpjMsgBytes, 0, bytesIn->size(), (signed char*) &bytesIn->front());
if (m_env->ExceptionCheck())
throw JNI_ERROR;
15. Error handling 2
The JNI methods that detect exceptions, can return JThrowable objects. Use
this to lookup your Java exceptions and stack traces.
try {
//Do Stuff
tmpjMsgBytes = m_env->NewByteArray(bytesIn->size());
if (m_env->ExceptionCheck())
throw JNI_ERROR;
} Catch JNI_ERROR {
//JNI call to grab the exception object
jthrowable theException = m_env->ExceptionOccurred();
//clear the exception, if not the JVM processing halts
m_env->ExceptionClear();
//call method to query over JNI for the exception
string the_error = m_fetchJavaExceptionMessage( theException);
}
16. JNI Performance Tips
Calling Java methods from C++ over JNI adds about 5-10ns per call.
Querying for java classes and methods over JNI is expensive. Look them up
once, and put them in a map to use over and over again.
Creating and destroying a JVM every time you interact with Java from native
code can waste resources and decrease performance.
Your C++ application can only create one JVM. Manage it as any other
resource with RAII.
The JVM is tunable even from unmanaged code. Most Java arguments can
be passed at creation time such as; Xms, Xmx, -Djava.class.path etc.
Threading; JNI provides methods to synchronize blocks of code between C+
+ and Java.