10

我正在使用 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);

该类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();
    }
}

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

我相信我的 C 进程中的虚拟机没有 AWT 事件队列,但我不知道如何设置它以拥有一个(我也不确定这是原因)。

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

--

编辑:我已经插入循环来查看哪些线程是活动的,哪些不是(可以在这个 gist中看到)。在这个版本中,我SwingUtilities.invokeAndWait在另一个线程中调用。结果:主线程处于活动状态(C)。Java 调度的第一个线程(不是主线程)是存活的;执行 Call 的线程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
)

这也是我在 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”(这似乎与加载动态库有关)。

根据 Apples Launch Services Reference的说法,我还尝试按照此 Q&A中的描述捆绑二进制文件,但启动失败并显示消息,这是一个未知错误。后者也发生在没有尝试使用 AWT 的情况下(单纯的 JVM 调用失败)。lsopenurlswithrole() failed with the message -10810

4

2 回答 2

8

最后我找到了解决方案。

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

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

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

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

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

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

于 2013-05-07T14:56:22.943 回答
1

我通过 OSX 的指令解决了类似的问题:JavaVM、AWT/Swing 和可能的死锁,即CFRunLoopRun()在另一个线程中启动 JVM 后启动。

于 2013-12-27T09:23:49.350 回答