Création JFrame java avec JNI

OimyOimy Membre
13 juin modifié dans Objective-C, Swift, C, C++ #1

Bonjour,

Mon but est de créer une Frame en utilisant JNI. Cela fonctionne quand j'utilise un code simple et que je le compile tout seul. Par contre cela ne fonctionne pas quand je l'intègre à mon projet global.

CPP:
include <iostream>
include <jni.h>
include <objc/objc-runtime.h>

void runCocoaMain(void)
{
    id clazz = (id) objc_getClass("NSApplication");
    id _Nullable app = objc_msgSend(clazz, sel_registerName("sharedApplication"));
    objc_msgSend(app, sel_registerName("run"));
}

int main()
{
    using namespace std;
     JavaVM *jvm;               // Pointer to the JVM (Java Virtual Machine)
    JNIEnv *env;                // Pointer to native interface
     JavaVMInitArgs vm_args;                        // Initialization arguments
    JavaVMOption* options = new JavaVMOption[1];   // JVM invocation options
    options[0].optionString = "-Djava.class.path=.";   // where to find java .class
    vm_args.version = JNI_VERSION_1_8;             // minimum Java version
    vm_args.nOptions = 1;                          // number of options
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_TRUE;     // invalid options make the JVM init fail
    jint rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);  // YES !!
    delete options;    // we then no longer need the initialisation options. 

        jclass cls2 = env->FindClass("MyTest2");  // try to find the class 
    if(cls2 != nullptr) 
           {
        jmethodID mid = env->GetStaticMethodID(cls2, "run", "()V");
        if(mid != nullptr)
                      {
                      env->CallStaticVoidMethod(cls2, mid);
                      runCocoaMain();
              }
           }
    jvm->DestroyJavaVM();
}_
JAVA:
_import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class MyTest2 {

    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!");
            frame.setSize(400, 200);
            JLabel label = new JLabel("A Label");
            frame.getContentPane().add(label);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            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();
    }
}

La fonction RunCocoaMain() me permet de faire fonctionner la JFrame comme expliqué dans ce forum (https://stackoverflow.com/questions/14661249/java-jni-creating-a-swing-window-using-jni-from-c). Cela fonctionne parfaitement tout seul par contre lorsque j'insère le code dans mon projet global, j'ai une erreur pendant l'exécution dans la fonction RunCocoaMain() au niveau de la ligne "id _Nullable app = objc_msgSend(clazz, sel_registerName("sharedApplication"));"

Erreur :
A fatal error has been detected by the Java Runtime Environment:
SIGSEGV (0xb) at pc=0x00007fff5f159165, pid=4917, tid=0x0000000000000307
JRE version: Java(TM) SE Runtime Environment (8.0_172-b11) (build 1.8.0_172-b11)
Java VM: Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode bsd-amd64 compressed oops)
Problematic frame:
C [SkyLight+0x20a165] _ZN12_GLOBAL__N_112get_registryEv.8847+0x33
Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
An error report file with more information is saved as:
/Users/.../Desktop/projet/total_test/hs_err_pid4917.log
If you would like to submit a bug report, please visit:
http://bugreport.java.com/bugreport/crash.jsp
The crash happened outside the Java Virtual Machine in native code.
See problematic frame for where to report the bug.

Le fichier hs_err_pid4917.log est en PJ

Mes questions sont les suivantes :
_ Etant novice dans l'interprétation du fichier .log, quelqu'un pourrai m'indiquer un auto ou alors me dire ce qu'il indique comme problème ?
_D'où vient le fait que le code fonctionne tout seul mais pas quand je l'insère dans mon projet global ?
_Ou d'un autre côté, le but étant d'ouvrir une JFrame avec JNI. J'utilise la fonction "runCocoaMain(void)" pour que la JFrame fonctionne l'idée serait de ne pas utiliser la fonction runCocoaMain() donc comment faire ?

