Sans Pareil Technologies, Inc.

Key To Your Business

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 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.

Base Java 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 Java 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

    1: #include <jni.h>
    2: #include <iostream>
    3: #include <string>
    4:
    5: namespace com
    6: {
    7:   namespace sptci
    8:   {
    9:     namespace jnitest
   10:     {
   11:       class Client
   12:       {
   13:       public:
   14:         ~Client()
   15:         {
   16:           if ( env && obj ) env->DeleteGlobalRef( obj );
   17:           if ( env && child ) env->DeleteGlobalRef( child );
   18:
   19:           if ( jvm )
   20:           {
   21:             std::cout << "DTOR - Destroying JVM" << std::endl;
   22:             jvm->DestroyJavaVM();
   23:           }
   24:         }
   25:
   26:         void run()
   27:         {
   28:           long vmstatus = startJVM();
   29:
   30:           if ( vmstatus == JNI_ERR )
   31:           {
   32:             status = 1;
   33:             return;
   34:           }
   35:
   36:           init();
   37:           staticIntMethod();
   38:           intMethod();
   39:           booleanMethod();
   40:           stringMethod();
   41:           nonVirtualStringMethod();
   42:         }
   43:
   44:         int getStatus() { return status; }
   45:
   46:       private:
   47:         long startJVM()
   48:         {
   49:           JavaVMOption options[1];
   50:           char str[] = "-Djava.class.path=.";
   51:           options[0].optionString = str;
   52:
   53:           JavaVMInitArgs vm_args;
   54:           memset( &vm_args, 0, sizeof( vm_args ) );
   55:           vm_args.version = JNI_VERSION_1_6;
   56:           vm_args.nOptions = 1;
   57:           vm_args.options = options;
   58:
   59:           return JNI_CreateJavaVM( &jvm, reinterpret_cast<void**>( &env ), &vm_args );
   60:         }
   61:
   62:         void init()
   63:         {
   64:           child = env->FindClass( "com/sptci/jnitest/Child" );
   65:          
   66:           if ( ! child )
   67:           {
   68:             std::cerr << "init - Cannot find Child" << std::endl;
   69:             status = 3;
   70:             return;
   71:           }
   72:           child = static_cast<jclass>( env->NewGlobalRef( child ) );
   73:
   74:           std::cout << "init - Looking up CTOR for Child" << std::endl;
   75:           jmethodID mid = env->GetMethodID( child, "<init>", "()V" );
   76:
   77:           if ( mid )
   78:           {
   79:             std::cout << "init - Creating new Child instance" << std::endl;
   80:             obj = env->NewObject( child, mid );
   81:             obj = env->NewGlobalRef( obj );
   82:           }
   83:           else
   84:           {
   85:             std::cerr << "Unable to find Child CTOR" << std::endl;
   86:             status = 2;
   87:           }
   88:         }
   89:
   90:         void staticIntMethod()
   91:         {
   92:           if ( ! child ) return;
   93:           jmethodID mid = env->GetStaticMethodID( child, "staticIntMethod", "(I)I" );
   94:
   95:           if ( mid )
   96:           {
   97:             jint square = env->CallStaticIntMethod( child, mid, 7 );
   98:             std::cout << "staticIntMethod - Result: " << square << std::endl;
   99:           }
  100:         }
  101:
  102:         void intMethod()
  103:         {
  104:           if ( ! obj ) return;
  105:           jmethodID mid = env->GetMethodID( child, "intMethod", "(I)I" );
  106:
  107:           if ( mid )
  108:           {
  109:             jint square = env->CallIntMethod( obj, mid, 5 );
  110:             std::cout << "intMethod - Result: " << square << std::endl;
  111:           }
  112:         }
  113:
  114:         void booleanMethod()
  115:         {
  116:           if ( ! obj ) return;
  117:           jmethodID mid = env->GetMethodID( child, "booleanMethod", "(Z)Z" );
  118:
  119:           if ( mid )
  120:           {
  121:             jboolean boolean = env->CallBooleanMethod( obj, mid, 1 );
  122:             std::cout << "booleanMethod - Result: " << ( boolean ? JNI_TRUE : JNI_FALSE ) << std::endl;
  123:           }
  124:         }
  125:
  126:         void stringMethod()
  127:         {
  128:           if ( ! obj ) return;
  129:           jmethodID mid = env->GetMethodID( child, "stringMethod", "()Ljava/lang/String;" );
  130:
  131:           if ( mid )
  132:           {
  133:             jstring str = static_cast<jstring>( env->CallObjectMethod( obj, mid ) );
  134:             displayString( "stringMethod", str );
  135:           }
  136:         }
  137:
  138:         void nonVirtualStringMethod()
  139:         {
  140:           jclass base = env->FindClass( "com/sptci/jnitest/Base" );
  141:
  142:           if ( ! base || ! obj ) return;
  143:
  144:           jmethodID mid = env->GetMethodID( base, "stringMethod", "()Ljava/lang/String;" );
  145:
  146:           if ( mid )
  147:           {
  148:             jstring str = static_cast<jstring>( env->CallNonvirtualObjectMethod( obj, base, mid ) );
  149:             displayString( "nonVirtualStringMethod", str );
  150:           }
  151:
  152:           env->DeleteLocalRef( base );
  153:         }
  154:
  155:         void displayString( std::string method, jstring str )
  156:         {
  157:             const char* cstr = env->GetStringUTFChars( str, nullptr );
  158:             std::cout << method << " - Result: " << cstr << std::endl;
  159:             env->ReleaseStringUTFChars( str, cstr );
  160:             env->DeleteLocalRef( str );
  161:         }
  162:
  163:       private:
  164:         JavaVM* jvm;
  165:         JNIEnv* env;
  166:         jclass child;
  167:         jobject obj;
  168:         int status = 0;
  169:       };
  170:     };
  171:   };
  172: };
  173:
  174: int main()
  175: {
  176:   com::sptci::jnitest::Client client;
  177:   client.run();
  178:   return client.getStatus();
  179: }
  180:

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.

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.