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:

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

  2. 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
  3. 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_1explicit who 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?

published on
Previous
Flexible calendar control in SWT