Java Language
Java native interface
Zoeken…
parameters
Parameter | Details |
---|---|
JNIEnv | Wijzer naar de JNI-omgeving |
jobject | Het object dat de niet- static native methode heeft aangeroepen |
jclass | De klasse die de static native methode heeft aangeroepen |
Opmerkingen
Het instellen van JNI vereist zowel een Java- als een native compiler. Afhankelijk van de IDE en het besturingssysteem is een aantal instellingen vereist. Een gids voor Eclipse kan worden gevonden hier . Een volledige handleiding is te vinden hier .
Dit zijn de stappen voor het instellen van de Java-C ++ koppeling op Windows:
- Compileer de Java-bronbestanden (
.java
) in klassen (.class
) met behulp vanjavac
. - Maak header (
.h
) bestanden van de Java-klassen metnative
methoden waarbijjavah
. Deze bestanden "instrueren" de native code welke methoden verantwoordelijk zijn voor de implementatie. - Neem de header-bestanden (
#include
) op in de C ++ bronbestanden (.cpp
) die denative
methoden implementeren. - Compileer de C ++ bronbestanden en maak een bibliotheek (
.dll
). Deze bibliotheek bevat de implementatie van de native code. - Geef het bibliotheekpad op (
-Djava.library.path
) en laad het in het Java-bronbestand (System.loadLibrary(...)
).
Voor callbacks (oproepen van Java-methoden vanuit native code) moet een methodebeschrijving worden opgegeven. Als de descriptor onjuist is, treedt een runtime-fout op. Daarom is het handig om de descriptors voor ons te laten maken, dit kan met javap -s
.
C ++ methoden aanroepen vanuit Java
Statische en lidmethoden in Java kunnen worden gemarkeerd als native om aan te geven dat hun implementatie te vinden is in een gedeeld bibliotheekbestand. Bij uitvoering van een native methode zoekt de JVM naar een overeenkomstige functie in geladen bibliotheken (zie Inheemse bibliotheken laden ), met behulp van een eenvoudig schema voor naammenging, voert argumentconversie en stapelconfiguratie uit en draagt vervolgens de controle over aan native code.
Java-code
/*** 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);
}
}
C ++ code
Koptekstbestanden met native functieverklaringen moeten worden gegenereerd met behulp van de javah
tool op doelklassen. De volgende opdracht uitvoeren in de build-map:
javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava
... produceert het volgende koptekstbestand ( beknopt commentaar vanwege beknoptheid ):
// 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
Hier is een voorbeeldimplementatie:
// 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;
}
uitgang
Het uitvoeren van de bovenstaande voorbeeldklasse levert de volgende uitvoer op:
C ++ 'printString' aangeroepen vanuit Java
Kregen resultaat van C ++ 'gemiddeld': 3.4
Java-methoden oproepen vanuit C ++ (callback)
Het aanroepen van een Java-methode vanuit native code is een proces in twee stappen:
- verkrijg een methode pointer met de
GetMethodID
JNI functie, met behulp van de methode naam en descriptor; - bel een van de hier genoemde
Call*Method
functies.
Java-code
/*** 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);
}
}
C ++ code
// 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);
}
uitgang
Gekregen float van C ++: 5.221
Got int van C ++: 17
De descriptor verkrijgen
Beschrijvingen (of interne typeaanduidingen ) worden verkregen met behulp van het javap- programma in het gecompileerde .class
bestand. Hier is de uitvoer van 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
}
Inheemse bibliotheken laden
Het algemene idioom voor het laden van gedeelde bibliotheekbestanden in Java is het volgende:
public class ClassWithNativeMethods {
static {
System.loadLibrary("Example");
}
public native void someNativeMethod(String arg);
...
Oproepen naar System.loadLibrary
zijn bijna altijd statisch, zodat ze tijdens het laden van de klas kunnen voorkomen, zodat geen native methode kan worden uitgevoerd voordat de gedeelde bibliotheek is geladen. Het volgende is echter mogelijk:
public class ClassWithNativeMethods {
// Call this before using any native method
public static void prepareNativeMethods() {
System.loadLibrary("Example");
}
...
Dit maakt het mogelijk het laden van gedeelde bibliotheken uit te stellen totdat dit nodig is, maar vereist extra zorg om java.lang.UnsatisfiedLinkError
s te voorkomen.
Doelbestand opzoeken
Er wordt gezocht naar gedeelde bibliotheekbestanden in de paden die zijn gedefinieerd door de java.library.path
, die tijdens runtime kunnen worden overschreven met het argument -Djava.library.path=
JVM:
java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods
Pas op voor systeempadscheidingstekens: bijvoorbeeld Windows gebruikt ;
in plaats van :
Merk op dat System.loadLibrary
bibliotheekbestandsnamen op een platformafhankelijke manier oplost: het bovenstaande codefragment verwacht een bestand met de naam libExample.so
op Linux en Example.dll
op Windows.
Een alternatief voor System.loadLibrary
is System.load(String)
, die het volledige pad naar een gedeeld bibliotheekbestand neemt en de opzoeking java.library.path
omzeilt:
public class ClassWithNativeMethods {
static {
System.load("/path/to/lib/libExample.so");
}
...