Recherche…


Paramètres

Paramètre Détails
JNIEnv Pointeur vers l'environnement JNI
jobject L'objet qui a appelé la méthode native non static
jclass La classe qui a invoqué la méthode native static

Remarques

La configuration de JNI nécessite à la fois un compilateur Java et un compilateur natif. Selon l'IDE et le système d'exploitation, une configuration est requise. Un guide pour Eclipse peut être trouvé ici . Un tutoriel complet peut être trouvé ici .

Voici les étapes à suivre pour configurer le lien Java-C ++ sur Windows:

  • Compilez les fichiers source Java ( .java ) dans les classes ( .class ) en utilisant javac .
  • Créez des fichiers d'en-tête ( .h ) à partir des classes Java contenant native méthodes native utilisant javah . Ces fichiers "instruisent" le code natif dont il est responsable de l'implémentation.
  • Incluez les fichiers d'en-tête ( #include ) dans les fichiers source C ++ ( .cpp ) implémentant les méthodes native .
  • Compilez les fichiers source C ++ et créez une bibliothèque ( .dll ). Cette bibliothèque contient l'implémentation du code natif.
  • Spécifiez le chemin de la bibliothèque ( -Djava.library.path ) et chargez-le dans le fichier source Java ( System.loadLibrary(...) ).

Les rappels (appel des méthodes Java à partir du code natif) nécessitent de spécifier un descripteur de méthode. Si le descripteur est incorrect, une erreur d'exécution se produit. Pour cette raison, il est utile de faire les descripteurs pour nous, cela peut être fait avec javap -s .

Appel des méthodes C ++ à partir de Java

Les méthodes statiques et membres de Java peuvent être marquées comme natives pour indiquer que leur implémentation se trouve dans un fichier de bibliothèque partagé. Lors de l'exécution d'une méthode native, la JVM recherche une fonction correspondante dans les bibliothèques chargées (voir Chargement de bibliothèques natives ) en utilisant un simple schéma de gestion des noms, effectue la conversion des arguments et la pile.

Code Java

/*** com/example/jni/JNIJava.java **/

package com.example.jni;

public class JNIJava {
    static {
        System.loadLibrary("libJNI_CPP");
    }

    // Obviously, native methods may not have a body defined in Java
    public native void printString(String name);
    public static native double average(int[] nums);

    public static void main(final String[] args) {
        JNIJava jniJava = new JNIJava();
        jniJava.printString("Invoked C++ 'printString' from Java");

        double d = average(new int[]{1, 2, 3, 4, 7});
        System.out.println("Got result from C++ 'average': " + d);
    }
}

Code C ++

Les fichiers d'en-tête contenant des déclarations de fonctions natives doivent être générés à l'aide de l'outil javah sur les classes cibles. Exécuter la commande suivante dans le répertoire de construction:

javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava

... produit le fichier d'en-tête suivant ( commentaires supprimés pour des raisons de concision ):

// com_example_jni_JNIJava.hpp

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h> // The JNI API declarations

#ifndef _Included_com_example_jni_JNIJava
#define _Included_com_example_jni_JNIJava
#ifdef __cplusplus
extern "C" { // This is absolutely required if using a C++ compiler
#endif

JNIEXPORT void JNICALL Java_com_example_jni_JNIJava_printString
  (JNIEnv *, jobject, jstring);

JNIEXPORT jdouble JNICALL Java_com_example_jni_JNIJava_average
  (JNIEnv *, jclass, jintArray);

#ifdef __cplusplus
}
#endif
#endif

Voici un exemple d'implémentation:

// com_example_jni_JNIJava.cpp

#include <iostream>
#include "com_example_jni_JNIJava.hpp"

using namespace std;

JNIEXPORT void JNICALL Java_com_example_jni_JNIJava_printString(JNIEnv *env, jobject jthis, jstring string) {
    const char *stringInC = env->GetStringUTFChars(string, NULL);
    if (NULL == stringInC)
        return;
    cout << stringInC << endl;
    env->ReleaseStringUTFChars(string, stringInC);
}

JNIEXPORT jdouble JNICALL Java_com_example_jni_JNIJava_average(JNIEnv *env, jclass jthis, jintArray intArray) {
    jint *intArrayInC = env->GetIntArrayElements(intArray, NULL);
    if (NULL == intArrayInC)
        return -1;
    jsize length = env->GetArrayLength(intArray);
    int sum = 0;
    for (int i = 0; i < length; i++) {
        sum += intArrayInC[i];
    }
    env->ReleaseIntArrayElements(intArray, intArrayInC, 0);
    return (double) sum / length;
}

Sortie

L'exécution de la classe d'exemple ci-dessus génère la sortie suivante:

C ++ appelé "printString" depuis Java
Vous avez obtenu le résultat de la moyenne C ++: 3.4

Appeler les méthodes Java à partir de C ++ (rappel)

L'appel d'une méthode Java à partir de code natif est un processus en deux étapes:

  1. obtenir un pointeur de méthode avec la fonction JNI GetMethodID , en utilisant le nom de la méthode et le descripteur;
  2. appelez l'une des fonctions de la Call*Method répertoriées ici .

Code Java

/*** com.example.jni.JNIJavaCallback.java ***/

package com.example.jni;

public class JNIJavaCallback {
    static {
        System.loadLibrary("libJNI_CPP");
    }
    
    public static void main(String[] args) {
        new JNIJavaCallback().callback();
    }

    public native void callback(); 

    public static void printNum(int i) {
        System.out.println("Got int from C++: " + i);
    }
    
    public void printFloat(float i) {
        System.out.println("Got float from C++: " + i);
    }
}

Code C ++

// com_example_jni_JNICppCallback.cpp

#include <iostream>
#include "com_example_jni_JNIJavaCallback.h"

using namespace std;

JNIEXPORT void JNICALL Java_com_example_jni_JNIJavaCallback_callback(JNIEnv *env, jobject jthis) {
    jclass thisClass = env->GetObjectClass(jthis);

    jmethodID printFloat = env->GetMethodID(thisClass, "printFloat", "(F)V");
    if (NULL == printFloat)
        return;
    env->CallVoidMethod(jthis, printFloat, 5.221);

    jmethodID staticPrintInt = env->GetStaticMethodID(thisClass, "printNum", "(I)V");
    if (NULL == staticPrintInt)
        return;
    env->CallVoidMethod(jthis, staticPrintInt, 17);
}

Sortie

Got float de C ++: 5.221
Got int de C ++: 17

Obtenir le descripteur

Les descripteurs (ou signatures de type internes ) sont obtenus à l'aide du programme javap sur le fichier .class compilé. Voici la sortie de javap -p -s com.example.jni.JNIJavaCallback :

Compiled from "JNIJavaCallback.java"
public class com.example.jni.JNIJavaCallback {
  static {};
    descriptor: ()V

  public com.example.jni.JNIJavaCallback();
    descriptor: ()V

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V

  public native void callback();
    descriptor: ()V

  public static void printNum(int);
    descriptor: (I)V  // <---- Needed

  public void printFloat(float);
    descriptor: (F)V  // <---- Needed
}

Chargement de bibliothèques natives

L'idiome commun pour charger des fichiers de bibliothèque partagée en Java est le suivant:

public class ClassWithNativeMethods {
    static {
        System.loadLibrary("Example");
    }

    public native void someNativeMethod(String arg);
    ...

Les appels à System.loadLibrary sont presque toujours statiques pour se produire lors du chargement de la classe, garantissant qu'aucune méthode native ne peut s'exécuter avant le chargement de la bibliothèque partagée. Cependant, ce qui suit est possible:

public class ClassWithNativeMethods {
    // Call this before using any native method
    public static void prepareNativeMethods() {
        System.loadLibrary("Example");
    }

    ...

Cela permet de différer le chargement de la bibliothèque partagée jusqu'à ce que cela soit nécessaire, mais nécessite un soin particulier pour éviter java.lang.UnsatisfiedLinkError s.

Recherche de fichier cible

Les fichiers de bibliothèque partagés sont recherchés dans les chemins définis par la propriété système java.library.path , qui peuvent être -Djava.library.path= l'aide de l'argument -Djava.library.path= JVM au moment de l'exécution:

java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods

Attention aux séparateurs de chemin système: par exemple, Windows utilise ; au lieu de :

Notez que System.loadLibrary résout les noms de fichiers de bibliothèque en fonction de la plate-forme: l'extrait de code ci-dessus attend un fichier nommé libExample.so sous Linux et Example.dll sous Windows.

Une alternative à System.loadLibrary est System.load(String) , qui prend le chemin d'accès complet à un fichier de bibliothèque partagée, contournant la recherche java.library.path :

public class ClassWithNativeMethods {
    static {
        System.load("/path/to/lib/libExample.so");
    }

    ...


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow