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 ) usando javac .
  • Cree archivos de encabezado ( .h ) a partir de las clases de Java que contienen métodos native utilizando javah . 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étodos native .
  • 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:

  1. obtenga un puntero de método con la función JNI de GetMethodID , utilizando el nombre y el descriptor del método;
  2. 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");
    }

    ...


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow