Cuando se
desea usar una librería del sistema operativo (.dll, .so, etc…) desde una aplicación Java la solución que nos
aporta el lenguaje Java es usar JNI y los métodos “native”. La arquitectura es
la siguiente:
Las
librerías se cargan al solo una vez y no es posible eliminarlas ni volver a
cargarlas durante la ejecución de la aplicación Java. Yo quiero proponer otra
alternativa (a la que llamaremos por control) y luego comprarla con JNI para
ver cual sería mejor.
Por la parte
de C, lo que propongo es no usar los métodos JNIEXPORT de la librería y crear
una aplicación C que recibe por entrada estándar una secuencia de bytes que
indicara que comando ejecutar y con que parámetros y escribirá en la salida
estándar los resultados de la operación que ejecute.
Por la parte
de Java, lo que propongo es no usar los método nativos ni cargar las librerías.
La aplicación Java ejecutará la aplicación C y tomará el control de la
ejecución. Al tomar el control puede:
- Parar la aplicación C y volver a lanzarla.
- Capturar la salida estándar de la aplicación C por lo que puede escribir y leer de ella.
La nueva
arquitectura seria:
La forma en
Java de lanzar una aplicación y tomar el control es la siguiente:
Clase Main:
public class MainNoJNI
{
public static void main (String arg[]) throws Exception{
String process = "D:\\fuentes\\noJNI\\noJNI\\Release\\noJNI.exe";
Runtime rtPrimary = Runtime.getRuntime();
final Process pidPrimary = rtPrimary.exec(process);
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
pidPrimary.destroy();
}
}));
DataOutputStream dataOutPrimary = new
DataOutputStream(pidPrimary.getOutputStream());
Date inicio = new Date();
byte [] entrada = generarBytesAltaCliente(new AltaEntrada());
dataOutPrimary.write( entrada );
dataOutPrimary.flush();
dataOutPrimary.close();
DataInputStream dataInPrimary = new
DataInputStream(pidPrimary.getInputStream());
// vamos a suponer que la salida es como mucho de 10 bytes
byte[] salida = new byte[10];
dataInPrimary.read( salida, 0, 10 );
AltaSalida altaSalida = interpretaSalida(salida);
Date fin = new Date();
//System.out.println("Con control has tardado : " + (fin.getTime() - inicio.getTime()) + " milisegundos");
System.out.println( (fin.getTime() - inicio.getTime()) );
pidPrimary.destroy();
}
private static AltaSalida interpretaSalida( byte[] salida )
{
AltaSalida altaSalida = new AltaSalida();
altaSalida.respuesta = salida[0];
byte [] codigo = new byte [4];
codigo[0] = salida[1];
codigo[1]= salida[2];
codigo[2]= salida[3];
codigo[3]= salida[4];
altaSalida.codigoUsuario = new String(codigo);
altaSalida.departamento = salida[5];
altaSalida.esFiable= salida[6];
altaSalida.fechaAltaDia = salida[7];
altaSalida.fechaAltaMes= salida[8];
altaSalida.fechaAltaAnyo = salida[9];
return altaSalida;
}
private static byte[] generarBytesAltaCliente(AltaEntrada altaEntrada)
{
// vamos a suponer que la entrada es como mucho de 10 bytes
byte[] salida = new byte[10];
salida[0] = altaEntrada.tipo; // 1 byte para el tipo de operacion. Alta usuario
byte[] nombre = altaEntrada.nombre.getBytes();
salida[1] = nombre[1];
salida[2] = nombre[2];
salida[3] = nombre[2];
salida[4] = nombre[3]; // 4 bytes para el nombre
salida[5] = altaEntrada.edad; // 1 byte para la edad
salida[6] = altaEntrada.sexo; // 1 byte el sexo
byte[] ret = new byte[2];
ret[0] = (byte)(altaEntrada.codigoPais & 0xff);
ret[1] = (byte)((altaEntrada.codigoPais >> 8) & 0xff);
salida[9] = altaEntrada.codigoCiudad; // 1 bytes para el codigo del ciudad
return salida;
}
}
La clase entrada seria:
public class AltaEntrada
{
public byte tipo;
public String nombre;
public byte edad;
public byte sexo;
public short codigoPais;
public byte codigoCiudad;
public AltaEntrada (){
tipo = 1;
nombre = "pepe";
edad = 20;
sexo = 1;
codigoPais = 300;
codigoCiudad = 100;
}
}
La clase salida:
public class AltaSalida
{
public byte respuesta;
public String codigoUsuario;
public byte departamento;
public byte esFiable;
public byte fechaAltaDia;
public byte fechaAltaMes;
public byte fechaAltaAnyo;
}
El codigo C:
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <string>
using namespace std;
string alta (string leido){
string salida;
char nombre[4];
char edad;
char sexo;
unsigned short codigoPais;
char codigoCiudad;
// parseamos
leido.copy(nombre, 4, 1);
edad = leido.at(5);
sexo = leido.at(6);
codigoPais = (leido.at(7)<<8) | leido.at(8);
codigoCiudad = leido.at(9);
// lo que tarde la operacion
Sleep(1000);
// creamos la salida
salida.append(1,0);
salida.append(1,'p');
salida.append(1,'e');
salida.append(1,'p');
salida.append(1,'e');
salida.append(1,12);
salida.append(1,1);
salida.append(1,11);
salida.append(1,10);
salida.append(1,5);
return salida;
}
int _tmain(int argc, _TCHAR* argv[])
{
char comando = -1;
string leido;
string respuesta;
// el comando 0 es cierra la aplicacion
cin >> leido;
comando = leido.at(0);
// es un alta
if (comando == 1)
respuesta = alta (leido);
else
respuesta = -1;
cout << respuesta ;
return 0;
}
Vamos ha
hacer una comparativa con varios conceptos para ver que opción puede resultar
mejor:
Concepto
|
JNI
|
Por control
|
Memoria
|
Se suma a al tamaño de la aplicación
Java
|
Va a parte de la aplicación Java
|
Forma de comunicación
|
Por memoria
|
Por entrada/salida estándar
|
Unidad de comunicación
|
Objetos/metodos – mapeo
|
Cadena – parseo
|
Error en C
|
Se cierra la aplicación/hilo
|
Se puede detectar y actuar en
consecuencia. La aplicación/hilo continúa
|
Instancias
|
Una
|
Infinitas
|
Cambio en ejecución
|
No se puede
|
Se puede
|
Rendimiento
|
Igual
|
Que es cada
concepto:
- Memoria: donde se coloca la parte C en memoria.
- Forma de comunicación: como se comunica la parte Java con la parte C.
- Unidad de comunicación: como Java le dice a C que ejecute un método y como C le da la respuesta de la ejecución.
- Error en C: que sucede en la parte Java si se produce un error grabe en la parte C.
- Instancias: instancias de la parte C que se pueden hacer desde Java.
- Cambio en ejecución: capacidad de cambiar la parte C en ejecución por otra parte C que tenga los mismos métodos.
Teniendo en
cuenta estos conceptos, la arquitectura “por control” parece mejor excepto
quizás en el apartado de Unidad de ejecución ya que JNI trata de que no se note
cuando la ejecución de la aplicación Java pasa por ser C ni que tampoco se note
el fin de esa ejecución C y la ejecución de la aplicación Java vuelve a ser
Java. Desde el punto de la programación, desde Java es mejor pero en la parte C
los objetos no son mapeados directamente si no que están en un objeto
indeterminado y cada campo del objeto hay que buscarlo uno a uno. Si son muchos
el código se complica y no queda tan claro que sea mas “limpio” que pasear un
array de bytes.
¿Pero que
pasa si no usas un objeto y lo que haces es pasarlo parámetro a parámetro?
No hay comentarios:
Publicar un comentario