Blog
Strategy of wrapping C++ libraries in Java
This blog post is more of a question to others (probably much more knowledgeable people than me) who have already wrapped C++ libraries in Java. So if you have and the strategy I document as part of this blog post is completely wrong please comment and point me towards a better one! Anyways let's get started! We take a very simply case where we have a C++-Class like this:
#ifndef CTESTSIMPLE_H
#define CTESTSIMPLE_H
class CTestSimple
{
public:
CTestSimple();
virtual int test();
int test_delegate();
};
#endif // CTESTSIMPLE_H
#include "ctestsimple.h"
CTestSimple::CTestSimple()
{
}
int CTestSimple::test() {
return 100;
}
int CTestSimple::test_delegate() {
return test();
}
and in java we want to do the following things:
import testjni.TestSimple;
public class TestApp {
public static void main(String[] args) {
System.load("/Users/tomschindl/build-JTestJNI/libJTestJNI.dylib");
System.out.println("===== Simple");
TestSimple t = new TestSimple();
System.out.println("Direct: " + t.test());
System.out.println("Delegate:" + t.test_delegate());
System.out.println("===== No Override");
NoOverride tn = new NoOverride();
System.out.println("Direct: " + tn.test());
System.out.println("Delegate: " + tn.test_delegate());
System.out.println("===== With Override");
SubTestSimple ts = new SubTestSimple();
System.out.println("Direct: " + ts.test());
System.out.println("Delegate: " + ts.test_delegate());
System.out.println("===== With Override");
SubSuperTestSimple tss = new SubSuperTestSimple();
System.out.println("Direct: " + tss.test());
System.out.println("Delegate: " + ts.test_delegate());
}
static class SubTestSimple extends TestSimple {
@Override
public int test() {
return 0;
}
}
static class SubSuperTestSimple extends TestSimple {
@Override
public int test() {
return super.test() + 10;
}
}
static class NoOverride extends TestSimple {
}
}
We expect the application to print:
===== Simple
Direct: 100
Delegate:100
===== No Override
Direct: 100
Delegate: 100
===== With Override
Direct: 0
Delegate: 0
===== With Override & super
Direct: 110
Delegate: 110
The strategy I found to make this work looks like this:
On the C++ side one needs to define a subclass of CTestSimple I named JTestSimple
#ifndef JTESTSIMPLE_H #define JTESTSIMPLE_H #include "ctestsimple.h" #include <jni.h> class JTestSimple : public CTestSimple { private: jobject jObject; JNIEnv* env; jmethodID jTest; public: JTestSimple(JNIEnv *,jobject,jboolean derived); virtual int test(); }; #endif // JTESTSIMPLE_H#include "jtestsimple.h" #include <iostream> #include <jni.h> JTestSimple::JTestSimple(JNIEnv * env, jobject o, jboolean derived) { this->jObject = env->NewGlobalRef(o); this->env = env; this->jTest = 0; if( derived ) { jclass cls = env->GetObjectClass(this->jObject); jclass superCls = env->GetSuperclass(cls); jmethodID baseMethod = env->GetMethodID(superCls,"test","()I"); jmethodID custMethod = env->GetMethodID(cls,"test","()I"); if( baseMethod != custMethod ) { this->jTest = custMethod; } } } int JTestSimple::test() { if( this->jTest != 0 ) { return env->CallIntMethod(this->jObject,this->jTest); } return CTestSimple::test(); }The important step is to check in if the java-object we got passed in has overwritten the test()-method because in that case we need to call from C++ to java for the correct result.
On the Java side we define our object like this
package testjni; public class TestSimple { private long NativeObject; public TestSimple() { NativeObject = createNativeInstance(); } protected long createNativeInstance() { return Native_new(getClass() != TestSimple.class); } public int test() { if( getClass() == TestSimple.class ) { return Native_test(); } else { return Native_test_explicit(); } } public final int test_delegate() { return Native_test_delegate(); } private native long Native_new(boolean subclassed); private native int Native_test(); private native int Native_test_explicit(); private native int Native_test_delegate(); }Some remarks are probably needed:
- Notice that the there are 2 native delegates for
test()depending on fact whether the method is invoked in subclass or not test_delegate()is final because it is none-virtual on the C++ side and hence is not subject to overloading in subclasses
- Notice that the there are 2 native delegates for
On the JNI-Side
#include <jni.h> #include <stdio.h> #include <iostream> #include "testjni_TestSimple.h" #include "jtestsimple.h" JNIEXPORT jint JNICALL Java_testjni_TestSimple_Native_1test (JNIEnv * env, jobject thiz) { jclass cls = env->GetObjectClass(thiz); jfieldID id = env->GetFieldID(cls,"NativeObject","J"); jlong pointer = env->GetLongField(thiz,id); JTestSimple* obj = (JTestSimple*)pointer; return obj->test(); } JNIEXPORT jint JNICALL Java_testjni_TestSimple_Native_1test_1explicit (JNIEnv * env, jobject thiz) { jclass cls = env->GetObjectClass(thiz); jfieldID id = env->GetFieldID(cls,"NativeObject","J"); jlong pointer = env->GetLongField(thiz,id); JTestSimple* obj = (JTestSimple*)pointer; return obj->CTestSimple::test(); } JNIEXPORT jint JNICALL Java_testjni_TestSimple_Native_1test_1delegate (JNIEnv * env, jobject thiz) { jclass cls = env->GetObjectClass(thiz); jfieldID id = env->GetFieldID(cls,"NativeObject","J"); jlong pointer = env->GetLongField(thiz,id); JTestSimple* obj = (JTestSimple*)pointer; return obj->test_delegate(); } JNIEXPORT jlong JNICALL Java_testjni_TestSimple_Native_1new (JNIEnv * env, jobject thiz, jboolean derived) { jlong rv = (jlong)new JTestSimple(env, thiz, derived); return rv; }The only interesting thing is the
Java_testjni_TestSimple_Native_1test_1explicitwho does not invoke the test-method on JTestSimple but on its super class CTestSimple.
So my dear C++/Java gurus how bad / dump / ... is this strategy?