Merci d'avance

Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur

    Comme j'ai dit dans l'autre discussion, je doute qu'il y ait quelqu'un qui sache comment le faire ici.

    J'ai fait une petite recherche et j'ai trouvé ceci : https://stackoverflow.com/questions/4972973/calling-a-java-class-function-from-cocoa-with-jni/5621879?

  • LexxisLexxis Membre
    12 juin modifié #3

    Peux tu partager les projets de tests que tu utilises (Xcode et java) ?
    Si tu lances l'app depuis le terminal (sans passer par xcode) quel résultat as-tu ?

  • OimyOimy Membre

    je n'utilise pas Xcode car le debugger fait planter le système.

    Ce que j'ai comme affichage :

    runCocoaMain debut.
    Going to put something on the AWT queue.

    A fatal error has been detected by the Java Runtime Environment:

    SIGSEGV (0xb) at pc=0x00007fff6674c165, pid=1863, tid=0x0000000000000307

    JRE version: Java(TM) SE Runtime Environment (8.0_172-b11) (build 1.8.0_172-b11)
    Java VM: Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode bsd-amd64 compressed oops)
    Problematic frame:
    C [SkyLight+0x20a165] _ZN12_GLOBAL__N_112get_registryEv.8847+0x33

    Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again

    An error report file with more information is saved as:
    /Users/obruneteau/Desktop/projet/total_test/hs_err_pid1863.log

    If you would like to submit a bug report, please visit:
    http://bugreport.java.com/bugreport/crash.jsp
    The crash happened outside the Java Virtual Machine in native code.
    See problematic frame for where to report the bug.

  • LexxisLexxis Membre
    12 juin modifié #5

    De mon coté en lançant l'app depuis le terminal, la fenêtre Java s'affiche bien, par contre depuis le debugger l'application bloque pendant la création de la JVM (je n'ai pas trouvé la cause pour l'instant) sur un SIGSEGV comme tu as apparemment.
    J'ai cependant déporté la création de la JVM dans un nouveau thread (autre que le principal - j'avais lu un tech note Apple à ce sujet qui avait un rapport entre la JVM, AWT de l'implémentation d'Apple - qui est maintenant une implémentation Oracle). J'ai aussi linker (@rpath) la lib jli en plus de la lib jvm. Ceci car apparemment il y a une "interception" de l'appel à la création de la VM par la lib Apple qui affiche une alerte indiquant qu'il n'y a pas d'implémentation JAVA installé et qui redirige vers "une ancienne" installation 1.6 (ceci sans même être linker avec le framework JAVA Apple).
    Ma config: Xcode 9.4, java JDK 1.8.
    Du coup quel IDE utilises-tu ? ou utilises-tu uniquement c++ en ligne de commande ?

  • LexxisLexxis Membre

    En faisait quelque recherche je suis tombé sur un post intéressant ici. Il "suffit" d'ignorer cette erreur ("pr h -s false SIGSEGV" dans le debugger) et de continuer l'exécution... Une erreur qui n'en ai pas vraiment une en fait...

  • OimyOimy Membre

    je n'utilise pas d'aide, je travail en ligne de commande, j'ai abandonné Xcode.

  • LexxisLexxis Membre

    Si tu utilises lldb dans ce cas un simple "c" sur l'apparition de l'exception fonctionne.

  • OimyOimy Membre
    13 juin modifié #9

    Merci pour le debugger de Xcode 9.3.1.
    J'utilise la JDK 1.8.0.72
    Que ce soit Xcode ou non, l'appel à la class javax/swing/JFrame que je fais ne passe pas (ça tourne en rond).
    Quand je fait appel à une class que j'ai créé, ça fonctionne.
    Pour ma class perso, c'est des fichier .class mais pour la JFrame, c'est c'est le fichier rt.jar dont je précise le chemin dans CLASSPATH mais rien n'y fait.

    Fonction du projet global :

    JNIEnv* UIObject::GraphicGetJNIEnv()
    {
        static ALString sLastIconImageJarPath;
        static ALString sLastCopyrightLabel;
        ALString sCurrentLocale;
        JavaVMInitArgs vm_iargs;
        JavaVMAttachArgs vm_aargs;
        int nOptionNumber;
        const int nMaxOptionNumber = 20;
        JavaVMOption options[nMaxOptionNumber];
        jint res;
        jclass cls;
        const ALString sJavaClassPathPrefix = "-Djava.class.path=";
        const ALString sOptinMinMemory = "-Xms128M";
        const ALString sOptinMaxMemory = "-Xmx128M";
        const ALString sDefaultStackSize = "-Xss512k";
        const ALString sErrorFilePrefix = "-XX:ErrorFile=";
        const ALString sHeapDumpPathPrefix = "-XX:HeapDumpPath=";
        const ALString sShowMessageBoxOnError = "-XX:+ShowMessageBoxOnError";
        const ALString sLogFileSuffix = "JavaCrash.log";
        ALString sJavaClassPath;
        ALString sErrorFile;
        ALString sHeapDumpPath;
        ALString sClassPath;
        ALString sLogFileName;
    
        if (env == NULL)
        {
            assert(jvm == NULL);
             sCurrentLocale = setlocale(LC_ALL, NULL);
            sClassPath = p_getenv("CLASSPATH");
            sJavaClassPath = sJavaClassPathPrefix + sClassPath;
            sLogFileName = Global::GetErrorLogFileName();
            sLogFileName = FileService::BuildFilePathName(FileService::GetPathName(sLogFileName),
                FileService::BuildFileName(FileService::GetFilePrefix(sLogFileName), sLogFileSuffix));
            sErrorFile = sErrorFilePrefix + sLogFileName;
            sHeapDumpPath = sHeapDumpPathPrefix + FileService::GetSystemNulFileName();
            nOptionNumber = 0;
            options[nOptionNumber].optionString = sJavaClassPath;
            nOptionNumber++;
            options[nOptionNumber].optionString = sOptinMinMemory;
            nOptionNumber++;
            options[nOptionNumber].optionString = sOptinMaxMemory;
            nOptionNumber++;
            options[nOptionNumber].optionString = sDefaultStackSize;
            nOptionNumber++;
            options[nOptionNumber].optionString = sErrorFile;
            nOptionNumber++;
            options[nOptionNumber].optionString = sHeapDumpPath;
            nOptionNumber++;
            options[nOptionNumber].optionString = sShowMessageBoxOnError;
            nOptionNumber++;
            assert(nOptionNumber <= nMaxOptionNumber);
            vm_iargs.version = JNI_VERSION_1_6;
            vm_iargs.options = options;
            vm_iargs.nOptions = nOptionNumber;
            vm_iargs.ignoreUnrecognized = JNI_FALSE;
            res = -1;
            // Erreur fatale si impossible de lancer Java
            if (res < 0)
            {
                Global::AddFatalError("Java", "", "Can't create Java VM");
                return NULL;
            }
    
            // Verification de la compatibilite des moteurs d'interface
            CheckUserInterfaceEngineCompatibility();
    
            // Enregistrement des methodes natives de GUIUnit
            cls = GraphicGetGUIUnitID();
            res = env->RegisterNatives(cls, jniGUIUnitMethodArray,
                sizeof(jniGUIUnitMethodArray) / sizeof(JNINativeMethod));
            if (res != 0)
                Global::AddFatalError("Java", "GUIUnit",
                "Can't register native methods");
    
            // Enregistrement des methodes natives de GUIList
            cls = GraphicGetGUIListID();
            res = env->RegisterNatives(cls, jniGUIListMethodArray,
                sizeof(jniGUIListMethodArray) / sizeof(JNINativeMethod));
            if (res != 0)
                Global::AddFatalError("Java", "GUIList",
                "Can't register native methods");
    
            env->DeleteLocalRef(cls);
            setlocale(LC_ALL, sCurrentLocale);
        }
    
        vm_aargs.version = JNI_VERSION_1_6;
        vm_aargs.name = NULL;
        vm_aargs.group = NULL;
        res = jvm->AttachCurrentThread(&env, &vm_aargs);
        if (res < 0)
            Global::AddFatalError("Java", "","Unable to attach current thread to Java VM");
    cout << "uiobject.cpp 1057\n";
        // Recherche de la classe JFrame pour savoir si on a acces au GUI
        cls = env->FindClass("javax/swing/JFrame");
        cout << "uiobject.cpp 1060 ->" << cls<<"\n";
        if (cls == 0)
            Global::AddFatalError("Java", "GUI", "No GUI available under current environment, try using the tool in batch mode (-b)");
    cout << "uiobject.cpp 1062\n";
        // Recherche de la classe GUIObject pour verifier la presence de la librairie norm.jar
        cls = env->FindClass("normGUI/engine/GUIObject");
        cout << "uiobject.cpp 1068 ->" << cls<<"\n";
        if (cls == 0)
        {
    #ifdef __UNIX__
            ALString sDisplay;
            sDisplay = p_getenv("DISPLAY");
            if (sDisplay.IsEmpty())
                Global::AddFatalError("Java", "GUI", "No GUI (X server) available under current environment, try using the tool in batch mode (-b)");
            else
                Global::AddFatalError("Java", "Library", "Please check that Java version is at least Java 7, that the X server is available under current environment and check access to the norm.jar library in the Java classpath");
    #else
            Global::AddFatalError("Java", "Library", "Please check that Java version is at least Java 7 and check access to the norm.jar library in the Java classpath");
    #endif
        }
    
    
        return env;
    }
    

    Donc mon soucis se place au niveau de la ligne "cls = env->FindClass("javax/swing/JFrame");"

  • OimyOimy Membre
    13 juin modifié #10

    @Lexxis a dit :
    J'ai cependant déporté la création de la JVM dans un nouveau thread (autre que le principal - j'avais lu un tech note Apple à ce sujet qui avait un rapport entre la JVM, AWT de l'implémentation d'Apple - qui est maintenant une implémentation Oracle).

    Je suis assez novice dans les threads, j'espère ne pas dire une connerie :
    Tu as créer un nouveau thread de cette façon ?

    #include <thread>
    #include <mutex>
    void new_thread()
    {
        ... création JVM
    }
    
    int main()
    {
    std::thread t1(new_thread);
    }
    

    J'ai aussi linker (@rpath) la lib jli en plus de la lib jvm. Ceci car apparemment il y a une "interception" de l'appel à la création de la VM par la lib Apple qui affiche une alerte indiquant qu'il n'y a pas d'implémentation JAVA installé et qui redirige vers "une ancienne" installation 1.6 (ceci sans même être linker avec le framework JAVA Apple).

    Sinon dans mon fichier de compile j'ai déjà les options -ljli -ljvm -lobjc

  • LexxisLexxis Membre

    Pour la création du thread, j'utilise les fonctions ** pthread_xxx**. Mais quelque soit la façon de le créer le thread, le principe est le même (à noté que non seulement la création de la JVM est faite dans ce nouveau thread mais aussi l'appel à la classe)

    Pour la classe JFrame je viens de faire un test rapide (ci dessous) à partir du code que j'utilise et je n'ai pas eu de soucis majeur. Je n'ai pas modifié l'option classpath et je n'ai même pas de variables CLASSPATH.

    jclass cls3 = env->FindClass("javax/swing/JFrame");
      if (cls3 != nullptr) {
        jmethodID method = env->GetStaticMethodID(cls3, "isDefaultLookAndFeelDecorated", "()Z");
        if (method != nullptr) {
          jboolean result = env->CallBooleanMethod(cls3, method);
          printf("result = %d", result);
        }
      } else {
        printf("Unable to find JFrame");
      }
    

    Cela doit sûrement venir de la config j'imagine...

  • OimyOimy Membre
    13 juin modifié #12

    Ok récapitulons, moi j'ai ceci comme code :

    include <cstring>
    include <objc/objc-runtime.h>
    include <thread>
    include <mutex>
    void runCocoaMain(void)
    {
        id clazz = (id) objc_getClass("NSApplication");
        printf("runCocoaMain 1.\n");
        id _Nullable app = objc_msgSend(clazz, sel_registerName("sharedApplication"));
        printf("runCocoaMain 2.\n");
        objc_msgSend(app, sel_registerName("open"));
    }
    
    
    void display()
    {
        JavaVMOption options[1]; // A list of options to build a JVM from C++
        static JNIEnv *env_test=NULL;
        static JavaVM *jvm_test=NULL;
        JavaVMInitArgs vm_args; // Arguments for the JVM (see below)
        jclass cls;
        jmethodID constructor;
        jobject simpleJNITestInstance;
        options[0].optionString = strdup("-Djava.class.path=.");
        vm_args.version = JNI_VERSION_1_8;
        vm_args.nOptions = 1;
        vm_args.options = options;
        long status = JNI_CreateJavaVM(&jvm_test, (void**)&env_test, &vm_args);
    
        if (status != JNI_ERR) {
            cout << "JVM load succeeded. \nVersion ";
        jint ver = env_test->GetVersion();
        cout << ((ver >> 16) & 0x0f) << "." << (ver & 0x0f) << endl;
    
            cls = env_test->FindClass("MyTest2");
            if( cls == NULL ) {
                if( env_test->ExceptionOccurred() )
                    env_test->ExceptionDescribe();
                else
                    printf("Can't find MainClass class.\n");
                jvm_test->DestroyJavaVM();
    
            } else
                printf("Found Main class.\n");
    
            jmethodID mid = env_test->GetStaticMethodID(cls, "run", "()V");  // find method
            if(mid == nullptr)
                cerr << "ERROR: method void mymain() not found !" << endl;
            else {
                cout << "===Call to java====" << endl; 
                 env_test->CallStaticVoidMethod(cls, mid);                      // call method
                 runCocoaMain();
                cout << "===End of call to java==========="<<endl;
                }
    
            jvm_test->DestroyJavaVM();
            printf("All done, bye bye!\n");
        }
    }
    
    int UIObject::TestJava()
    {
         std::thread t1(display);
        t1.join();
            return -1;
    }
    

    Je me retrouve quand même avec les affichages suivants :
    JVM load succeeded.
    Version 1.8
    Found Main class.
    ===Call to java===
    runCocoaMain 1.
    Going to put something on the AWT queue.
    A fatal error has been detected by the Java Runtime Environment:
    SIGSEGV (0xb) at pc=0x00007fff6674c165, pid=53046, tid=0x0000000000001503
    JRE version: Java(TM) SE Runtime Environment (8.0_172-b11) (build 1.8.0_172-b11)
    Java VM: Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode bsd-amd64 compressed oops)
    Problematic frame:
    C [SkyLight+0x20a165] _ZN12_GLOBAL__N_112get_registryEv.8847+0x33
    Core dump written. Default location: /cores/core or core.53046
    An error report file with more information is saved as:
    /Users/obruneteau/Desktop/projet/total_test/hs_err_pid53046.log
    If you would like to submit a bug report, please visit:
    http://bugreport.java.com/bugreport/crash.jsp
    The crash happened outside the Java Virtual Machine in native code.
    See problematic frame for where to report the bug.

    Mon fichier de compilation ressemble à cela :
    clang++ -std=c++11 BaseTest.cpp UITest.cpp UITestClassSpecArrayView.cpp DiversTests.cpp UITestClassSpec.cpp UITestClassSpecView.cpp ALString.cpp ManagedObjectTable.cpp Standard.cpp UIList.cpp BufferedFile.cpp MemVector.cpp SystemResource.cpp UIObject.cpp CharVector.cpp MemoryManager.cpp TableServices.cpp UIObjectArrayView.cpp Ermgt.cpp Menu.cpp TaskProgression.cpp UIObjectView.cpp FileBuffer.cpp Obarray.cpp TaskProgressionManager.cpp UITaskProgression.cpp FileService.cpp Obdic.cpp Timer.cpp UIUnit.cpp InputBufferedFile.cpp Oblist.cpp Translation.cpp Vector.cpp LMLicenseService.cpp OutputBufferedFile.cpp UICard.cpp Longint.cpp Portability.cpp UIData.cpp ManagedObject.cpp SortedList.cpp UIElements.cpp -framework CoreFoundation -framework CoreServices -I "./" -I /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/include/darwin/ -L /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/server/ -I /Users/.../Desktop/projet/total_test/ -L /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jli/ -o main_test -ljli -ljvm -lobjc -D UNIX -D _CPPRTTI -D APPLE
    export DYLD_LIBRARY_PATH=/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jli/:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/server/:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/include/:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/include/darwin/:/Users/.../Desktop/projet/total_test/
    export CLASSPATH=/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/:/Users/.../Desktop/projet/total_test/
    export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/
    export PATH=$PATH:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin
    ./main_test

    Pourquoi moi j'ai toujours cette erreur ?

    Quand j'insère ton code à la place du mien ça reste sans réponse (pas de message d'erreur) au niveau du FindClass

  • OimyOimy Membre

    @Lexxis a dit :
    J'ai aussi linker (@rpath) la lib jli en plus de la lib jvm. Ceci car apparemment il y a une "interception" de l'appel à la création de la VM par la lib Apple qui affiche une alerte indiquant qu'il n'y a pas d'implémentation JAVA installé et qui redirige vers "une ancienne" installation 1.6 (ceci sans même être linker avec le framework JAVA Apple).

    Je suis revenu sur Xcode, Comment se fait le lien de la lib JLI et JVM sous Xcode ? C'est dans "Library search path" ?

  • LexxisLexxis Membre

    Ci joint mon projet de test. Peut être te sera-t-il utile.

    jvm.zip 73.4K
  • LexxisLexxis Membre

    Une remarque: l'appel de ta fonction runCocoaMain ne devrait pas être appelé dans le même thread que la création de la JVM. rnCocoaMain doit être appelé depuis la mainThread (et avant le t1.join() si cet appel est aussi réalisé sur le mainThread)

  • OimyOimy Membre

    Merci pour les infos, je vais regarder cela

  • Joanna CarterJoanna Carter Membre, Modérateur

    @Oimy S'il te plaît t'enseigner sur le Markdown pour rendre plus lisible ton code.

    On commence le code avec ``` sur la ligne avant le code et termine avec la même chose sur la ligne après.

    https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet

Connectez-vous ou Inscrivez-vous pour répondre.