Java JNI:使用C从JNI创建Swing窗口

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,需要做什么?

--

编辑: 我已经插入循环以查看哪些线程是活动的,哪些不是(可以在this gist中看到)。在这个版本中,我在另一个线程中调用了SwingUtilities.invokeAndWait。结果: 主线程是活动的(C)。Java调度的第一个线程(不是主线程)是活动的; 调用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的调用移动到一个新的本地线程上。这在Mac OS X 10.6上使用苹果Java 32位(1.6.0_37)可以工作,但会导致与上述相同的死锁。在Mac OS X 10.8上更糟糕,应用程序会崩溃,并显示唯一的消息“Trace/BPT trap: 5”(这似乎与加载动态库有关)。
我还尝试按此Q&A所述捆绑二进制文件,但启动失败,显示消息lsopenurlswithrole() failed with the message -10810,这是一个未知错误,根据苹果的Launch Services Reference。后者即使不尝试使用AWT也会发生(仅JVM调用失败)。

请参阅此问答 - trashgod
谢谢,我已经查看了那个问答并尝试了一下我的应用程序;但是最终它还是无法工作(就像其他问题一样,在那里也没有给出解决方案)。 - scravy
我得到了类似的结果;我只是使用了JavaApplicationStub,但我不知道它是如何工作的。我想知道在这里引用的JVM TI是否有任何相关内容。 - trashgod
它在Linux上(Ubuntu 12.04 LTS 32位)完全按预期工作。 - scravy
太好了,我找到了解决方案,请看我的答案。 - scravy
2个回答

8

最终我找到了一个解决方案。

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

但现在事件队列将被阻塞,因为它将把工作委托给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类将成功,然后您可以继续进行。

不是很明白。请问“在另一个线程中加载AWT类”是什么意思? - Sswater Shi
1
我知道这可能是一个愚蠢的问题,但为什么需要objc_函数?为什么不只使用[object message]符号发送消息?提前感谢您的解释。 - ed22
1
这是纯C代码,不是Objective-C代码。由于Objective-C曾经只是一个预处理器,所有的Objective-C函数都可以通过使用objc_-函数直接从C中调用。 - scravy

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接