Buscar
Social
Ofertas laborales ES
« JNI (Java Native Interface) | Main | IzPack ahora tambrión en castellano »
lunes
oct012001

Conexrión Java-C y C-Java


Enlace Java-C y C-Java




Alguna vez nos podemos encontrar en la situación de que un lenguaje, en nuestro caso Java, no nos permite programar a bajo nivel para poder programar, por ejemplo, un dispositivo de hardware. Cuando estamos en una situación como esta, tenemos que acudir a lenguajes que lo permitan, bien realizar toda la aplicación en este lenguaje tan permisivo o lo que es más normal utilizar este para la programación de más bajo nivel y el otro para la programación de alto nivel. Otros motivos para la utilización de varios lenguajes en un mismo programa es que podemos tener rutinas que ya están escritas y probadas en otros lenguajes como C. Por eso el objetivo de este artículo es mostrar y conocer el interface C - Java.



Lo primero que explicaremos antes de entrar en profundidad en la comunicación entre los lenguajes, son los principales pasos que tenemos que conocer para la mezcla de C y Java.




Paso primero



Escribiremos nuestra clase Java que va a tener métodos escritos en C, estos métodos son los que llamaremos métodos nativos y se declararán en la clase de la siguiente forma:



<ámbito> native ()


Si tenemos un método público que retorna un entero, se llama suma y tiene como argumento un entero. Se definira:



public native int suma(int numero2);


Se tendra que cargar la librería donde se encuentran los métodos nativos esto se puede realizar con:



loadLobrary();


o



load();


La primera opción es la que utilizaremos ya que va a buscar la librería en donde este marcado por el PATH, en unix la variable la variable LD_LIBRARY_PATH nos indica los directorios donde buscar la librería.



La segunda opción nos servirá cuando estemos en la fase de desarrollando y se diferencia de la anterior por tener que especificar el camino completo de la librería.



En el Listado 1 tenemos el primer ejemplo en el creamos una instancia de si misma, de esta forma llamaremos al método nativo y será en esta instancia donde nos queden las modificaciones que el método realice sobre nuestra clase.



Después compilaremos con nuestro compilador de Java.



javac clase en nuestro caso javac Ejemplo1


 




/**************************************************************************
* Ejemplo1.java
*
* Descripcion: Nos permite ver como comunicamos datos entre Java y C y a
* la inversa.
*
* Autor: Fco. Javier Rodriguez Navarro (c) 1998
*
**************************************************************************/
import java.io.*;
public class Ejemplo1
{
int numero1 = 9;
int total;

public native int suma(int numero2);

public void ejecuta()
{
int resultado;
Ejemplo1 clase = new Ejemplo1();
System.out.println("Esto es Java y llamamos a C");
System.loadLibrary("Ejemplo1_C");
resultado = clase.suma(13);
System.out.println("Otra vez en desde Java");
System.out.println("El total calculado por C es: " + resultado);
System.out.println("El valor que modifico C quedo en: "+ clase.total);
}
}




Paso segundo



Crearemos un fichero .h que contendrá el prototipo de la función de nuestro método nativo así como una estructura que tendrá las variables no estáticas de nuestra clase. Para realizar este proceso utilizaremos la utilidad javah que nos viene con el JDK. La forma de invocar a esta utilidad será:
javah en nuestro caso seria javah Ejemplo1



El resultado lo podemos ver en el Listado 2, este fichero podrá variar de una versión a otra, que pasamos a explicar.




/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class Ejemplo1 */
#ifndef _Included_Ejemplo1
#define _Included_Ejemplo1

#pragma pack(4)

typedef struct ClassEjemplo1 {
int32_t numero1;
int32_t total;
} ClassEjemplo1;
HandleTo(Ejemplo1);

#pragma pack()

#ifdef __cplusplus
extern "C" {
#endif
extern int32_t Ejemplo1_suma(struct HEjemplo1 *,int32_t);
#ifdef __cplusplus
}
#endif
#endif



Tenemos la definición de una estructura H en ella tenemos las variables no estáticas de nuestra clase java, más adelante explicaremos como acceder a ellas. Si no tuviéramos variables en nuestra clase en la estructura tendríamos la variable char PAD ya que C requiere que las estructuras tengan al menos un elemento. HandleTo es una macro que nos hace tener el tipo de estructura H<clase>.



Después tenemos el prototipo del método nativo que tendremos que implementar el nombre de este se forma poniendo delante del nombre del método y separado por un guión bajo “_” el nombre de la clase asi en nuestro ejemplo el nombre de la clase es Ejemplo1 y el método suma formandose “Ejemplo1_suma”. Si la clase perteneciera a un paquete como por ejemplo “mispaquetes.Ejemplo1.suma” el nombre de la función que tendriamos que implementar seria “mispaquetes_Ejemplo1_suma”.




