En diseño de software , la interfaz nativa de Java ( JNI ) es un marco de programación de interfaz de función externa que permite que el código Java que se ejecuta en una máquina virtual Java (JVM) llame y sea llamado por [1] aplicaciones nativas (programas específicos de una plataforma de hardware y sistema operativo ) y bibliotecas escritas en otros lenguajes como C , C++ y ensamblador .
JNI permite a los programadores escribir métodos nativos para manejar situaciones en las que una aplicación no puede escribirse completamente en el lenguaje de programación Java, por ejemplo, cuando la biblioteca de clases estándar de Java no admite las características específicas de la plataforma o la biblioteca de programas. También se utiliza para modificar una aplicación existente (escrita en otro lenguaje de programación) para que sea accesible para las aplicaciones Java. Muchas de las clases de la biblioteca estándar dependen de JNI para proporcionar funcionalidad al desarrollador y al usuario, por ejemplo, E/S de archivos y capacidades de sonido. La inclusión de implementaciones de API sensibles al rendimiento y a la plataforma en la biblioteca estándar permite que todas las aplicaciones Java accedan a esta funcionalidad de una manera segura e independiente de la plataforma.
El marco JNI permite que un método nativo utilice objetos Java de la misma manera que el código Java utiliza estos objetos. Un método nativo puede crear objetos Java y luego inspeccionar y utilizar estos objetos para realizar sus tareas. Un método nativo también puede inspeccionar y utilizar objetos creados por el código de la aplicación Java.
Sólo las aplicaciones y los subprogramas firmados pueden invocar JNI.
Una aplicación que depende de JNI pierde la portabilidad de plataforma que ofrece Java (una solución parcial es escribir una implementación separada del código JNI para cada plataforma y hacer que Java detecte el sistema operativo y cargue el correcto en tiempo de ejecución).
El código nativo no solo puede interactuar con Java, sino que también puede dibujar en un código Java Canvas
, lo que es posible con la interfaz nativa Java AWT . El proceso es casi el mismo, con solo unos pocos cambios. La interfaz nativa Java AWT solo está disponible desde J2SE 1.3.
JNI también permite el acceso directo al código ensamblador , sin siquiera pasar por un puente C. [2] El acceso a aplicaciones Java desde el ensamblador es posible de la misma manera. [3]
En el marco de JNI, las funciones nativas se implementan en archivos .c o .cpp independientes. (C++ proporciona una interfaz ligeramente más simple con JNI). Cuando la JVM invoca la función, pasa un JNIEnv
puntero, un jobject
puntero y cualquier argumento Java declarado por el método Java. Por ejemplo, lo siguiente convierte una cadena Java en una cadena nativa:
externo "C" JNIEXPORT void JNICALL Java_ClassName_MethodName ( JNIEnv * env , jobject obj , jstring javaString ) { const char * NativeString = env -> GetStringUTFChars ( javaString , 0 ); //Hacer algo con la cadena nativa env -> ReleaseStringUTFChars ( javaString , nativeString ); }
El env
puntero es una estructura que contiene la interfaz con la JVM. Incluye todas las funciones necesarias para interactuar con la JVM y trabajar con objetos Java. Algunos ejemplos de funciones JNI son la conversión de matrices nativas a/desde matrices Java, la conversión de cadenas nativas a/desde cadenas Java, la creación de instancias de objetos, el lanzamiento de excepciones, etc. Básicamente, todo lo que puede hacer el código Java se puede hacer usando JNIEnv
, aunque con mucha menos facilidad.
El argumento obj
es una referencia al objeto Java dentro del cual se ha declarado este método nativo.
Los tipos de datos nativos se pueden asignar a/desde tipos de datos Java. Para tipos compuestos como objetos, matrices y cadenas, el código nativo debe convertir explícitamente los datos llamando a métodos en el JNIEnv
.
Un puntero de entorno JNI ( JNIEnv* ) se pasa como argumento para cada función nativa asignada a un método Java, lo que permite la interacción con el entorno JNI dentro del método nativo. Este puntero de interfaz JNI se puede almacenar, pero sigue siendo válido solo en el hilo actual. Otros hilos deben llamar primero a AttachCurrentThread() para adjuntarse a la VM y obtener un puntero de interfaz JNI. Una vez adjunto, un hilo nativo funciona como un hilo Java normal que se ejecuta dentro de un método nativo. El hilo nativo permanece adjunto a la VM hasta que llama a DetachCurrentThread() para separarse. [4]
El marco JNI no proporciona ninguna recolección automática de basura para los recursos de memoria que no sean de JVM asignados por el código que se ejecuta en el lado nativo. En consecuencia, el código del lado nativo (como el lenguaje ensamblador) asume la responsabilidad de liberar explícitamente dichos recursos de memoria que adquiera el código nativo.
En las plataformas Linux y Solaris, si el código nativo se registra a sí mismo como un controlador de señales, podría interceptar las señales destinadas a la JVM. Se puede utilizar una cadena de responsabilidad para permitir que el código nativo interactúe mejor con la JVM. En las plataformas Windows, se puede emplear el Manejo de excepciones estructurado (SEH) para envolver el código nativo en bloques try/catch de SEH a fin de capturar interrupciones de software generadas por la máquina (CPU/FPU) (como violaciones de acceso a punteros NULL y operaciones de división por cero) y para manejar estas situaciones antes de que la interrupción se propague nuevamente a la JVM (es decir, código del lado de Java), lo que con toda probabilidad resultará en una excepción no manejada. [ ¿Investigación original? ]
La codificación utilizada para las funciones NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars y GetStringUTFRegion es "UTF-8 modificado", [5] que no es UTF-8 válido para todas las entradas, sino una codificación diferente en realidad. El carácter nulo (U+0000) y los puntos de código que no están en el plano multilingüe básico (mayores o iguales a U+10000, es decir, aquellos representados como pares sustitutos en UTF-16) se codifican de manera diferente en UTF-8 modificado. Muchos programas usan estas funciones de manera incorrecta y tratan las cadenas UTF-8 devueltas o pasadas a las funciones como cadenas UTF-8 estándar en lugar de cadenas UTF-8 modificadas. Los programas deben utilizar las funciones NewString, GetStringLength, GetStringChars, ReleaseStringChars, GetStringRegion, GetStringCritical y ReleaseStringCritical, que utilizan codificación UTF-16LE en arquitecturas little-endian y UTF-16BE en arquitecturas big-endian, y luego utilizar una rutina de conversión de UTF-16 a UTF-8. [ investigación original? ]
La siguiente tabla muestra el mapeo de tipos entre Java (JNI) y el código nativo.
Además, la firma "L fully-qualified-class ;"
significaría la clase especificada de forma única por ese nombre; por ejemplo, la firma "Ljava/lang/String;"
hace referencia a la clase java.lang.String
. Además, anteponer [
la firma hace que la matriz sea de ese tipo; por ejemplo, [I
significa el tipo de matriz int. Finalmente, una void
firma usa el V
código.
Estos tipos son intercambiables. Se pueden usar jint
donde normalmente se usan an int
y viceversa, sin necesidad de conversión de tipos . Sin embargo, la asignación entre cadenas y matrices de Java a cadenas y matrices nativas es diferente. Si jstring
se usa a donde char *
se usaría a, el código podría bloquear la JVM. [¿ Investigación original? ]
JNI genera una considerable sobrecarga y pérdida de rendimiento en determinadas circunstancias: [6]
La implementación propietaria de Microsoft de una máquina virtual Java ( Visual J++ ) tenía un mecanismo similar para llamar a código nativo desde Java, llamado Raw Native Interface ( RNI ). Además, tenía una forma sencilla de llamar a código nativo existente que no era consciente de Java, como (pero no limitado a) la API de Windows, llamada J/Direct . Sin embargo, tras el litigio entre Sun y Microsoft sobre esta implementación, Visual J++ ya no se mantiene.
RNI era menos complicado de usar que JNI, porque no era necesario llevar la contabilidad con un puntero de entorno Java. En cambio, se podía acceder directamente a todos los objetos Java. Para facilitar esto, se utilizó una herramienta que generaba archivos de encabezado a partir de clases Java. De manera similar, J/Direct era más fácil de usar que usar la biblioteca nativa intermedia necesaria y JNI.
Java Native Access (JNA) es una biblioteca desarrollada por la comunidad que ofrece a los programadores de Java un acceso sencillo a bibliotecas nativas compartidas sin utilizar JNI. Sin embargo, esto requiere la redistribución de la biblioteca jar dependiente. La compensación es que JNI es más difícil de codificar y JNA es más lento. [7] JNI está integrado en el núcleo de Java.