JNI Tutorial
Invoke Java API from C++
A very simple tutorial that illustrates accessing Java API from C++. We use a simple Java class hierarchy that exposes a static method as well as regular methods, and then develop a sample C++ client that instantiates the Java class and invokes the methods.
The workflow for accessing Java API is as follows:
- Create a JVM instance and specify the classpath as appropriate using
JNI_CreateJavaVM
- Look up the Java Class definition using
JNIEnv::FindClass
- Retrieve the constructor for the class using
JNIEnv::GetMethodID
- Instantiate a new instance of the class using
JNIEnv::NewObject
- Retrieve static methods using
JNIEnv::GetStaticMethodID
and instance methods usingJNIEnv::GetMethodID
- Invoke static methods using
JNIEnv::CallStatic
and instance methods usingMethod JNIEnv::Call
whereMethod
will depend upon the return type for the method (eg. Int, Boolean, Object, ...) - You can invoke a super class variant of an instance method using
JNIEnv::CallNonvirtual
. Note that theMethod jmethodID
must be retrieved using the appropriate super-class definition.
Base Class
A simple base java class that exposes static and regular methods. We will be accessing instances of this class and its methods from our C++ client.
File: src/java/Base.java
package com.sptci.jnitest; import static java.lang.String.format; public class Base { public static int staticIntMethod( int n ) { return n*n; } public boolean booleanMethod( boolean bool ) { return bool; } public String stringMethod() { return format( "From %s.stringMethod", Base.class.getName() ); } }
Child Class
A simple child java class that regular methods as well as an over-ridden method. We will be accessing instances of this class and its methods from our C++ client.
File: src/java/Child.java
package com.sptci.jnitest; import static java.lang.String.format; public class Child extends Base { public int intMethod( int n ) { return n*n; } @Override public String stringMethod() { return format( "From %s.stringMethod", Child.class.getName() ); } }
C++ Client
A simple C++ client that starts a JVM, looks up our sample Java class, instantiates an instance and invokes the methods defined.
File: src/cpp/Client.cpp
#include <jni.h> #include <iostream> #include <string> namespace com { namespace sptci { namespace jnitest { class Client { public: ~Client() { if ( env && obj ) env->DeleteGlobalRef( obj ); if ( env && child ) env->DeleteGlobalRef( child ); if ( jvm ) { std::cout << "DTOR - Destroying JVM" << std::endl; jvm->DestroyJavaVM(); } } void run() { long vmstatus = startJVM(); if ( vmstatus == JNI_ERR ) { status = 1; return; } init(); staticIntMethod(); intMethod(); booleanMethod(); stringMethod(); nonVirtualStringMethod(); } int getStatus() { return status; } private: long startJVM() { JavaVMOption options[1]; char str[] = "-Djava.class.path=."; options[0].optionString = str; JavaVMInitArgs vm_args; memset( &vm_args, 0, sizeof( vm_args ) ); vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = options; return JNI_CreateJavaVM( &jvm, reinterpret_cast<void**>( &env ), &vm_args ); } void init() { child = env->FindClass( "com/sptci/jnitest/Child" ); if ( ! child ) { std::cerr << "init - Cannot find Child" << std::endl; status = 3; return; } child = static_cast<jclass>( env->NewGlobalRef( child ) ); std::cout << "init - Looking up CTOR for Child" << std::endl; jmethodID mid = env->GetMethodID( child, "<init>", "()V" ); if ( mid ) { std::cout << "init - Creating new Child instance" << std::endl; obj = env->NewObject( child, mid ); obj = env->NewGlobalRef( obj ); } else { std::cerr << "Unable to find Child CTOR" << std::endl; status = 2; } } void staticIntMethod() { if ( ! child ) return; jmethodID mid = env->GetStaticMethodID( child, "staticIntMethod", "(I)I" ); if ( mid ) { jint square = env->CallStaticIntMethod( child, mid, 7 ); std::cout << "staticIntMethod - Result: " << square << std::endl; } } void intMethod() { if ( ! obj ) return; jmethodID mid = env->GetMethodID( child, "intMethod", "(I)I" ); if ( mid ) { jint square = env->CallIntMethod( obj, mid, 5 ); std::cout << "intMethod - Result: " << square << std::endl; } } void booleanMethod() { if ( ! obj ) return; jmethodID mid = env->GetMethodID( child, "booleanMethod", "(Z)Z" ); if ( mid ) { jboolean boolean = env->CallBooleanMethod( obj, mid, 1 ); std::cout << "booleanMethod - Result: " << ( boolean ? JNI_TRUE : JNI_FALSE ) << std::endl; } } void stringMethod() { if ( ! obj ) return; jmethodID mid = env->GetMethodID( child, "stringMethod", "()Ljava/lang/String;" ); if ( mid ) { jstring str = static_cast<jstring>( env->CallObjectMethod( obj, mid ) ); displayString( "stringMethod", str ); } } void nonVirtualStringMethod() { jclass base = env->FindClass( "com/sptci/jnitest/Base" ); if ( ! base || ! obj ) return; jmethodID mid = env->GetMethodID( base, "stringMethod", "()Ljava/lang/String;" ); if ( mid ) { jstring str = static_cast<jstring>( env->CallNonvirtualObjectMethod( obj, base, mid ) ); displayString( "nonVirtualStringMethod", str ); } env->DeleteLocalRef( base ); } void displayString( std::string method, jstring str ) { const char* cstr = env->GetStringUTFChars( str, nullptr ); std::cout << method << " - Result: " << cstr << std::endl; env->ReleaseStringUTFChars( str, cstr ); env->DeleteLocalRef( str ); } private: JavaVM* jvm; JNIEnv* env; jclass child; jobject obj; int status = 0; }; }; }; }; int main() { com::sptci::jnitest::Client client; client.run(); return client.getStatus(); }
Build and Run
A simple shell script used to build and run the samples. Note that we use javap to dump the method signatures for use in the C++ code.
#!/bin/ksh
javac -d build src/java/*.java
javap -cp build -s -p com.sptci.jnitest.Base
javap -cp build -s -p com.sptci.jnitest.Child
clang++ -std=c++11 \
-I$JAVA_HOME/include -I$JAVA_HOME/include/darwin \
-L$JAVA_HOME/jre/lib/server -ljvm \
-rpath $JAVA_HOME/jre/lib/server \
-o build/Client src/cpp/Client.cpp
if [ $? -eq 0 ]
then
(cd build; ./Client; echo "Program Result code: $?")
else
echo "Build failed"
fi
A sample run should produce output similar to the following:
init - Looking up CTOR for Child
init - Creating new Child instance
staticIntMethod - Result: 49
intMethod - Result: 25
booleanMethod - Result: 1
stringMethod - Result: From com.sptci.jnitest.Child.stringMethod
nonVirtualStringMethod - Result: From com.sptci.jnitest.Base.stringMethod
DTOR - Destroying JVM
Program Result code: 0
Code Walk Through
Java developers familiar with the Reflection API will have noticed the similarities between using Reflection to look up a class, use constructor to instantiate an instance etc. and the JNI API. They are indeed similar and follow a similar programming model.
The first step is to start a JVM instance with any parameters needed to properly initialise the JVM (system properties, class path, ...). For efficiency C++ client applications will generally use a single JVM instance (encapsulated suitably using RAII) and use it to load the Java API necessary. The process is illustrated in the
The next step usually is to load a class available in the classpath using
Once you have a
You can instantiate an object using the
Static methods for a class are retrieved using
Notes:
In our sample Client class, we promote the
Start JVM
The first step is to start a JVM instance with any parameters needed to properly initialise the JVM (system properties, class path, ...). For efficiency C++ client applications will generally use a single JVM instance (encapsulated suitably using RAII) and use it to load the Java API necessary. The process is illustrated in the
startJVM
method of the Client class.Load Class and Instantiate Object
The next step usually is to load a class available in the classpath using
JNIEnv::FindClass
. This step is equivalent to the java.lang.Class.forName
static method. These steps are illustrated in the init
method of the Client class.Once you have a
jclass
instance, you can invoke any static methods that are defined for that class without need to instantiate an object instance. You can look up constructors using the same technique as used to look up instance methods in the class. Constructors are always identified by the name <init>
. This is slightly different from Reflection which separates constructor look ups from method lookups.You can instantiate an object using the
JNIEnv::NewObject
function, which is the equivalent of java.lang.reflect.Constructor.newInstance
method.Invoke Methods
Static methods for a class are retrieved using
JNIEnv::GetStaticMethodID
, while instance methods are retrieved using JNIEnv::GetMethodID
. Once you have a valid jmethodID
you invoke the method using JNIEnv::CallStatic<Type>Method
or JNIEnv::Call<Type>Method
depending upon whether the method is static or not.Notes:
In our sample Client class, we promote the
jclass
and jobject
instances to global references to avoid the instances being garbage collected before we are finished using them. Following RAII principles we release the references to these instances in the destructor. JNI client applications will in general keep a single global reference to Java class definitions and instantiate objects are necessary following the model used by the JVM.com::sptci::jnitest::Client::staticIntMethod
- Invokes the static method defined in Base.javacom::sptci::jnitest::Client::intMethod
- Invokes the instance method defined in Child.javacom::sptci::jnitest::Client::booleanMethod
- Invokes the instance method defined in Base.javacom::sptci::jnitest::Client::stringMethod
- Invokes the over-ridden method defined in Child.javacom::sptci::jnitest::Client::nonVirtualStringMethod
- Invokes the base class variant of stringMethod defined in Base.java
The code was built and tested on Mac OS X Mavericks. The shell script shown uses path's used on OS X, and will need to be modified accordingly for other platforms.