Paso tercero



Crearemos la función que conectara la clase java con el C. Para esto usaremos también la utilidad javah pero esta vez con la opción -stubs quedando el comando de la siguiente forma:



javah -stubs en nuestro caso javah -stubs Ejemplo1


El resultado lo podemos ver aquí



/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Stubs for class Ejemplo1 */
/* SYMBOL: "Ejemplo1/suma(I)I", Java_Ejemplo1_suma_stub */
stack_item *Java_Ejemplo1_suma_stub(stack_item *_P_,struct execenv *_EE_) {
extern int32_t Ejemplo1_suma(void *,int32_t);
_P_[0].i = Ejemplo1_suma(_P_[0].p,((_P_[1].i)));
return _P_ + 1;
}



Paso cuarto



Programaremos la implementación en C de nuestros métodos nativos. Aunque nuestro método nativo no tenga parámetros, en la implementación en C siempre tendremos un parámetro que será la estructura de la clase Java para poder acceder a sus variables. La mejor forma de asegurarnos de los tipos de parámetros y el nombre de la función que se tiene que implementar es coger la definición del prototipo que se genera con javah. En el Listado 4 tenemos la implementación del método nativo para el ejemplo1.



#include "Ejemplo1.h"
#include
int32_t Ejemplo1_suma(struct HEjemplo1 *javaObj, int32_t numero2)
{
int resultado,numero1;

printf("En estos momento estamos en C\n");
numero1 = unhand(javaObj)->numero1;
resultado = numero1 + numero2;
unhand(javaObj)->total = resultado * 2;
printf("En C llego %d por parámetro y %d en la clase\n",numero2,numero1);
printf("Enviamos como retorno %d, a la clase %d\n",resultado, (resultado * 2));

return resultado;
}



Paso quinto



Creamos la librería dinámica C con nuestro métodos nativos.
Dependiendo del compilador que utilicemos podran variar las opciones de localización de ficheros cabecera o la forma de crear una librería dinámica, a continuación damos las opciones de compilación para tres sistemas diferentes:



Compilador GNU de Linux:



cc - shared -I$(JAVAHOME)/include -I$(JAVAHOME)/include/genlinux \
Ejemplo1Imp.c Ejemplo1.c -o libEjemplo1.so


Compilador de Sun:



cc - shared -I$(JAVAHOME)/include -I$(JAVAHOME)/include/solaris \
Ejemplo1Imp.c Ejemplo1.c -o libEjemplo1.so


Compilador Microsoft C/C++ de Windows 95:



cl Ejemplo1Imp.c Ejemplo1.c -I$(JAVAHOME)\include -I$(JAVAHOME)\include\win32
-FeEjemplo1_C.dll -MD -LD $(JAVAHOME)\lib\javai.lib


JAVAHOME es una variable de entorno que apunta al directorio donde se encuentra nuestro JDK y por lo tanto los ficheros cabeceras en sus correspondientes directorios que en nuestro caso son:



/include para todos los sistemas.
/include/genunix para linux.
/include/solaris para el sistema operativo de SUN.
/include/win32 para Windows 95.


Los demás parámetro se tendrán que ver en las opciones del compilador, viendo como indicarle los caminos a ficheros cabecera y como crear una librería dinamica. En Windows 95 tendremos que enlazar con la librería javai.lib que se encuentra en $(JAVAHOME)/lib.




Paso sexto y ultimo



Ejecutar y ver que todo nos ha funcionado correctamente.





Comunicación de datos entre la clase java y los métodos nativos de C



Como hemos podido ver en el Listado 2 javah crea una estructura que tiene las variables no estáticas de la clase Java. Para poder llegar a estos datos utilizaremos la macro de C unhand(clase) con esto tenemos acceso a la estructura y después seleccionamos el nombre de la variable sobre la que deseamos actuar. En el Listado 2 podemos ver como recoger el valor de una variable y como modificar una variable de la clase.
numero1 = unhand(javaObj)-> numero1



Unhand(javaObj)->total = resultado * 2


Cadenas de caracteres



En el Listado 5 podemos el uso de algunas funciones para el manejo de cadenas de caracteres que a continuación vamos a explicar:



char *javaString2Cstring(Hjava_lang_String *, char[], int );


Pasa el número de caracteres indicados por el tercer parámetro del String de tipo Java que se especifica en el primer parámetro al array de caracteres de C del segundo parámetro. Tendremos que tener cuidado de no copiar más caracteres de los que admita nuestro array de caracteres definido en C. En el Listado 5 podemos ver que se ha definido un array de 11 caracteres y solo recogemos 10 esto es para tener un lugar más para el carácter ‘\0’ que indicara el final de la cadena.



