Java JNI:使用C中的JNI创建Swing窗口

scr*_*avy 10 c java java-native-interface swing awt

我正在使用JNI来调用静态java方法,该方法又创建一个Swing JFrame并显示它.代码非常简单,Java代码独立工作(即java StartAWT做它应该做的事情),而当使用JNI从C调用时,进程挂起.

我在Mac OS X 10.8 Mountain Lion上使用JDK 1.7.0_09.

这是我用来调用静态方法的C代码:

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);
Run Code Online (Sandbox Code Playgroud)

这个StartAWT类看起来像这样:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我启动应用程序时,我确实看到了Going to put something on the AWT queue但没有Running on AWT Queue.

我相信我的C Process中的虚拟机没有AWT事件队列但是我不知道如何设置它也没有(我也不确定这是原因).

要使用JNI显示基于AWT的GUI,需要做什么?

-

编辑:我已插入循环,以查看哪些线程是活着的,哪些不是(可以在这个要点中看到).在这个版本中,我SwingUtilities.invokeAndWait在另一个线程中进行调用.结果:主线程处于活动状态(C).由Java(不是主线程)调度的第一个线程是活动的; 执行调用的线程invokeAndWait被阻止(我认为invokeAndWait甚至没有返回),甚至没有输入应该在EventQueue上运行的函数.

我也尝试过SwingUtilities.invokeAndWait直接调用,它会给出以下消息:

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
)
Run Code Online (Sandbox Code Playgroud)

这也是我在StackOverflow上的其他问题中读到的内容,例如下面评论中建议的问题.但是,我找不到原始问题的解决方案.也许值得注意的是,在上面的消息出现后,主线程仍然存在,即进程死锁或崩溃都没有.

-

编辑:我在Linux上测试了它正在按预期工作的代码.所以我认为这是与Cocoa AWT的Mac OS X问题,但我不知道如何规避它.

-

编辑:我也尝试将整个JVM调用移动到一个新的本机线程.这适用于使用Apples Java 32位(1.6.0_37)的Mac OS X 10.6,但会导致与上述相同的死锁.在Mac OS X 10.8上,这更糟糕,应用程序只使用"Trace/BPT trap:5"消息(这似乎与加载动态库有关).

我还尝试按照本问答中的描述捆绑二进制文件,但lsopenurlswithrole() failed with the message -10810根据Apples Launch Services Reference,启动失败并显示消息,这是一个未知错误.后者也在不尝试使用AWT的情况下发生(仅仅JVM调用失败).

scr*_*avy 8

最后我找到了解决方案.

问题不在于创建虚拟机的线程,问题在于AWT事件队列被初始化的线程.换句话说:第一次加载AWT类时,它可能不会加载到主线程上.因此,步骤1:java.awt.Component在另一个线程上加载(例如).

但是现在EventQueue将阻塞,因为它将工作委托给未运行的Cocoa主事件队列 - 当然,因为它只能在主线程上运行而主线程是我的应用程序.因此,需要在主线程上启动主运行循环:

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

    objc_msgSend(app, sel_registerName("run"));
}
Run Code Online (Sandbox Code Playgroud)

我不得不将我的应用程序与Cocoa框架链接并包含<objc/objc-runtime.h>.在调用runCocoaMain之后,主线程被阻塞(因为事件循环在那里运行),因此需要为应用程序本身寻求另一个线程.

使用上面的代码片段运行EventQueue后,另一个线程上的AWT类加载将成功,您可以继续.