Ricerca…


Parametri

Parametro Dettagli
MEnv Puntatore all'ambiente JNI
jobject L'oggetto che ha invocato il metodo native non static
jclass La classe che ha invocato il metodo native static

Osservazioni

L'impostazione di JNI richiede sia un compilatore Java che nativo. A seconda dell'IDE e del sistema operativo, sono necessarie alcune impostazioni. Una guida per Eclipse può essere trovata qui . Un tutorial completo può essere trovato qui .

Questi sono i passaggi per configurare il collegamento Java-C ++ su windows:

  • Compilare i file di origine Java ( .java ) in classi ( .class ) usando javac .
  • Crea .h intestazione ( .h ) dalle classi Java che contengono metodi native usando javah . Questi file "istruiscono" il codice nativo su quali metodi è responsabile dell'implementazione.
  • Includere i file di intestazione ( #include ) nei file di origine C ++ ( .cpp ) che implementano i metodi native .
  • Compilare i file di origine C ++ e creare una libreria ( .dll ). Questa libreria contiene l'implementazione del codice nativo.
  • Specificare il percorso della libreria ( -Djava.library.path ) e caricarlo nel file di origine Java ( System.loadLibrary(...) ).

Callback (Chiamando i metodi Java dal codice nativo) richiede di specificare un descrittore del metodo. Se il descrittore non è corretto, si verifica un errore di runtime. Per questo javap -s è utile avere i descrittori creati per noi, questo può essere fatto con javap -s .

Chiamare i metodi C ++ da Java

I metodi statici e membri in Java possono essere contrassegnati come nativi per indicare che la loro implementazione si trova in un file di libreria condiviso. Al momento dell'esecuzione di un metodo nativo, la JVM cerca una funzione corrispondente nelle librerie caricate (vedi Caricamento librerie native ), usando un semplice schema di mangling dei nomi, esegue la conversione degli argomenti e l'impostazione dello stack, quindi passa il controllo al codice nativo.

Codice 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);
    }
}

Codice C ++

I file di intestazione contenenti dichiarazioni di funzioni native devono essere generati utilizzando lo strumento javah sulle classi di destinazione. Eseguendo il seguente comando nella directory di build:

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

... produce il seguente file di intestazione ( commenti ridotti per brevità ):

// 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

Ecco un esempio di implementazione:

// 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;
}

Produzione

L'esecuzione della classe di esempio sopra produce l'output seguente:

Invocato C ++ 'printString' da Java
Ottenuto risultato da 'media' C ++: 3.4

Chiamare i metodi Java da C ++ (callback)

La chiamata di un metodo Java da codice nativo è un processo in due fasi:

  1. ottenere un puntatore del metodo con la funzione JNI GetMethodID , utilizzando il nome del metodo e il descrittore;
  2. chiama una delle funzioni Call*Method elencate qui .

Codice 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);
    }
}

Codice 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);
}

Produzione

Ottenuto float da C ++: 5.221
Got int da C ++: 17

Ottenere il descrittore

I descrittori (o le firme dei tipi interni ) sono ottenuti utilizzando il programma javap nel file .class compilato. Ecco l'output di 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
}

Caricamento delle librerie native

L'idioma comune per caricare i file di libreria condivisi in Java è il seguente:

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

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

Le chiamate a System.loadLibrary sono quasi sempre statiche in modo da verificarsi durante il caricamento della classe, assicurando che nessun metodo nativo possa essere eseguito prima che la libreria condivisa sia stata caricata. Tuttavia è possibile:

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

    ...

Ciò consente di posticipare il caricamento della libreria condivisa finché necessario, ma richiede un'attenzione particolare per evitare java.lang.UnsatisfiedLinkError s.

Ricerca file di destinazione

I file della libreria condivisa vengono cercati nei percorsi definiti dalla proprietà di sistema java.library.path , che può essere sovrascritta utilizzando l'argomento -Djava.library.path= JVM in fase di runtime:

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

Attenzione ai separatori dei percorsi di sistema: ad esempio, Windows utilizza ; invece di :

Notare che System.loadLibrary risolve i nomi dei file di libreria in modo dipendente dalla piattaforma: lo snippet di codice sopra prevede un file denominato libExample.so su Linux e Example.dll su Windows.

Un'alternativa a System.loadLibrary è System.load(String) , che prende il percorso completo di un file di libreria condivisa, aggirando la ricerca 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow