Java Language
Интерфейс Java Native
Поиск…
параметры
параметр | подробности |
---|---|
JNIEnv | Указатель на среду JNI |
jobject | Объект , который ссылается на не- static native метода |
JClass | Класс, который вызвал static native метод |
замечания
Для настройки JNI требуется как Java, так и собственный компилятор. В зависимости от IDE и ОС требуется некоторая настройка. Руководство для Eclipse можно найти здесь . Полный учебник можно найти здесь .
Это шаги для настройки связи Java-C ++ в окнах:
- Скомпилируйте исходные файлы Java (
.java
) в классы (.class
) с помощьюjavac
. - Создайте файлы заголовка (
.h
) из классов Java, содержащихnative
методы, используяjavah
. Эти файлы «инструктируют» собственный код, какие методы он отвечает за реализацию. - Включите файлы заголовков (
#include
) в исходные файлы C ++ (.cpp
), реализующиеnative
методы. - Скомпилируйте исходные файлы C ++ и создайте библиотеку (
.dll
). Эта библиотека содержит реализацию собственного кода. - Укажите путь к библиотеке (
-Djava.library.path
) и загрузите его в исходный файл Java (System.loadLibrary(...)
).
Обратные вызовы (вызов методов Java из собственного кода) требуют указания дескриптора метода. Если дескриптор неверен, возникает ошибка времени выполнения. Из-за этого полезно иметь дескрипторы для нас, это можно сделать с помощью javap -s
.
Вызов методов C ++ из Java
Статические и членские методы в Java могут быть помечены как родные, чтобы указать, что их реализация должна быть найдена в файле общей библиотеки. После выполнения собственного метода JVM ищет соответствующую функцию в загружаемых библиотеках (см. Загрузка собственных библиотек ), используя простую схему переключения имен, выполняет преобразование аргументов и установку стека, а затем передает управление собственному коду.
Код 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);
}
}
Код C ++
Заголовочные файлы, содержащие декларации javah
функций, должны быть сгенерированы с использованием инструмента javah
для целевых классов. Выполнение следующей команды в каталоге сборки:
javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava
... создает следующий заголовочный файл ( комментарии для краткости ):
// 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
Вот пример реализации:
// 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;
}
Выход
Запуск класса example выше дает следующий результат:
Вызывается C ++ 'printString' из Java
Получил результат от C ++ «средний»: 3.4
Вызов методов Java из C ++ (callback)
Вызов метода Java из собственного кода - это двухэтапный процесс:
- получить указатель метода с
GetMethodID
, используя имя и дескриптор метода; - вызовите одну из функций
Call*Method
перечисленных здесь .
Код 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);
}
}
Код 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);
}
Выход
Поплавок с C ++: 5.221
Получил int из C ++: 17
Получение дескриптора
Дескрипторы (или сигнатуры внутреннего типа ) получают с помощью javap- программы в скомпилированном файле .class
. Вот результат работы 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
}
Загрузка собственных библиотек
Общая идиома для загрузки файлов общей библиотеки в Java:
public class ClassWithNativeMethods {
static {
System.loadLibrary("Example");
}
public native void someNativeMethod(String arg);
...
Вызовы в System.loadLibrary
почти всегда статичны, чтобы происходить во время загрузки класса, гарантируя, что собственный метод не может быть запущен до того, как загружена общая библиотека. Однако возможно следующее:
public class ClassWithNativeMethods {
// Call this before using any native method
public static void prepareNativeMethods() {
System.loadLibrary("Example");
}
...
Это позволяет отложить загрузку общей библиотеки до тех пор, пока это не будет необходимо, но требует дополнительной осторожности, чтобы избежать java.lang.UnsatisfiedLinkError
s.
Поиск целевого файла
Поиск общих файлов библиотеки осуществляется в путях, определенных системным свойством java.library.path
, которые могут быть переопределены с помощью аргумента -Djava.library.path=
JVM во время выполнения:
java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods
Следите за разделителями системных путей: например, Windows использует ;
а не :
Обратите внимание, что System.loadLibrary
разрешает имена файлов библиотек зависимым от платформы образом: фрагмент кода выше ожидает файл с именем libExample.so
в Linux и Example.dll
в Windows.
Альтернативой System.loadLibrary
является System.load(String)
, которая переносит полный путь к файлу общей библиотеки, обходя поиск в java.library.path
:
public class ClassWithNativeMethods {
static {
System.load("/path/to/lib/libExample.so");
}
...