Java JNI: создание окна Swing с использованием JNI с C

Я использую JNI для вызова статического java-метода, который, в свою очередь, создает Swing JFrame и отображает его. Код довольно прост, и Java-код работает автономно (т.е. java StartAWT делает то, что он должен), тогда как при вызове из C с использованием JNI процесс зависает.

Я использую JDK 1.7.0_09 для Mac OS X 10.8 Mountain Lion.

Это код 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 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, но я не знаю, как ее настроить для ее использования (и я не уверен, что это причина).

Что нужно сделать, чтобы показать графический интерфейс на основе AWT с использованием JNI?

-

EDIT: Я вставил циклы, чтобы увидеть, какие потоки живы, а какие нет (можно увидеть в this gist). В этой версии я делаю вызов 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
)

Это также то, что я читал в других вопросах здесь, в StackOverflow, например, в комментариях ниже. Однако я не мог найти решение исходной проблемы. Возможно, стоит отметить, что после того, как появилось вышеприведенное сообщение, основной поток все еще жив, т.е. Не затормозился и не сработал процесс.

-

EDIT: Я тестировал код в Linux, где он работает как ожидалось. Поэтому я считаю, что это проблема Mac OS X с Cocoa AWT, но я не знаю, как обойти ее.

-

EDIT: Я также попытался переместить весь вызов JVM на новый собственный поток. Это работает на Mac OS X 10.6 с 32-битным ядром Java (1.6.0_37), но приводит к тому же тупику, который описан выше. В Mac OS X 10.8 это хуже, а приложения - с единственным сообщением "Trace/BPT trap: 5" (что связано с загрузкой динамических библиотек).

Я также попытался связать двоичный файл, как описано в этом Q & A, но запуск завершился неудачно с сообщением lsopenurlswithrole() failed with the message -10810, которое является неизвестной ошибкой, согласно Яблоки Справочное руководство по запуску. Последнее также происходит, не пытаясь использовать AWT (простое вызов JVM не работает).

Ответ 1

Наконец, я нашел решение.

Проблема не в том, какой поток создается виртуальной машиной, проблема в том, какой поток инициализируется очереди событий AWT. Другими словами: в первый раз, когда загружается класс AWT, он не может быть загружен в основной поток. Таким образом, шаг 1: Загрузить (например) java.awt.Component в другой поток.

Но теперь EventQueue будет блокироваться, поскольку он делегирует работу в Main Event Queue 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 в другом потоке будет успешной, и вы можете продолжить там.