Documentos de Académico
Documentos de Profesional
Documentos de Cultura
1
Tabla de Contenido
1 Introducción
2 Las librerías de enlace estático y dinámico
3 Ejemplo básico con dos clases
3.1 Escribir Código Java
3.2 Compilar el Código Java
3.3 Crear el archivo de Cabecera
3.4 Escribir la función C
3.5 Crear la Librería Dinámica
3.5.1 Unix
3.5.2 Windows
3.6 Ejecutar el Programa
4 Ejemplo básico con una clase
4.1 Declarar el método nativo como miembro de una clase.
4.2 Crear el archivo de cabecera nativo
4.3 Implementar el método nativo
4.4 Compilar el archivo nativo
4.5 Ejecutar el programa
5 Tipos de datos fundamentales, arreglos y objetos
5.1 Parámetros de un método nativo
5.2 Correspondencia de tipos
5.3 Acceso a tipos de datos fundamentales
5.4 Acceso a String
5.4.1 Obtener el texto de un String
5.4.2 Crear un nuevo String
5.4.3 Ejemplo
5.5 Acceso a arreglos
5.5.1 Acceso a arreglos de tipos de datos fundamentales
5.5.2 Acceso a arreglos de referencias
6 Acceso a objetos
6.1 La signatura de tipo
2
1. Introducción
La plataforma Java 2 incorpora la interfaz de programación Java Native Interface (JNI), para
permitir que se puedan escribir programas en otros lenguajes diferentes a Java y mantener la
portabilidad entre todas las plataformas. También, JNI permite que el código Java que se ejecute
en una Máquina Virtual de Java pueda interactuar con aplicaciones y librerías escritas en otros
lenguajes, como C, C++ y ensamblador. Además, la interfaz de programación de aplicación
“Invocation API” permite que se puedan llamar a código de Máquina Virtual Java desde
aplicaciones nativas.
Un método nativo es un método Java (una instancia de un objeto o una clase) cuya
implementación se ha realizado en otro lenguaje de programación fuera del código Java, por
ejemplo, C. Vamos a ver cómo se integran métodos nativos en clases Java. Actualmente, el
lenguaje Java solamente proporciona mecanismos para integrar código C en programas Java.
El objetivo de utilizar JNI para escribir métodos nativos es el de permitir que los
programadores puedan manejar aquellas situaciones en las que una aplicación no puede ser
escrita estrictamente en Java. Por ejemplo algunas de las situaciones en las que puede ser
necesario recurrir a métodos nativos son las siguientes:
La programación a través del entorno que proporciona JNI para implementar métodos nativos
involucra varias operaciones, y proporciona a estos métodos nativos una gran flexibilidad, de
forma que:
o Los métodos nativos pueden llamar a métodos Java con suma facilidad.
o Un método nativo puede utilizar los objetos Java del mismo modo que el propio Java los
usan.
o Un método nativo puede crear objetos Java, incluyendo arreglos y objetos de tipo String,
y luego inspeccionarlos y utilizarlos para sus propias operaciones.
o Un método nativo también puede inspeccionar y utilizar objetos creados por parte de
código Java de la aplicación.
o Un método nativo puede actualizar objetos Java ya creados para que luego sean usados
por la parte de código Java de la aplicación.
En resumen, las partes Java y nativa de la aplicación pueden crear, actualizar, acceder y
compartir objetos java.
JNI permite utilizar las ventajas que ofrece el lenguaje de programación Java desde métodos
nativos; en concreto, permite capturar y lanzar excepciones desde métodos nativos y que estas
excepciones sean manejadas en la aplicación Java. Finalmente, los métodos nativos pueden
3
utilizar JNI para realizar la comprobación de tipos estricta que proporciona Java en tiempo de
ejecución.
La Figura 1 resume todas las características anteriores, mostrando cómo JNI sirve de enlace entre
Java y aplicaciones nativas, por ejemplo, escritas en lenguaje C.
C
Funciones Librerías
Figura 1. Muestra cómo JNI sirve de enlace entre Java y aplicaciones nativas
En resumen, JNI es un mecanismo que nos permite ejecutar código nativo desde Java y
viceversa, en donde el código nativo son funciones escritas en un lenguaje de programación
como C o C++ para el sistema operativo (a partir de ahora SO) donde se está ejecutando la
máquina virtual.
De esta forma podemos decir que las máquinas virtuales Java se hacen para un determinado
host environment.
La máquina virtual que se usa en la Mac OS X es el JRE (Java Runtime Environment) de Apple,
aunque hay otras máquinas virtuales Java (también llamadas plataformas) como la de Sun que es
la implementación de referencia y la más usada en sistemas como Windows, Linux y Solaris.
IBM tiene una máquina virtual para Windows, para AIX y para Linux. Borland por su parte
también tiene su propia máquina virtual en Windows. Microsoft también hizo una máquina
virtual para Windows, pero está desestimada por no seguir las especificaciones de Sun, que
ahora es Oracle.
JNI tiene un interfaz bidireccional que permite a las aplicaciones Java llamar a código nativo y
viceversa. Es decir JNI soporta dos tipos de interfaces:
4
1. Native methods. Que permiten a Java llamar a funciones implementadas en librerías
nativas
2. Invocation Interface. Que nos permite incrustar una máquina virtual Java en una
aplicación nativa. Por ejemplo, una máquina virtual Java en un navegador de Internet
escrito en C. Para ello, la aplicación nativa llama a librerías nativas de la máquina virtual
y luego usa el llamado invocation interface para ejecutar métodos Java en la máquina
virtual.
5
2. Las librerías de enlace estático y dinámico
Las librerías de enlace estático son archivos destinados a almacenar funciones, clases y
variables globales y se crean a partir de varios archivos .o (UNIX) o .obj (Windows). En UNIX
suelen tener la extensión .a y en Windows la extensión .lib
Las funciones de la librería estática se incluyen dentro del programa ejecutable durante la fase de
enlazado, con lo que una vez generado el ejecutable ya no es necesario disponer de las librerías
de enlace estático.
Las librerías de enlace dinámico son archivos cuyas funciones no se incrustan al programa
ejecutable durante la fase de enlazado, sino que en tiempo de ejecución el programa busca el
archivo, carga su contenido en memoria y enlaza su contenido según va siendo necesario, es
decir según vamos llamando a las funciones.
Esto tiene la ventaja de que varios programas pueden compartir las mismas librerías, lo cual
reduce el gasto de disco duro, especialmente con las llamadas al sistema (APIs) que suelen ser
usadas por muchas aplicaciones a la vez.
Su extensión también varía dependiendo del SO en que se esté. Extensiones típicas son:
Para que los programas encuentren las librerías de enlace dinámico, estas deben ponerse en
unos determinados directorios. Cada SO sigue sus reglas:
Para fijar estas variables de entorno existen comandos que varían dependiendo del sistema donde
estemos. Por ejemplo, si se quiere incluir el directorio actual en el path de búsqueda de librerías
de enlace dinámico.
1
En algunos sistemas también podemos indicar rutas de búsqueda de librerías en el archivo
/etc/ld.so.conf que es cargado por /sbin/ldconfig al arrancar.
6
En Mac OS X de 64 bits se haría:
Editar el archivo .bash_profile y colocar ahí tus rutas. Este archivo se ejecuta cada vez que
iniciamos sesión en la terminal y está ligado a cada usuario, por lo que otros usuarios del sistema
no se verán afectados por tus cambios.
Los ":" sirven para concatenar rutas, de manera que cada vez que necesites agregar una
nueva ruta solo debes colocar ":" y tu nueva ruta al final de la asignación del PATH.
Ejemplo real de como está configurado en mi equipo para que puedas darte una mejor
idea:
export ANDROID_HOME=/Users/MeridaDesignStudio/Development/adt-bundle/sdk
export COMPOSER=/Users/MeridaDesignStudio/.composer/vendor/bin
export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$COMPOSER
Como puedes ver en el ejemplo, se pueden crear variables de tus rutas y luego usarlas en
la asignación del PATH.
Paso 4: Guarda el archivo y cierra el editor.
Paso 5: Forza la ejecución del archivo .bash_profile para ver los cambios inmediatamente sin
tener que reiniciar. En tu terminal ejecuta el siguiente comando:
source ~/.bash_profile
Establezca la variable de entorno JAVA_HOME para que apunte al directorio instalado de JDK
(que contendrá el subdirectorio de include que se usará en el siguiente paso):
7
Para PATH (valor del entorno) escriba el comando como se muestra a continuación, en la
“Terminal” usando su ruta de instalación … Nota: (la ruta predeterminada es la que se muestra,
pero si tiene instalado OpenJDK en otra ubicación, establezca esa ruta).
java -version
Y en Windows se haría:
El Shell de Windows busca el directorio actual y los directorios listados en la PATH ( variable
del sistema ) para los programas ejecutables. Los programas de JDK (como el compilador Java
javac.exe y el tiempo de ejecución de Java java.exe) residen en el subdirectorio "bin" del
directorio instalado de JDK. Debe incluir "bin" en el PATH para ejecutar los programas JDK.
1. Primero, encuentre su directorio instalado JDK. Para JDK 10, el valor predeterminado es
"c:\Program Files\Java\jdk-10.0.xx ", donde xx es el número de actualización. Utilice su
"Explorador de archivos" para ver este directorio y tome nota de su directorio instalado
JDK.
2. Compruebe si JAVA_HOME ya está configurado. Iniciar un CMD y emitir:
SET JAVA_HOME
9
Encontrar la compilación correcta para su plataforma operativa (Windows, Mac OS X, Linux),
para su JDK (64 bits) y descubrir las opciones correctas del compilador es la parte más difícil
para que funcione el JNI.
1. // Para mí máquina @
/Library/Java/JavaVirtualMachines/jdk1.8.0_xx.jdk/Contents/Home
$ echo $JAVA_HOME
A continuación, use los siguientes comandos para compilar archivo .c (Archivo.c) en archivo .dll
(Arch.dll). En Windows, hacemos referencia a la variable de entorno JAVA_HOME como
%JAVA_HOME% en el comando:
• -I headerDir: para especificar el directorio del encabezado. En este caso "jni.h" (en
"%JAVA_HOME%\include") y "jni_md.h" (en "%JAVA_HOME%\include\win32"),
donde JAVA_HOME es una variable de entorno establecida en el directorio instalado de
JDK.
• -shared : para generar biblioteca compartida.
• -o outputFilename: para establecer el nombre de archivo de salida "hello.dll".
Arch.dll verifica el tipo de archivo resultante a través de la utilidad "file", que indica que
"Arch.dll" es una DLL de Windows nativa de 64 bits (x86_64).
En los siguientes subtemas se muestran los pasos necesarios para mezclar código nativo C y
programas Java.
11
3 El ejemplo básico con dos clases
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_1".
Recurriremos a nuestro saludo; en este caso, el programa HolaMundo tiene dos clases Java: la
primera, Main, implementa el método main() y la segunda, HolaMundo, tiene un método nativo
que presenta el mensaje de saludo. La implementación de este segundo método se realizará en C.
Las acciones que se debe realizar, para conseguir que la versión del saludo funcione, serán las
que se desarrolle en los subtemas siguientes.
En primer lugar, se debe crear una clase Java, HolaMundo, que declare un método nativo.
También se debe crear el programa principal que cree el objeto HolaMundo y llame al método
nativo.
Las siguientes líneas de código definen la clase HolaMundo, que consta de un método y un
segmento estático de código:
// HolaMundo.java
// Clase HolaMundo
class HolaMundo
{
public native void presentaSaludo();
static
{
System.loadLibrary("hola");
}
}
System.loadLibrary("hola");
Recibe el nombre de la librería de enlace dinámico "hola" y la carga. La librería debe cargarse
antes de llamar a cualquier método nativo.
12
Se puede decir que la implementación del método presentaSaludo() de la clase HolaMundo está
escrito en otro lenguaje, porque la palabra reservada native aparece como parte de la definición
del método. Esta definición, proporciona solamente la definición para presentaSaludo() y no
proporciona ninguna implementación para él. La implementación la proporcionaremos desde un
archivo fuente separado; escrito en lenguaje C.
El código C que implementa el método presentaSaludo() debe ser compilado en una librería
dinámica y cargado en la clase Java que lo necesite. Esta carga, mapea la implementación del
método nativo sobre su definición.
El siguiente bloque de código carga la librería dinámica, en este caso “hola”. El sistema Java
ejecutará un bloque de código estático de la clase cuando la cargue.
Todo el código anterior forma parte del archivo "HolaMundo.java", que contiene la clase
HolaMundo. En un archivo separado, "Main.java", se va a crear una aplicación Java que
instancie a la clase HolaMundo y llame al método nativo presentaSaludo().
// Main.java
// Aplicación Java
class Main
{
public static void main( String args[] )
{
new HolaMundo().presentaSaludo();
}
}
Como se puede observar, se llama al método nativo del mismo modo que a cualquier otro
método Java; se añade el nombre del método al final del nombre del objeto con un punto ("."). El
conjunto de paréntesis que sigue al nombre del método encierra los argumentos que se le pasen.
En este caso, el método presentaSaludo() no recibe ningún tipo de argumento.
En resumen, en el código Java para que se puedan incorporar métodos nativos hay que hacer
dos cosas:
o Escribir la declaración de cada método nativo que se vaya a utilizar. Eso sería igual que la
declaración de un método normal de una interfaz Java, incorporándole la palabra native.
o Colocar la carga de la librería que contiene el código nativo a través de un bloque de la clase
estática.
Static
{
System.loadLibrary( "hola" )
}
13
Y ya se puede incorporar en el método principal la llamada que permita invocar al método
nativo:
Compilar los dos archivos fuentes de código Java que se crearon en el directorio de trabajo, para
lo cual cargar la línea de comandos: botón derecho en Menú de Inicio de Windows, para elegir la
opción Ejecutar, luego digitar cmd y enter. Por último, teclear los siguientes comandos en la
línea de comandos:
Ahora se debe utilizar la aplicación javah para conseguir el archivo de cabecera .h. El archivo de
cabecera define una estructura que representa la clase HolaMundo sobre código C y proporciona
la definición de una función C para la implementación del método nativo presentaSaludo()
definido en ese clase.
A partir de JDK 8, debe usar "javac -h" para compilar el programa Java y generar el archivo de
encabezado C / C ++ denominado HolaMundo.h de la siguiente manera:
La opción "-h dir" genera el encabezado C/C++ y lo coloca en el directorio especificado (en el
ejemplo anterior, '.' para el directorio actual).
El archivo que creará, será un archivo de cabecera del mismo nombre que la clase y con
extensión .h. Por ejemplo, el comando anterior habrá creado el archivo HolaMundo.h, cuyo
contenido será el siguiente:
#ifndef _Included_HolaMundo
#define _Included_HolaMundo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HolaMundo
* Method: presentaSaludo
14
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HolaMundo_presentaSaludo
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Este archivo de cabecera contiene la llamada de la función C que está declarada como:
Esta es la definición de la función C que se debe escribir para implementar el método nativo
presentaSaludo() de la clase HolaMundo. Si HolaMundo llamase a otros métodos nativos, las
definiciones de las funciones también aparecerían aquí.
El nombre de la función C que implementa el método nativo está derivado del nombre del
paquete, el nombre de la clase y el nombre del método nativo. Así, el método nativo
presentaSaludo() dentro de la clase HolaMundo es HolaMundo_presentaSaludo(). En este
ejemplo, no hay nombre de paquete porque HolaMundo se considera englobado dentro del
paquete por defecto.
La función C acepta parámetros, aunque el método nativo definido en la clase Java no acepte
ninguno, dichos parámetros son:
// HolaMundoImp.c
#include <jni.h>
#include "HolaMundo.h"
#include <stdio.h>
15
JNIEXPORT void JNICALL
Java_HolaMundo_presentaSaludo (JNIEnv * env, jobject obj)
{
printf("Hola Mundo, desde C");
return;
}
Como se puede ver, la implementación no puede ser más sencilla: hace una llamada a la función
printf() para presentar el saludo y sale.
• jni.h
Proporciona la información para que el código C pueda interactuar con el sistema Java
• HolaMundo.h
Es el archivo de cabecera que hemos generado para nuestra clase. Contiene la estructura C
que representa la clase Java para la que estamos escribiendo el método nativo y la definición
de la función para ese método nativo.
• stdio.h
Es necesario incluirlo porque utilizamos la función printf() de la librería estándar de C, cuya
declaración se encuentra en este archivo de cabecera.
Se utiliza el compilador C para compilar el archivo .h y el archivo fuente .c; para crear una
librería dinámica. Ahora para generarlo utilizar el compilador C del sistema, haciendo que el
archivo "HolaMundoImp.c" genere una librería dinámica de nombre hola, que será la que el
sistema Java cargue cuando ejecute la aplicación que se está construyendo. Ejecutar el siguiente
comando:
Cuando se escriben métodos nativos, siempre habrá que garantizar incluir los archivos "jni.h" y
"jni_md.h", que son proporcionado por JDK y está disponible en "<JAVA_HOME>\include" y
"<JAVA_HOME>\include\win32" (para Windows), respectivamente.
Y, por fin, utilizar el intérprete de Java, java, para ejecutar el programa que construido,
siguiendo todos los pasos anteriormente descritos. Si se teclea el comando:
Si no aparece este mensaje de saludo y lo que aparece en pantalla son expresiones como
UnsatisfiedLinkError, es porque no se tiene fijado correctamente el camino de la librería
16
dinámica que hemos generado. Este camino es la lista de directorios que el sistema Java utilizará
para buscar las librerías que debe cargar. Debemos asegurarnos de que el directorio donde se
encuentra nuestra librería hola recién creada, figura entre ellos.
Si se fija el camino correcto y se ejecuta de nuevo el programa, se verá que ahora sí aparece el
mensaje de saludo que esperábamos.
17
4 El ejemplo básico con una clase
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_2".
Vamos a realizar un programa Java con una clase que llama a una función de librería C que dice:
“Hola mundo”. El uso de una sola clase no es adecuado, porque no se podría usar en otra
aplicación, debido a que ya es una aplicación.
Los métodos nativos llevan el modificador native y están sin implementar ya que su
implementación estará en una librería nativa, luego hacemos:
// HolaMundo.java
// Clase HolaMundo
public class HolaMundo
{
private native void saluda();
static
{
System.loadLibrary("hola");
}
Ahora tenemos que generar el archivo .class y un archivo .h con el prototipo de los métodos
nativos a implementar.
#include <jni.h>
/* Header for class HolaMundo */
#ifndef _Included_HolaMundo
#define _Included_HolaMundo
#ifdef __cplusplus
extern "C" {
#endif
18
/*
* Class: HolaMundo
* Method: saluda
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HolaMundo_saluda
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
// HolaMundoImp.c
#include <stdio.h>
#include "HolaMundo.h"
Los parámetros JNIEnv y jobject se ponen siempre en los métodos nativos, y ya veremos luego
para que sirven.
Usando los comandos que explicamos en el apartado 2 para crear la librería de enlace dinámico,
con el comando:
19
Alternativamente podemos indicar el path de la librería de enlace dinámico con la propiedad del
sistema:
20
5 Tipos de datos fundamentales, arreglos y objetos
En este apartado veremos que correspondencia existe entre los tipos de datos Java y sus
correspondientes tipos de datos C. Después veremos cómo intercambiar datos entre Java y C.
Empezaremos viendo los tipos de datos fundamentales para luego pasar a estudiar tipos más
complejos como los arreglos o los objetos.
1. El primer argumento env apunta a una tabla de punteros a funciones, que son las funciones
que usaremos para acceder a los datos Java de JNI.
1. Tipos fundamentales. Como int, float o double. Su correspondencia con tipos C es directa,
ya que en el archivo jni.h encontramos definiciones de tipos C equivalentes. Por ejemplo
para float encontramos el tipo jfloat.
La siguiente tabla muestra todos los tipos fundamentales Java y sus correspondientes tipos C.
21
Tipo Java Tipo C Descripción
boolean jboolean 8 bits sin signo
byte jbyte 8 bits con signo
char jchar 16 bits sin signo
short jshort 16 bits con signo
int jint 32 bits con signo
long jlong 64 bits con signo
float jfloat 32 bits formato IEEE
double jdouble 64 bits formato IEEE
JNI pasa los objetos a los métodos nativos como referencias opacas, es decir como punteros C
que apuntan a estructuras internas sólo conocidas por la máquina virtual concreta que estemos
usando. Los campos de esta estructura no se dan a conocer al programador, sino que el
programador accede a ellos a través de funciones JNI.
Por ejemplo el tipo JNI correspondiente a java.lang.String es jstring. El valor exacto de los
campos a los que apunta jstring es irrelevante al programador, sino que éste accede al texto del
objeto usando:
Todas las referencias en JNI son del tipo jobject, aunque por mejorar el control de tipos se han
creado tipos derivados como jstring o jobjectArreglo.
jobject
jclass
jstring
jarreglo
jobjectArreglo
jbooleanArreglo
jbyteArreglo
jcharArreglo
jshortArreglo
jintArreglo
jlongArreglo
jfloatArreglo
jdoubleArreglo
jthrowable
La conversión entre variables Java y tipos de datos fundamentales es como ya dijimos de forma
inmediata.
22
Por ejemplo si queremos hacer un método nativo que sume dos números.
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_3",
luego se crea la clase Parametros en el archivo Parametros.java:
// Parametros.java
// clase Parametros
public class Parametros
{
private native int suma(int a, int b);
static {
System.loadLibrary("Parametros");
}
}
El tipo int de Java corresponde con el tipo jint de C, luego tendremos que hacernos un archivo
Parametros.c así:
// Parametros.c
#include <jni.h>
#include "Parametros.h"
3+4=7
String, es una clase, que estará representado por el tipo C jstring. Para acceder al contenido de
este objeto existen funciones que convierten un String Java en cadenas C. Estás funciones nos
permiten convertir tanto a Unicode como a UTF-8.
23
• La función GetStringUTFChars() devuelve un puntero a un arreglo de caracteres UTF-8
del string enviado desde java.
isCopy puede ser NULL si no nos devolverá si ha hecho una copia en otro arreglo o ese
arreglo en el buffer original. isCopy valdrá JNI_TRUE o JNI_FALSE.
• El buffer obtenido por la llamada anterior no se libera hasta que lo liberamos usando:
Que nos sirven para obtener un buffer con la representación Unicode del texto del jstring.
• Las cadenas UTF-8 siempre acaban en '\0', pero no las cadenas Unicode, con lo que
tendremos que usar la siguiente función para saber la longitud de una cadena Unicode:
En general no es posible predecir si la máquina virtual hará copia del buffer o no. En principio si
la máquina virtual utiliza un formato para almacenar los strings, por ejemplo. Unicode, entonces
sabemos que GetStringUTFChars() siempre devolverá una copia, y GetStringChars() devolverá
24
una copia o no. El devolver una copia implica hacer una copia del buffer, pero a cambio la
máquina virtual podrá ejecutar la recogida de basura. Si devuelve el buffer original no podrá
realizar recogida de basura del objeto String de Java aunque en Java ya nadie lo apunte, porque
lo estamos apuntando desde C.
En el JSDK 1.2 surgieron nuevas funciones que, no nos garantizan, pero si aumentan la
probabilidad de obtener directamente un puntero al arreglo de caracteres de la máquina virtual,
estas funciones son:
También podemos crear nuevas instancias de un java.lang.String desde un método nativo usando
las funciones JNI:
Ambas funciones producen una OutOfMemoryError si fallan en cuyo caso devuelven NULL,
con lo que siempre hay que comprobar el retorno de la función.
5.4.3 Ejemplo
Como ejemplo proponemos hacer un método nativo que recibe como parámetro un String con un
mensaje de prompt a dar a un usuario por consola y recoge de consola el texto escrito por el
usuario que nos lo devuelve como un String.
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_4", luego
creamos la clase Parametros.
// Parametros.java
25
// Clase Parametros
public class Parametros {
static {
System.loadLibrary( "Parametros" );
}
}
// Parametros.c
#include <jni.h>
#include <string.h>
#include "Parametros.h"
puts(prompt_c);
(*env)->ReleaseStringUTFChars( env, prompt, prompt_c );
Escriba un texto:
Hola <ENTER>
El texto devuelto es: hola
Aquí es importante que la extensión del archivo Parametros.c sea .c y no .cpp, ya que el puntero
env está declarado de forma distinta en C que en C++.
26
o Arreglos de tipos de datos fundamentales. Son arreglos cuyos elementos son tipos de
datos fundamentales como boolean o int
o Arreglos de referencias. Son arreglos cuyos elementos son objetos o bien otros arreglos
(arreglos de arreglos)
Por ejemplo:
Para acceder a arreglos de tipos fundamentales existen funciones JNI que nos devuelven el
arreglo en una variable de tipo jarray o derivada.
Existen tipos derivados para los arreglos de tipos de elementos más comunes.
jarray
jobjectArray
jbooleanArray
jbyteArray
jcharArray
jshortArray
jintArray
jlongArray
jfloatArray
jdoubleArray
• Una vez que tengamos una variable nativa de tipo jarray o derivado podemos obtener la
longitud del arreglo con:
• Después podemos acceder a los datos del arreglo usando funciones JNI. Para cada tipo
fundamental existe su correspondiente función JNI.
isCopy nos dice si el puntero devuelto es al arreglo original o si se ha hecho una copia de
éste.
• Una vez que se nos devuelve un puntero, éste es válido hasta que lo liberamos con su
correspondiente función:
27
void ReleaseTipoArrayElements(JNIEnv* env, jbooleanArray arreglo, jboolean* buffer, jint mode);
Modo Acción
0 Copia el contenido de buffer en el arreglo Java y libera buffer
JNI_COMMIT Copia el contenido de buffer en el arreglo Java pero no libera buffer
JNI_ABORT Libera buffer sin copiar su contenido en el arreglo Java
Que al igual que pasaba con String las funciones procuran enviar el arreglo original si es
posible, con lo que el código nativo no debe bloquearse o llamar a otras funciones JNI entre
las llamadas a GetPrimitiveArrayCritical() y ReleasePrimitiveArrayCritical()
Por último también existen funciones para crear arreglos Java desde el código nativo:
Ejemplo
Como ejemplo podríamos hacer un método que recibe dos arreglos unidimensionales y devuelve
un nuevo arreglo con la suma de estos.
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_5", luego
creamos la clase Parametros.
// Parametros.java
// Clase Parametros
public class Parametros {
private native int suma(int a, int b);
private native String pideTexto(String prompt);
private native int[] sumaArreglos(int[]A, int[]B);
28
Parametros p = new Parametros();
int[] A = {1,2,3};
int[] B = {3,2,1};
int[] C = p.sumaArreglos(A, B);
if ( C == null )
System.out.println("No se puede hacer la suma");
else {
System.out.println("La suma de arreglos es: ");
for ( int i = 0; i < C.length; i++ )
System.out.print(C[i] + " ");
System.out.println();
}
}
static {
System.loadLibrary( "Parametros" );
}
jint* A;
jint* B;
jint* C;
jsize i;
// Medimos el arreglo
jsize longitud = (*env)->GetArrayLength( env, arregloA );
if ( longitud != (*env)->GetArrayLength( env, arregloB ) )
return NULL; // No coinciden las longitudes
// Creamos un nuevo arreglo con la solución
arregloC = (*env)->NewIntArray( env, longitud );
return arregloC;
}
3+4=7
Escriba un texto: Hola <ENTER>
El texto devuelto es: Hola
La suma de arreglos es:
444
A diferencia de los arreglos de tipos de datos fundamentales, no podemos acceder a todos los
elementos a la vez, sino que tenemos que procesar elemento a elemento con estas funciones.
initialElement es el valor inicial al que se fijan todos los elementos del arreglo, y puede ser
NULL.
30
elementType indica el tipo de los elementos del arreglo, y no puede ser NULL. Para obtener este
tipo se suele usar la función:
Nota: De momento basta decir que para el ejemplo que vamos a hacer en name, pasaremos "[I"
que significa que queremos un arreglo de arreglos de enteros. Al final se explicará lo indicado.
Ejemplo
Como ejemplo vamos hacer un método sumaMatrices(), que recibe dos matrices bidimensionales
y devuelve otra matriz con la suma de estas.
En primer lugar creamos un directorio para la aplicación, en este caso se llama " Proyecto_1_6", luego
creamos la clase Parametros.
// Parametros.java
// Clase Parametros
public class Parametros {
private native int suma(int a, int b);
private native String pideTexto(String prompt);
private native int[] sumaArreglos(int[] A, int[] B);
private native int[][] sumaMatrices(int[][] A, int[][] B);
int[] A = {1,2,3};
int[] B = {3,2,1};
int[] C = p.sumaArreglos(A, B);
if ( C == null )
System.out.println("No se puede hacer la suma");
else {
System.out.println("La suma de arreglos es: ");
for ( int i = 0; i < C.length; i++ )
System.out.print(C[i] + " ");
System.out.println();
}
if ( M3 == null )
System.out.println("Falló la suma");
else {
System.out.println("La suma de la matriz es:");
for ( int i = 0; i < M3.length; i++ ) {
for ( int j = 0; j < M3[i].length; j++ )
System.out.print(M3[i][j] + " ");
System.out.println();
31
}
}
static {
System.loadLibrary( "Parametros" );
}
}
// Parametros.c
#include <jni.h>
#include "Parametros.h"
jint* A;
jint* B;
jint* C;
jsize i;
// Medimos el arreglo
jsize longitud = (*env)->GetArrayLength( env, arregloA );
if ( longitud != (*env)->GetArrayLength( env, arregloB ) )
return NULL; // No coinciden las longitudes
// Creamos un nuevo arreglo con la solución
arregloC = (*env)->NewIntArray( env, longitud );
32
A = (jint*)(*env)->GetPrimitiveArrayCritical( env, arregloA, NULL );
B = (jint*)(*env)->GetPrimitiveArrayCritical( env, arregloB, NULL );
C = (jint*)(*env)->GetPrimitiveArrayCritical( env, arregloC, NULL );
return arregloC;
}
if ( arregloC == NULL )
return NULL; // Excepción OutOfMemoryError lanzada
return arregloC;
3+4=7
Escriba un texto: Hola
El texto devuelto es: Hola
La suma de arreglos es:
444
La suma de la matriz es:
66
48
39
La llamada a DeleteLocalRef() lo que hace es evitar que la máquina virtual pueda quedarse sin
memoria.
34
6 Acceso a objetos
En este apartado aprenderemos a acceder a los atributos de un objeto Java, así como a llamar a
sus métodos (no se va a ver en este curso).
Para describir los tipos de los atributos y de los métodos de un objeto, Java utiliza las signaturas
de tipo, también llamadas descriptores de miembro (member descriptor).
Una signatura de tipo es una cadena C que describe el tipo de un miembro (atributos o métodos)
de la clase.
Por ejemplo para representar un int usamos “I” y para representar un float “F”.
Para representar un arreglo se pone un [ delante del tipo. P.e. para representar un double [] se usa
“[D” y parar epresentar un int [] [] se usa “[ [I”.
Por último, para representar una clase se precede por una L, se pone el nombre completo de la
clase y se acaba en ;. P.e. para representar la clase java.util.Date se usaría “Ljava/util/Date;“.
Obsérvese que se sustituyen los . por /
Los métodos también tienen una signatura de tipo en cuyo caso los parámetros del método se
ponen primero entre paréntesis y el retorno del método después. P.e. para un método como:
Tenemos la signatura de tipo: “()I“. Aquí hay que tener cuidado de no poner (V) I” que sería
incorrecto. Sin embargo si tenemos el método:
su signatura de tipo sería ”(I) V”, es decir para indicar el retomo si se pone la V
35
O para el método:
Si el método recibe varios parámetros se ponen todos seguidos sin usar ningún símbolo como
separador.
Podemos sacar las signaturas de tipo de los miembros de una clase usando el comando javap:
% javap -s -p Parametros
Compiled from Parametros.java
public class Parametros extends java.lang.Objeot {
publio Parametros ();
/* ()V */
private native mt suma(int, intt);
/* (II)I */
private native java.lang.String
pideTexto (java.lang.String);
/* (Ljava/lang/String;)Ljava/lang/String; */
private native int sumaArreglos(int[], int[]) [];
/* ([I[I]) [I */
private native int sumaMatrioes(int[] [], int[ ] [ ]) [ ] [ ];
/* ([[I[[I) [[I */
publio statio void main(java.lang.String[]);
/* ([Ljava/lang/String;)V */
static {};
/* ()V */
El parámetros -s es para que saque las signaturas de tipo C y no la signatura de tipo Java, y -p es
para que también saque los miembros privados.
La cual, como vimos en el ejemplo anterior nos devuelve una variable que representa a la clase,
y que luego esta variable nos la piden otras funciones de JNI.
en el ejemplo anterior.
36