Java Language
Interfaz nativa de Java
Buscar..
Parámetros
Parámetro | Detalles |
---|---|
JNIEnv | Puntero al entorno JNI. |
proyecto de trabajo | El objeto que invocó el método native no static |
jclass | La clase que invocó el método native static |
Observaciones
La configuración de JNI requiere tanto de Java como de un compilador nativo. Dependiendo del IDE y el sistema operativo, se requiere alguna configuración. Una guía para Eclipse se puede encontrar aquí . Un tutorial completo se puede encontrar aquí .
Estos son los pasos para configurar el enlace Java-C ++ en Windows:
- Compile los archivos fuente de Java (
.java
) en clases (.class
) usandojavac
. - Cree archivos de encabezado (
.h
) a partir de las clases de Java que contienen métodosnative
utilizandojavah
. Estos archivos "instruyen" al código nativo de qué métodos es responsable de implementar. - Incluya los archivos de encabezado (
#include
) en los archivos fuente de C ++ (.cpp
) que implementan los métodosnative
. - Compile los archivos fuente de C ++ y cree una biblioteca (
.dll
). Esta biblioteca contiene la implementación del código nativo. - Especifique la ruta de la biblioteca (
-Djava.library.path
) y-Djava.library.path
en el archivo fuente de Java (System.loadLibrary(...)
).
Las devoluciones de llamada (Llamar a métodos Java desde código nativo) requieren especificar un descriptor de método. Si el descriptor es incorrecto, se produce un error de tiempo de ejecución. Debido a esto, es útil tener los descriptores creados para nosotros, esto se puede hacer con javap -s
.
Llamando a los métodos de C ++ desde Java
Los métodos estáticos y de miembro en Java se pueden marcar como nativos para indicar que su implementación se encuentra en un archivo de biblioteca compartida. Tras la ejecución de un método nativo, la JVM busca una función correspondiente en las bibliotecas cargadas (consulte Cómo cargar bibliotecas nativas ), utilizando un esquema simple de identificación de nombres, realiza la conversión de argumentos y la configuración de la pila, luego entrega el control al código nativo.
Código 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ódigo C ++
Los archivos de encabezado que contienen declaraciones de funciones nativas deben generarse utilizando la herramienta javah
en las clases de destino. Ejecutando el siguiente comando en el directorio de compilación:
javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava
... produce el siguiente archivo de encabezado ( comentarios eliminados por brevedad ):
// 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
Aquí hay una implementación de ejemplo:
// 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;
}
Salida
Al ejecutar la clase de ejemplo anterior se obtiene el siguiente resultado:
Invocó C ++ 'printString' desde Java
Resultado obtenido de C ++ 'promedio': 3.4
Llamando a métodos Java desde C ++ (callback)
Llamar a un método Java desde un código nativo es un proceso de dos pasos:
- obtenga un puntero de método con la función JNI de
GetMethodID
, utilizando el nombre y el descriptor del método; - llame a una de las funciones del
Call*Method
enumeradas aquí .
Código 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ódigo 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);
}
Salida
Consiguió flotar desde C ++: 5.221
Obtuvo int desde C ++: 17
Obteniendo el descriptor
Los descriptores (o firmas de tipo interno ) se obtienen utilizando el programa javap en el archivo .class
compilado. Aquí está la salida de 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
}
Cargando bibliotecas nativas
El idioma común para cargar archivos de biblioteca compartida en Java es el siguiente:
public class ClassWithNativeMethods {
static {
System.loadLibrary("Example");
}
public native void someNativeMethod(String arg);
...
Las llamadas a System.loadLibrary
son casi siempre estáticas para que ocurran durante la carga de clases, asegurando que ningún método nativo pueda ejecutarse antes de que se haya cargado la biblioteca compartida. Sin embargo, lo siguiente es posible:
public class ClassWithNativeMethods {
// Call this before using any native method
public static void prepareNativeMethods() {
System.loadLibrary("Example");
}
...
Esto permite diferir la carga de la biblioteca compartida hasta que sea necesario, pero requiere un cuidado especial para evitar java.lang.UnsatisfiedLinkError
s.
Búsqueda de archivos de destino
Los archivos de la biblioteca compartida se buscan en las rutas definidas por la propiedad del sistema java.library.path
, que se pueden anular utilizando el argumento -Djava.library.path=
JVM en el tiempo de ejecución:
java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods
Tenga cuidado con los separadores de ruta del sistema: por ejemplo, Windows usa ;
en lugar de :
Tenga en cuenta que System.loadLibrary
resuelve los nombres de archivo de la biblioteca de una manera dependiente de la plataforma: el fragmento de código anterior espera un archivo llamado libExample.so
en Linux y Example.dll
en Windows.
Una alternativa a System.loadLibrary
es System.load(String)
, que toma la ruta completa a un archivo de biblioteca compartida, evitando la búsqueda java.library.path
:
public class ClassWithNativeMethods {
static {
System.load("/path/to/lib/libExample.so");
}
...