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?

Advertisement
This entry was posted in Uncategorized. Bookmark the permalink.

13 Responses to Strategy of wrapping C++ libraries in Java

  1. winne says:

    I would always prefer to use a toolkit for this. In a recent case, SWIG showed to be an easy to use and fully working choice (with additional benefit like spitting out a Python interface if needed).

  2. Christoph says:

    Some years ago I have been working on a bindings generator which we used for a 3d rendering engine.

    I that project we used the same approach to store address of c++ objects in a java member. But we did not need to override c++ methods in java, so your JTestSimple was not required for us.

    Your code makes sense to me.

    Regards,
    Christoph

    https://code.google.com/p/xbig/
    https://code.google.com/p/ogre4j/
    http://www.ogre3d.org/

  3. WhoCares says:

    Hi Tom,

    I’m neither a C++ nor a JNDI expert but why do you exactly need this kind of complexity (concerning inheritance)? Because if I want to override a method in a sub-class, why should I want to call the method from a the super class depending on some c-logic?

    For me it would make more sense to stay with the standard Java behavior. Means if the sub-class wants to call the super method is should anounce this with “super.test()”. With this behavior a normal Java-Developer would see the super call and doesn’t have to know the logic in the c-code.

    So for me it would be:

    package testjni;

    public class TestSimple {
    private final long NativeObject;

    public TestSimple() {
    NativeObject = createNativeInstance();
    }

    private final long createNativeInstance() {
    return Native_new(getClass() != TestSimple.class);
    }

    public int test() {
    return Native_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();
    }

    And a Sub-Class would look like

    public class SubTestSimple extends TestSimple {
    @Override
    public int test() {
    return 0;
    }

    }

    And Sub-Super-Class would look like
    public class SubSuperTestSimple extends TestSimple {
    @Override
    public int test() {
    return super.test() + 10; // here i can see this want’s to have the super call which in will got to the native code
    }
    }

    Regards
    WhoCares

    • Tom Schindl says:

      Your code will end up in an endless loop – when the method from the base-class is invoked we never should call back from C++ to Java

  4. David Goodenough says:

    Have you looked at JNA or gluegen?

    • Tom Schindl says:

      gluegen & JNA are only for c if I get that right and so you’d have to write some C glue code. The task at hand is that I need to be able to subclass C++-Classes in Java!

      • David Goodenough says:

        Well there is JNAerator which claims to handle C++ to JNA bridging. Never used it but might work. There also used to be something (I think its most recent name was Koala, but look for the work of Richard Dale, he was the original author) that was used to generate the Java bindings for Qt and KDE. Those bindings have rather bitten the dust but the code is probably still in the archives somewhere.

      • Tom Schindl says:

        Thanks for the pointers!

  5. jmini says:

    Remi Forax suggests JNR [1] (example here [2]) In Java 9 there will be a new project called panama [3] (same team).

    [1] https://github.com/jnr/
    [2] https://github.com/jnr/jnr-invoke
    [3] http://openjdk.java.net/projects/panama/

  6. Yeah, subclassing C++ classes with Java classes certainly adds to the complexity. But it looks like your approach is something I’d do.

    And don’t trust any tools that generates this for you. You’ll end up fighting it since it won’t generate code the way you want it to. Better to hand code it yourself, or build you’re own generator ;).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.