Rakesh Vidyadharan Help

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 using JNIEnv::GetMethodID

  • Invoke static methods using JNIEnv::CallStaticMethod and instance methods using JNIEnv::CallMethod where result 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::CallNonvirtualMethod. Note that the jmethodID must be retrieved using the appropriate super-class definition.

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() ); } }

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() ); } }

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::sptci::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(); }

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

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.

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 lookups 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 as necessary following the model used by the JVM.

  • com::sptci::jnitest::Client::staticIntMethod - Invokes the static method defined in Base.java

  • com::sptci::jnitest::Client::intMethod - Invokes the instance method defined in Child.java

  • com::sptci::jnitest::Client::booleanMethod - Invokes the instance method defined in Base.java

  • com::sptci::jnitest::Client::stringMethod - Invokes the over-ridden method defined in Child.java

  • com::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.

Last modified: 30 April 2025