#include "Ejemplo2.h"
#include
void Ejemplo2_imprime(struct HEjemplo2 *javaobj, struct Hjava_lang_String *cadena)
{
char cadena_convertida[11];

/* Convertimos la cadena a una cadena C el maximo de 10 caracteres*/
javaString2CString(cadena, cadena_convertida, 10);

/* Se imprime la cadena que se con. */
if (javaStringLength(cadena) > 10)
{
printf("La cadena es mayor de 10 se produce una excepcion\n");
SignalError(0,"java/lang/IndexOutOfBoundsException",0);
}
printf("Ha llegado: %s\n",cadena_convertida);
}





char *allocCString(Hjava_lang_String *);


Creamos un buffer para almacenar el String java al que apunta el parámetro, es nuestra responsabilidad realizar un free del puntero después de su utilización.



char *makeCString(Hjava_lang_String *);


Es parecida a la primera función que vimos allocCString pero esta pasa la responsabilidad de su liberación al recolector de basura de java.



int javaStringLength(Hjava_lang_String *);


Nos da la longitud del String de java, un ejemplo de su uso se puede ver en el Listado 5.



Generación de excepciones java desde C



Cuando estamos en C podemos encontrarnos con una situación que no nos permita continuar y que es interesante que podamos producir una excepción que muy posiblemente nuestra clase Java capture para tomar una decisión.
Para poder lanzar una excepción Java desde C usaremos la función:



SignalError(ExecEnv *, char *, char *)


  • El primer parámetro será el entorno de ejecución NULL si queremos que sea el entorno actual.
  • El segundo el tipo de excepción que se produce sustituyendo los punto “.” por guiones bajos “_”.
  • El tercero es una cadena descriptiva de la excepción que se produce o NULL en otro caso.


La excepción la lanza java y es este cuando recoge el control el cual comprueba si se ha establecido una excepción para lanzarla.



En el Listado 5 se puede ver un ejemplo de como dependiendo de la cadena que nos llega se hace saltar o no una excepción. En este listado podemos observar un error que es muy típico. Una vez que hemos indicado que se produce la excepción, en lugar de salir, se continua con la función, en nuestro caso no pasa nada y es puramente didáctico pero en cualquier otro caso puede traernos consecuencias peligrosa, como se puede ver lo normal es que hubiéramos puesto un return después de SignalError para salir así de la función y no continuar ya que es Java quien lanza la excepción una vez que salgamos de la función.





Ejecución de objetos java desde métodos nativos C



En el Listado 6 podemos ver como un método nativo crea una clase escrita en java y ejecuta sus métodos. La clase Java que ejecuta la podemos ver en el Listado 7.



#include "Ejemplo3.h"
#include "Ejemplo3b.h"
#include
void Ejemplo3_ejecutamos(struct HEjemplo3 *javaobj)
{
HObject *clase1;
HEjemplo3b *clase2;
int resultado1, resultado2;

printf("Ya estamos en C y ejecutaremos clases y métodos java\n");
printf("Crearemos dos clases con dos métodos distintos\n");
clase1 = (struct HEjemplo3b *) execute_java_constructor(NULL,"Ejemplo3b", NULL, "()");
clase2 = (struct HEjemplo3b *) execute_java_constructor(NULL,"Ejemplo3b",NULL,"(I)",9);
printf("Realizamos sumas sobre las clase\n");
execute_java_dynamic_method(0,(HObject *)clase1,"suma","(I)V",98);
execute_java_dynamic_method(0,(HObject *)clase2,"suma","(I)V",11);
printf("Recogemos los valores con el método valor\n");
resultado1 = execute_java_dynamic_method(0,(HObject *)clase1,"valor","()I");
resultado2 = execute_java_dynamic_method(0,(HObject *)clase2,"valor","()I");
printf("Los Resultados son %d %d\n",resultado1, resultado2);
}



/*****************************************************************************
* Ejemplo3b.java
*
* Descripcion: Creacion y ejecucion de objetos java desde métodos nativos.
* Esta clase se creara y ejecutara desde C.
*
* Autor: Fco. Javier Rodriguez Navarro (c) 1998
*
*****************************************************************************/
import java.io.*;
public class Ejemplo3b
{
private int valor;
public Ejemplo3b()
{
valor = 0;
System.out.println("Se ejeuta el constructor sin argumentos");
}
public Ejemplo3b(int dato) // Inicializamos a un valor.
{
valor = dato;
System.out.println("Se ejeuta el constructor con el argumento: "+dato);
}
public void suma(int dato) // Suma al valor de la clase.
{
valor = valor + dato;
System.out.println("Se ejeuta el método suma con argumento: "+dato);
}
public int valor() // Nos da el valor de la clase
{
System.out.println("Se ejeuta el método valor");
return (valor);
}
}


Para la clase Java que se ejecutara no es necesario crear el stub pero si la cabecera con el prototipo de la clase para que el método C pueda acudir a sus variables. De esta forma solo realizaremos un paso con la utilidad javah.



Lo que si nos tiene que quedar claro el un método nativo es el que ejecutara clases Java y sus métodos, esto implica que este método nativo ha tenido que ser llamado por una clase Java.



Una clase Java en C se declarara como un puntero de tipo H<clase>, también podremos tener un puntero tipo HObject que podrá referenciar a cualquier tipo de clase java. En el Listado 6 podemos ver como declaramos dos variables tipo puntero una a HObject y otra Hejemplo3b que referenciaran a las clase que se construirán con la función execute_java_constructor.



HObject *Execute_java_constructor(ExecEnv *, char *, ClassClass *, char *, ..);


Esta función retorna un puntero tipo HObject que es la clase desde la que cuelgan todas las clase de java, los parámetros son:



  • El primer parámetro es un puntero de tipo ExecEnv, con NULL utilizaremos el entorno actual.
  • Segundo parametro es un puntero a carácter que será el nombre de la clase a crear.
  • Tercer parametro una estructura de clase que normalmente se pondrá a NULL (más adelante explicaremos como obtener estructuras de clases).
  • Cuarto parámetro un array de caracteres que especificara los parámetros de la clase con el formato siguiente:

       () No tenemos parámetros


       (<Tipo>,<Tipo,…) los tipos de parámetros los tipos primitivos tendrán las siguiente abreviaturas:
    Z boolean, C char, B Byte, S short, I int, J long, F float, D double.



Para los no primitivos como clases deberemos poner una L para evitar conflictos quedando L<tipo> este tipo será el que retorna Class.getName(), así para el tipo String tendríamos Ljava/lang/String;.
Los arrays se especifican poniendo delante un corchete y si tiene múltiples dimensiones pondremos tantos corchetes como dimensiones tenga.



Algunos ejemplos de especificación de parametros podrian ser:



  • (I) Un tipo entero.
  • (I, F,I) Un entero un flotante y otro entero.
  • (I, Ljava/lang/String;, [Z) Un entero, un String y un array de booleanos.


Después de haber construido nuestra clase pasaremos a explicar como llamar a los métodos de esa clase. Para llamar a un método dinámico ejecutaremos la función:



long execute_java_dynamic_method (ExecEnv*, HObject*, char*, char*,..)


  • El primer parámetro es el entorno y como siempre NULL para el actual..
  • El segundo parámetro es el puntero al objeto de la clase que se habrá construido con anterioridad.
  • El tercero es el nombre del método que vamos a ejecutar.
  • El cuarto especifica los parámetros que le vamos a pasar al igual que se hacia con la construcción de la clase, en este caso se tendrá que especificar también el tipo que va a devolver nuestro método, este se representara igual que los parámetros pero aparece el tipo void que se representa por V y se pondrá a continuación de los parámetros es decir después del cierre del paréntesis, para un parámetro entero y un retorno booleano pondríamos “(I)Z”.El tipo de retorno tendremos que convertirlo nosotros.


Para la ejecución de un método estático usaremos la función:



long execute_java_dynamic_method(ExecEnv*, ClassClass*, char*, char*, ..)


Los parámetros son iguales a los de los métodos dinámicos a excepción del segundo parámetro que sigue indicando la clase de la cual se ejecuta el método pero en lugar de ser un puntero de tipo objeto es una estructura de clase, para obtener estructuras de clases ejecutaremos la función:



ClassClass *FindClass(ExecEnv*, char*, bool_t)



  • El primer parámetro es un puntero al entorno de ejecución.
  • El segundo parámetro es el nombre de la clase a buscar.
  • El tercero es un booleano si es true se invocara a resolveClass para cargar todas las clases que necesita.
















Fco. Javier Rodriguez Navarro trabaja como responsable de desarrollos en Tms con el objetivo de crear sistemas abiertos y escalables, de ahí la afición por Java.


Cada vez que tiene un rato libre se lanza a viajar y pasear para conocer nuevas gentes, paisajes y costumbres.



Para cualquier duda o tirón de orejas, e-mail a:
frodrigu_ARROBA_idecnet.com




Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.
Comentarios deshabilitados
Comentarios deshabilitados en esta noticia.