Subnavigation

Extremely Interesting Jambi Trick #X: Instantiating Java widgets from C++

Say you are a C++ programmer who has a heartfelt passion for writing plugin based applications in Qt. One day, during a brief loss of your senses, you decide that you want to expand the plugin support in your latest creation to include contributions from both C++ and Java programmers. Riddled with idealism and optimism, you cross out a week in your calendar, sit down and start reading the JNI specification. At some point during this week, you'll probably realize that nothing is as easy as it seems, and that the road to hell is paved with the differences between C++ and Java. Next, you'll either give up, set off an indefinite amount of time to write elaborate bindings for your interfaces, or you'll read this blog and see that you were right all along and life still comes with happy endings.

All dramatic phrasing aside, in Qt Jambi we have already solved a bunch of the problems that arise when you are binding C++ with Java, and it's actually pretty easy and convenient to use the Qt Jambi generator and framework to import classes and objects back and forth across the boundaries of the language. In this blog, I'd like to go through some simple code that allows you to make a custom widget in Java using Qt Jambi, instantiate this in C++, and then add it to a C++-based layout. I have a bit of a cold, and I didn't really feel like putting together an example that makes any sense, but I've posted some code that you can download, which will create a window with a button and the Qt Jambi Colliding Mice example. The latter is written in Java, and can easily be replaced by any other Qt Jambi widget you might write. To get the same convience from your own classes and interfaces, you would simply use the Qt Jambi generator.

The prerequisites for compiling and using the code is: an installation of Qt Jambi, the JDK, and a Qt version matching your Qt Jambi version.

To build the code, set the JAVADIR environment variable to point to your JDK, the JAMBIDIR environment variable to point to your Qt Jambi installation and qmake away. You may have to tweak the .pro file a little for different platforms, because I've only tested it locally. Also make sure that you have both the Qt Jambi class files (e.g. in qtjambi.jar) and the root of the Qt Jambi installation in your CLASSPATH.

Building and running the javafromcpp application will show you a window with a push button on top and the colliding mice below it. The push button is a plain Qt/C++ widget, while the colliding mice are implemented in Qt Jambi, i.e. in Java. Here's what it will look like, and following that is all the magic explained:

Java widget inside C++ widget

In the start of the main() function in the code, you'll see the following function call:


// Make sure the JVM is loaded
qtjambi_initialize_vm();

This single line makes Qt Jambi search for your dynamic JVM library and instantiate a virtual machine for the process. Note that there is only space for a single VM in each process, so don't expect this to work twice. (If there are problems in this call, you'll get a warning. In that case we'd really like to know about it, plus which platform you are on, so we can figure out what's failing.)

Anyway, initializing the VM is required to get access to JNI.

The code then sets up a window with a push button and some snacks, and it makes the function call which actually instantiates the java widget and returns a C++ pointer to it:


QWidget *colliding_mice = qtjambi_instantiate_widget("com/trolltech/examples/CollidingMice");

The returned pointer is handled like any regular QWidget-pointer in Qt. We specify the fully qualified name of the class we wish to instantiate, in this case com.trolltech.examples.CollidingMice. In JVM syntax, the dots are replaced by forward slashes.

All the actual work is done inside qtjambi_instantiate_widget(), which I'll now go through in detail.

The first paragraph of code retrieves a JNIEnv-pointer, which is your access point to JNI for the current thread and virtual machine.


JNIEnv *env = qtjambi_current_environment();
if (env == 0) {
qWarning("Cannot get current JNI environment. Was the JVM loaded correctly?");

// Make sure a possible exception is removed from the stack
qtjambi_exception_check(env);
return 0;
}

If we do not get a pointer to the JNI environment, we have to bail out. In case there is an exception on the stack, we call qtjambi_exception_check() which will print any pending exception and clear the stack. The next step is to create a frame for local references using JNIEnv::PushLocalFrame() (which will be pop'ed at the end of the function to make sure we don't retain references to temporary objects.)

We go on by locating the class that was specified in the function call. The class will have to be available on the class path, or this call will return null.


jclass clazz = qtjambi_find_class(env, qualified_name);

Using the same function, we find Qt Jambi's QWidget-class, and use JNI to make sure the specified class is actually a QWidget subclass (this could help prevent some horrific crashes.)

Using some more JNI code, we get the ID of an assumed constructor in the specified class which takes a reference to a QWidget-parent:

    jmethodID constructor_id = env->GetMethodID(clazz, "<init>", "(Lcom/trolltech/qt/gui/QWidget;)V");

You have to design your classes by this convention in order to use the example code to instantiate them.

Next, we have to convert the supplied parent pointer to a Java object in order to pass it to the Java constructor.


jobject java_parent = parent != 0 ? qtjambi_from_QWidget(env, parent) : 0;

The qtjambi_from_QWidget() call will either create a new Java widget if the parent widget was created in C++, or it will return the existing Java object if the parent was created in Java. If it has to create a new java object, the type of this will be the closest Java supertype known to Qt Jambi. If you have mapped your own C++ widgets and want to use them correctly in calls such as these, you have to make sure the initialization code of your generated library is called prior to the conversion takes place. Also note that in qtjambi_core.h you will find several other convenient conversion functions that can be used to convert back and forth between C++ and JNI, as well as other convenient, JNI-based code.

We instantiate our new widget using JNI, create a global reference to it to make sure it isn't garbage collected before it can be hooked up to our hierarchy, and finally we convert the java object to C++ using the following call:


return qobject_cast(qtjambi_to_qobject(env, java_widget));

And that does the trick. If the class was available, had the right supertype and constructor, then we will now return a fully operational C++ pointer to the object.

By the way, here's another link to the downloadable code for those of you who skimmed everything up until the crazy screenshot.


Blog Topics:

Comments