Java JNI : Creating a Swing Window using JNI from C

标签 java c swing java-native-interface 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);

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

--

编辑: 我插入了循环以查看哪些线程处于 Activity 状态,哪些线程不活动(可以在 this gist 中看到)。在此版本中,我在另一个线程中调用了 SwingUtilities.invokeAndWait。结果:主线程处于 Activity 状态 (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 的整个调用移动到一个新的 native 线程上。这适用于带有 Apples Java 32 位 (1.6.0_37) 的 Mac OS X 10.6,但会导致与上述相同的死锁。在 Mac OS X 10.8 上,情况更糟,应用程序崩溃并显示唯一消息“Trace/BPT trap: 5”(seems to be related to loading dynamic libraries)。

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

最佳答案

终于找到了解决办法。

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

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

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 类加载将成功,您可以在那里继续。

关于Java JNI : Creating a Swing Window using JNI from C,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14661249/

相关文章:

java - 是否需要调用构造函数将变量初始化为默认值?

java - 从 groovy 中访问 java 类

c - 带指针的开关盒

java - 从 JDialog 返回值; dispose(), setVisible(false) - 例子

java - 将 jFrame 更改为 jDialog?

java - 排序 Android ListView

java - 使用 javaagent 获取 tomcat 中托管的应用程序的开始和结束时间

C - malloc 字符串时出错

c - 将结构指针初始化为 null

java - 在 Java 文本编辑器中创建类似 Eclipse 的行标记栏