Error: I'm afraid this is the first I've heard of a "ht" flavoured Blosxom. Try dropping the "/+ht" bit from the end of the URL.

Mon, 10 May 2004

Creating 'native' Qt applications with Java

Recently, Havoc Pennington and Miguel D'Icaza caused a big flap by starting a discussion about the desirability of using a managed language -- like Java, C#, Python or Lisp to develop the core applications that make up a Linux desktop environment. These two gentlemen are rather Gnome-centric, so it's perhaps excusable that they never realized that what they propose is already possible, and even easy, with KDE, thanks to Phil Thompson and Richard Dale.. Even better: you can use a managed language, and still have the fun of compiling your code down to native code -- thanks to GCJ.


Mark Wielaard, who is the maintainer of Classpath (the collection of free class libraries that will one day duplicate Sun's runtime libraries), helped me carry out the experimental compiling of a simple qtjava application with gcj.

This is the java code:

Hello.java

import org.kde.qt.*;

public class Hello extends QMainWindow {

    static {
        qtjava.initialize();
    }

    public Hello(QWidget parent, String name) {
        super(parent, name);
        setCaption("Hello World");
    }


    public static void main(String[] args) {
        QApplication app = new QApplication(args);
        Hello win = new Hello(null, null);
        win.show();
        app.connect(app, SIGNAL("lastWindowClosed()"),
                            app, SLOT("quit()"));
        app.exec();
    }
}

As prerequisites, you need gcj installed (which is true by default with SUSE 9.1), and qtjava, which is part of the kdebindings package, is is also a default component of SUSE 9.1.

Let's first compile our java source to bytecode with gcj, and run it with gij (the GNU interpreter for Java). The -C option tells gcj to compile to bytecode.

export CLASSPATH=/opt/kde3/lib/java/qtjava.jar:.:$CLASSPATH
gcj -C -classpath ${CLASSPATH} Hello.java
gij -classpath ${CLASSPATH} Hello

This is already quite exciting because it means that we have a completely free toolchain to create KDE applications in Java. Of course, we could already do so in Python, and because qtjava uses JNI for all calls to the Qt library, there's a fair chance that PyQt application would be faster than qtjava applications. Lindows uses PyQt for its LSongs and LPhoto applications. (Linspire has disappeared the code for the beta's of these two GPL'ed apps, but you can get them at http://www.valdyas.org/~boud.)

But now we're going to create a native-code version of our test application. This is infinitely cooler... It is possible to create a statically linked version of our app, by linking in the entire qtjava.jar by specifying it on the command line:

boud@talnus:~/prj/qjhello>export CLASSPATH=/opt/kde3/lib/java/qtjava.jar
boud@talnus:~/prj/qjhello>gcj -fjni -classpath ${CLASSPATH} /opt/kde3/lib/java/qtjava.jar \
  Hello.java --main=Hello -o hello

This line demands a bit of careful attention. The -fjni switch is needed because the qtjava lib uses JNI to link between Java and C++; another option would have been CNI, which is faster, cleaner but unsupported by Sun's legacy Java toolchain. Next, we still have to give all the jarfiles that we use on the classpath. Gcj uses this as a kind of C++ include files. Then we have a list of files that must be compiled: either jar files, or plain java files. Gcj can compile both byte code and source code to native code. Finally, we have to tell gcj which class file contains the main function that we want to use as the entry point for our application. (Potentially, every Java class can have a main.)

This, however produces a rather large executable:

-rwxr-xr-x  1 boud users 7024800 2004-05-10 17:59 hello

It would be better to make qtjava in to a shared library, and only link runtime to to qtjava.so. That's easy enough to do. The first line creates the shared library, the second the executable. We use the -O2 optimization -- Mark assures me it's completely safe, and it does make a large difference in startup speed.

gcj -O2 -fjni -shared ${CLASSPATH} -o qtjava.so
gcj -O2 -fjni -classpath ${CLASSPATH} Hello.java qtjava.so \
 --main=Hello -o hello

Life is not all sweetness yet.. Because qtjava uses JNI, all calls to Qt go through gij anyway, even when we have compiled everything down to native code, which comes at a performance cost. And it would be nice if one could mix and match Java with any C++ lib, without bindings glue. I'd love to swap large chunks of Krita's C++ code for natively compiled Java.

But: natively compiled Java still uses the garbage collection, is still a managed language, but with the performance and ease of integration in an existing native code framework that's hard to achieve otherwise. The best of all possible worlds, it seems to me.

Now for something that works the other way around: create a core library in Java (some things are quite a bit easier in a language that doesn't force you to look in two files to find out how something works, and that is simplified to the point where there's sometimes just a dozen ways of doing things, instead of hundreds), and use that library from C++.

#