Java Language
Java Native Interface
Szukaj…
Parametry
Parametr | Detale |
---|---|
JNIEnv | Wskaźnik do środowiska JNI |
Jobject | Obiekt, który wywołał static metodę native |
jclass | Klasa, która wywołała static metodę native |
Uwagi
Konfigurowanie JNI wymaga zarówno Java, jak i rodzimego kompilatora. W zależności od IDE i systemu operacyjnego wymagana jest pewna konfiguracja. Przewodnik po Eclipse można znaleźć tutaj . Pełny samouczek można znaleźć tutaj .
Oto kroki konfigurowania połączenia Java-C ++ w systemie Windows:
- Skompiluj pliki źródłowe Java (
.java
) w klasy (.class
) przy użyciujavac
. - Utwórz pliki nagłówka (
.h
) z klas Java zawierających metodynative
przy użyciujavah
. Pliki te „instruują” natywny kod, które metody są odpowiedzialne za implementację. - Dołącz pliki nagłówkowe (
#include
) do plików źródłowych C ++ (.cpp
) implementujących metodynative
. - Skompiluj pliki źródłowe C ++ i utwórz bibliotekę (
.dll
). Ta biblioteka zawiera natywną implementację kodu. - Podaj ścieżkę do biblioteki (
-Djava.library.path
) i załaduj ją do pliku źródłowego Java (System.loadLibrary(...)
).
Oddzwanianie (wywoływanie metod Java z kodu macierzystego) wymaga określenia deskryptora metody. Jeśli deskryptor jest niepoprawny, występuje błąd czasu wykonywania. Z tego powodu pomocne jest stworzenie dla nas deskryptorów, można to zrobić za pomocą javap -s
.
Wywoływanie metod C ++ z Java
Metody statyczne i składowe w Javie można oznaczyć jako rodzime, aby wskazać, że ich implementacja znajduje się we wspólnym pliku biblioteki. Po wykonaniu metody natywnej JVM szuka odpowiedniej funkcji w załadowanych bibliotekach (patrz Ładowanie bibliotek natywnych ), używając prostego schematu manglingu nazw, wykonuje konwersję argumentów i konfigurację stosu, a następnie przekazuje kontrolę nad kodem natywnym.
Kod 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);
}
}
Kod C ++
Pliki nagłówkowe zawierające deklaracje funkcji rodzimych powinny być generowane przy użyciu narzędzia javah
dla klas docelowych. Uruchom następujące polecenie w katalogu kompilacji:
javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava
... tworzy następujący plik nagłówka ( komentarze są usuwane ze względu na zwięzłość ):
// 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
Oto przykładowa implementacja:
// 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;
}
Wynik
Uruchomienie powyższej klasy przykładowej daje następujące wyniki:
Wywołano C ++ „printString” z Java
Otrzymałem wynik z „średniej” C ++: 3.4
Wywoływanie metod Java z C ++ (callback)
Wywołanie metody Java z kodu natywnego jest procesem dwuetapowym:
- uzyskać wskaźnik metody za pomocą funkcji
GetMethodID
JNI, używając nazwy metody i deskryptora; - wywołaj jedną z funkcji
Call*Method
wymienionych tutaj .
Kod 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);
}
}
Kod 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);
}
Wynik
Mam zmiennoprzecinkowe z C ++: 5.221
Mam int z C ++: 17
Uzyskiwanie deskryptora
Deskryptory (lub podpisy typu wewnętrznego ) są uzyskiwane za pomocą programu javap w skompilowanym pliku .class
. Oto dane wyjściowe 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
}
Ładowanie rodzimych bibliotek
Typowy idiom do ładowania plików bibliotek współdzielonych w Javie jest następujący:
public class ClassWithNativeMethods {
static {
System.loadLibrary("Example");
}
public native void someNativeMethod(String arg);
...
Wywołania System.loadLibrary
są prawie zawsze statyczne, aby występowały podczas ładowania klas, zapewniając, że żadna metoda natywna nie będzie mogła zostać wykonana przed załadowaniem biblioteki współdzielonej. Możliwe są jednak:
public class ClassWithNativeMethods {
// Call this before using any native method
public static void prepareNativeMethods() {
System.loadLibrary("Example");
}
...
Pozwala to odroczyć ładowanie biblioteki współużytkowanej, dopóki nie będzie to konieczne, ale wymaga dodatkowej uwagi, aby uniknąć java.lang.UnsatisfiedLinkError
.
Wyszukiwanie pliku docelowego
Pliki bibliotek współużytkowanych są wyszukiwane w ścieżkach zdefiniowanych we właściwości systemowej java.library.path
, które można zastąpić za pomocą argumentu -Djava.library.path=
JVM w czasie wykonywania:
java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods
Uważaj na separatory ścieżek systemowych: na przykład Windows używa ;
zamiast :
.
Zauważ, że System.loadLibrary
rozwiązuje nazwy plików bibliotecznych w sposób zależny od platformy: urywek kodu powyżej spodziewa się plik o nazwie libExample.so
na Linuksie, a Example.dll
na Windows.
Alternatywą dla System.loadLibrary
jest System.load(String)
, który pobiera pełną ścieżkę do pliku biblioteki współużytkowanej, omijając wyszukiwanie java.library.path
:
public class ClassWithNativeMethods {
static {
System.load("/path/to/lib/libExample.so");
}
...