Java Language
Java Native Interface
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
) usandojavac
. - Crea
.h
intestazione (.h
) dalle classi Java che contengono metodinative
usandojavah
. 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 metodinative
. - 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:
- ottenere un puntatore del metodo con la funzione JNI
GetMethodID
, utilizzando il nome del metodo e il descrittore; - 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");
}
...