Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java JNI: Creating a Swing Window using JNI from C

I'm using JNI to invoke a static java method which in turn creates a Swing JFrame and displays it. The code is fairly simple, and the Java-code is working standalone (i.e. java StartAWT does what it should) whereas when called from C using JNI the process hangs.

I'm using the JDK 1.7.0_09 on Mac OS X 10.8 Mountain Lion.

This is the C code I'm using to invoke the static method:

JavaVM* jvm;
JNIEnv* env = create_vm(&jvm);

jclass class = (*env)->FindClass(env, "StartAWT");
jmethodID method = (*env)->GetStaticMethodID(env, class, "run", "()V");

(*env)->CallStaticVoidMethod(env, class, method);

(*jvm)->DestroyJavaVM(jvm);

The StartAWT class looks like this:

public class StartAWT {

    public static class Starter implements Runnable {
        public void run() {
            System.out.println("Runnning on AWT Queue.");

            JFrame.setDefaultLookAndFeelDecorated(true);
            JFrame frame = new JFrame("That's a frame!");
            JLabel label = new JLabel("A Label");
            frame.getContentPane().add(label);

            frame.pack();
            frame.setVisible(true);
        }
    }

    public static class GUI implements Runnable {
        public void run() {
            try {
                System.out.println("Going to put something on the AWT queue.");
                SwingUtilities.invokeAndWait(new Starter());
            } catch (Exception exc) {
                throw new RuntimeException(exc);
            }
        }
    }

    public static void run() {
        Thread gui = new Thread(new GUI());
        gui.start();
    }
}

When I start the application, I do see Going to put something on the AWT queue but not Running on AWT Queue.

I believe that the Virtual Machine inside my C Process does not have an AWT event queue but I don't know how to set it up for having one either (nor am I sure that this is the reason).

What is to be done in order to show an AWT based GUI using JNI ?

--

EDIT: I've inserted loops to see which threads are alive and which are not (can be seen in this gist). In this version I do the invocation of SwingUtilities.invokeAndWait in another thread. The result: The main thread is alive (C). The first thread dispatched by Java (not the main thread) is alive; the thread doing the Call invokeAndWait is blocked (I don't think that invokeAndWait did even return), the function which should be run on the EventQueue is not even entered.

I've also tried invoking SwingUtilities.invokeAndWait directly, which will give the following message:

2013-02-02 13:50:23.629 swing[1883:707] Cocoa AWT: Apple AWT Java VM was loaded on first thread -- can't start AWT. (
    0   liblwawt.dylib                      0x0000000117e87ad0 JNI_OnLoad + 468
    1   libjava.dylib                       0x00000001026076f1      Java_java_lang_ClassLoader_00024NativeLibrary_load + 207
    2   ???                                 0x000000010265af90 0x0 + 4335185808
)

This is also what I've read in other questions here on StackOverflow, such as the one suggested in the comments below. However, I could not find a solution to the original problem. Maybe it is worth noting that after the above message came up the main thread is still alive, i.e. neither did the process deadlock nor crash.

--

EDIT: I tested the code on Linux where it is working as expected. So I believe this is a Mac OS X issue with Cocoa AWT, but I don't know how to circumvent it.

--

EDIT: I also tried moving the entire invocation of the JVM onto a new native thread. This works on Mac OS X 10.6 with Apples Java 32-bit (1.6.0_37), but results in the same deadlock as described above. On Mac OS X 10.8 this is worse, the application crases with the only message "Trace/BPT trap: 5" (which seems to be related to loading dynamic libraries).

I also tried bundling the binary as described in this Q&A, but the launch fails with the message lsopenurlswithrole() failed with the message -10810, which is an unknown error, according to Apples Launch Services Reference. The latter also happens without attempting to use AWT (the mere JVM invocation fails).

like image 282
scravy Avatar asked Feb 02 '13 11:02

scravy


2 Answers

Finally I found a solution.

The problem is not on which thread the Virtual Machine is being created, the problem is on which thread the AWT Event Queue is being initialized. In other words: The first time that an AWT class is loaded, it may not be loaded on the main thread. Thus step 1: Load (for example) java.awt.Component on another thread.

But now the EventQueue will block, as it delegates work to the Cocoa Main Event Queue which is not running - sure enough, since it will only run on the main thread and the main thread is my application. Thus the main run loop needs to be started on the main thread:

void
runCocoaMain()
{
    void* clazz = objc_getClass("NSApplication");
    void* app = objc_msgSend(clazz, sel_registerName("sharedApplication"));

    objc_msgSend(app, sel_registerName("run"));
}

I had to link my application with the Cocoa framework and include <objc/objc-runtime.h>. The main thread is blocked after the call to runCocoaMain (since the event loop is running there), so one needs to resort to another thread for the application itself.

After running the EventQueue using the above snippet the loading of the AWT class on the other thread will succeed and you can proceed there.

like image 196
scravy Avatar answered Nov 16 '22 20:11

scravy


I resolved similar problem by instructions of OSX: JavaVM, AWT/Swing and possibly a deadlock , that is to start CFRunLoopRun() after start JVM in another thread.

like image 43
Sswater Shi Avatar answered Nov 16 '22 20:11

Sswater Shi