Iván Zaera Avellón

Consultor tecnológico de desarrollo de proyectos informáticos.

Ingeniero en Informática

Ver todos los tutoriales del autor

Fecha de publicación del tutorial: 2006-02-14

Tutorial visitado 25.755 veces Descargar en PDF
INDICE

Indice


1.Introduccion.......................................................................................................................................... 2

2.Declaracion de la clase nativa................................................................................................................ 3

3.Implementacion de los metodos de la DLL.............................................................................................. 4

3.1.Nomenclatura de los metodos........................................................................................................ 4

3.2.Convencion de llamada.................................................................................................................. 4

3.3.Manipulacion de objetos................................................................................................................. 4

3.4.Manejo de excepciones................................................................................................................. 4

3.4.1.Capturar excepciones lanzadas por la parte Java...................................................................... 4

3.4.2.Lanzar excepciones a la parte Java......................................................................................... 5

4.Ejemplo de implementación................................................................................................................... 6

4.1.Parte Java..................................................................................................................................... 6

4.2.Parte C++..................................................................................................................................... 7

4.3.Paso a paso................................................................................................................................. 8

5.Apendice............................................................................................................................................ 11

5.1.Secuencias de escape en los nombres de funcion.......................................................................... 11

5.2.Signaturas de funcion................................................................................................................... 11

5.3.Equivalencia de tipos primitivos..................................................................................................... 12

5.4.Equivalencia de tipos compuestos................................................................................................. 12


1.               Introduccion

El objetivo de este tutorial es mostrar como se puede implementar una clase Java con metodos nativos escritos en C++ y contenidos en una DLL de Windows. Se puede extrapolar facilmente a Unix, generando librerias compartidas de Unix (normalmente ficheros con extension .so) en vez de DLLs.

El mecanismo JNI de Java viene incluido con cualquier JDK de Sun y consiste en unos ficheros .h que hay en el directorio include del JDK. Dichos ficheros .h definen la interfaz entre nuestra DLL nativa y la JVM.

Para crear una clase Java con metodos nativos solo hay que marcar los metodos de la clase con la palabra native y, despues, implementarlos en la DLL siguiendo una convenciones sencillas.

A la hora de ejecutar solo es necesario que nuestra clase Java carque la DLL con los metodos nativos mediante una llamada a System.loadLibrary. La DLL se buscara en el PATH de Windows y en todos los directorios apuntados por la propiedad del sistema Java llamada java.library.path.

Este tutorial tiene dos capitulos genericos (2 y 3) que explican como se implementan los metodos nativos, un capitulo 4 con un ejemplo, y un apendice con referencias extraidas de la documentacion del JDK 5.0 de Sun.

2.               Declaracion de la clase nativa

Lo primero que tenemos que hacer es crear un fichero .java con la definicion de la clase como si fuesemos a escribir una clase Java normal. Todos los metodos que esten implementados en la DLL nativa deben llevar el prefijo native.

Además, hay que definir el constructor estatico de la clase y, dentro de el, llamar a System.loadLibrary para cargar la DLL que implementara los metodos nativos.

Ejemplo:

package autentia;  

 

class Cls

{

     // Metodo implementado nativamente

     native double f(int i, String s);

 

     // Inicializacion de la clase

     static

     {

         // Cargar la DLL que implementa la clase

         System.loadLibrary(“pkg_autentia”);

     }

}

 

La DLL se puede llamar como queramos, no hay ninguna restriccion sobre el nombre. Lo importante son los nombres de los metodos.

Este .java hay que compilarlo y nos dara un .class que, al usarlo, cargara la DLL y llamara a los metodos nativos. Si no se carga la DLL, la JVM lanzara una excepcion UnsatisfiedLinkError al llamar a los metodos nativos.

3.               Implementacion de los metodos de la DLL

3.1.        Nomenclatura de los metodos

Los nombres de los metodos deben seguir una convención predefinida:

         Metodos sobrecargados: Java_<clase cualificada>_<nombre del metodo>__<signatura de los argumentos>

         Metodos unicos: Java_<clase cualificada>_<nombre del metodo>__<signatura de los argumentos>

Existen ciertas secuencias de escape para los nombres de los metodos que estan listadas en el apéndice. Asimismo, se puede consultar en el mismo apéndice una explicacion de como se compone la signatura de los argumentos. La signatura describe los argumentos que recibe el metodo de forma inequivoca y es necesaria puesto que, debido a la sobrecarga, metodos con el mismo nombre pueden recibir distintos argumentos.

3.2.        Convencion de llamada

La convencion de llamada de los argumentos implementados nativamente es la siguiente:

         Para Win32 se usa la convencion de llamada __stdcall (PASCAL).

         Para Unix se usa la convencion de llamada __cdecl (C).

         Parametros:

       Un puntero a objeto JNIEnv, que se usa para comunicarse con la JVM

       Según el tipo de metodo:

         Estatico: una referencia a la clase

         No estatico: una referencia al objeto (this)

       Resto de argumentos del metodo

3.3.        Manipulacion de objetos

Todos los objetos Java manipulados en la implementacion nativa se deben acceder a traves del puntero al objeto JNIEnv. Existen unas equivalencias entre tipos Java y tipos nativos que se pueden consultar en el apendice.

Se pueden consultar las funciones existentes en el objeto JNIEnv en la documentacion del JNI o en el fichero jni.h. Todas las funciones son bastante intuitivas, en cuanto al nombre y a los parametros.

Es importante saber que todo metodo o campo de objeto se accede a traves del objeto JNIEnv despues de haber obtenido su ID. Para obtener el ID de un metodo o un campo hay que emplear funciones tambien en el JNIEnv. El mecanismo de ID es necesario puesto que puede haber metodos sobrecargados, campos heredados, etc, y porque, ademas, todo el acceso a objetos Java debe ser indirecto para no interferir con el recolector de basura.

3.4.        Manejo de excepciones

3.4.1.    Capturar excepciones lanzadas por la parte Java

Cuando se llama a funciones del JNIEnv o de otras clases Java, se pueden producir excepciones. Para determinar si ha habido alguna excepcion se llama a la funcion ExceptionOccurred. Si hay alguna excepcion pendiente, se debe llamar a ExceptionClear para desactivarla (es como si se hubiese hecho un catch) o devolver el control a la parte Java (retornar del metodo) para que la excepcion se propague a la parte Java.

3.4.2.    Lanzar excepciones a la parte Java

Las excepciones que se quieran lanzar hacia la parte Java, se deben propagar con los metodos Throw o ThrowNew del JNIEnv.

4.               Ejemplo de implementación

A continuacion se describe un ejemplo de implementacion de un metodo nativo estatico y de otro no estatico. Ademas, en el metodo estatico se muestra como lanzar una excepcion.

4.1.        Parte Java

Las clase Java utilizadas tienen el siguiente codigo:

autentia/Cls.java

 

package autentia;

 

public class Cls

{

      native public int noEstatico(int i);

      native public static int estatico(int i) throws TestException;

     

      int campo;

     

      static

      {

            String lib = "tutorial-jni";

           

            System.err.println("cargando libreria "+lib );

            System.loadLibrary( lib );

      }

     

      public static void main( String[] args )

      {

            Cls t = new Cls();

            int i;

           

            i = t.noEstatico( 17 );

            System.err.println("t.noEstatico(17)="+i);

            System.err.println("t.campo="+t.campo);

 

            try

            {

                  i = Cls.estatico( 26 );

                  System.err.println("Cls.estatico(26)="+i);

                  i = Cls.estatico( -1 );

                  System.err.println("Cls.estatico(-1)="+i);

            }

            catch( TestException e )

            {

                  System.err.println("Cls.estatico(-1) catch "+e);

            }

      }

}

 

autentia/TestException.java

 

package autentia;

 

public class TestException extends Exception

{

      private static final long serialVersionUID = 1L;

 

      private int n;

     

      public TestException( String msg, int n )

      {

            super(msg);

            this.n = n;

      }

     

      public String toString()

      {

            return "TestException["+getMessage()+","+n+"]";

      }

}

4.2.        Parte C++

Los ficheros de la DLL, escrita en C++, tienen el siguiente codigo:

tutorial-jni.h

 

#include <jni.h>

 

// native public int no_estatico(int i);

extern "C" __declspec(dllexport) jint Java_autentia_Cls_noEstatico__I( JNIEnv* env, jobject obj, jint i );

 

// native public static int estatico(int i) throws TestException;

extern "C" __declspec(dllexport) jint Java_autentia_Cls_estatico__I( JNIEnv* env, jobject obj, jint i );

 

tutorial-jni.cpp

 

#include "tutorial-jni.h"

 

extern "C" __declspec(dllexport) jint Java_autentia_Cls_noEstatico__I( JNIEnv* env, jobject obj, jint i )

{

            // cambiar el valor del atributo "campo" del objeto

            jclass clazz = env->GetObjectClass(obj);

            jfieldID idCampo = env->GetFieldID( clazz, "campo", "I" );

            env->SetIntField( obj, idCampo, i );

 

            // devolver el valor pasado como parametro

            return i;

}

 

extern "C" __declspec(dllexport) jint Java_autentia_Cls_estatico__I( JNIEnv* env, jobject obj, jint i )

{

            // si nos pasan -1 lanzamos una excepcion

            if( i==-1 )

            {

                        jclass clazz = env->FindClass("autentia/TestException");

                        jmethodID idCtor = env->GetMethodID( clazz, "<init>", "(Ljava/lang/String;I)V" );

                        jstring msg = env->NewStringUTF( "mensaje de la excepcion" );

                        jobject ex = env->NewObject( clazz, idCtor, msg, -1 );

                        env->Throw( (jthrowable)ex );

            }

 

            // devolver el valor pasado como parametro

            return i;

}

4.3.        Paso a paso

Lo primero que haremos es escribir las clase Java con nuestro editor de ficheros favorito, guardarlas y compilarlas. Esto nos dara dos ficheros .class que nos permiten probar el ejemplo. Si ejecutamos la clase Cls, se producira el siguiente error:

Este error se debe a que la JVM no encuentra los metodos nativos. Logico, puesto que aun no hemos creado la DLL. Para crear la DLL, abrimos un nuevo projecto DLL en el Visual Studio de Microsoft o en el compilador de C que mas nos guste.

Añadimos al proyecto los ficheros tutorial-jni.h y tutorial-jni.cpp descritos mas arriba y modificamos los directorios de inclusion para que incluyan los directorios del JDK que contienen los ficheros .h del JNI. Dichos directorios son include e include/win32 dentro del directorio de instalacion del JDK. No son necesarias librerias puesto que todo lo necesario esta en los ficheros .h.

Compilamos la DLL y echamos el fichero resultante en el directorio base del proyecto Java:

A continuacion ejecutamos el proyecto de nuevo, con lo que obtenemos el siguiente resultado, que demuestra las llamadas al codigo C++ desde la clase Java:

 

 

5.               Apendice

Tablas y notas extraidas de la documentacion de JNI del JDK 5.0 de Sun.

5.1.        Secuencias de escape en los nombres de funcion

Escape Sequence

Denotes

_0XXXX

a Unicode character XXXX.
Note that lower case is used
to represent non-ASCII
Unicode characters, e.g.,
_0abcd as opposed to
_0ABCD.

_1

the character “_”

_2

the character “;” in signatures

_3

the character “[“ in signatures

5.2.        Signaturas de funcion

Type Signature

Java Type

Z

boolean

B

byte

C

char

S

short

I

int

J

long

F

float

D

double

L fully-qualified-class ;

fully-qualified-class

[ type

type[]

( arg-types ) ret-type

method type

 

For example, the Java method:

      long f (int n, String s, int[] arr);

has the following type signature:

      (ILjava/lang/String;[I)J

5.3.        Equivalencia de tipos primitivos

Java Type

Native Type

Description

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

N/A

 

The following definition is provided for convenience.

#define JNI_FALSE  0

#define JNI_TRUE   1

The jsize integer type is used to describe cardinal indices and sizes:

typedef jint jsize;

5.4.        Equivalencia de tipos compuestos

The JNI includes a number of reference types that correspond to different kinds of Java objects. JNI reference types are organized in the hierarchy shown in Figure 3-1.

jobject

java.lang.Object

 

jclass

java.lang.Class

 

jstring

java.lang.String

 

jarray

arrays nativos de Java

 

 

jobjectArray

Java.lang.Object []

 

 

jbooleanArray

boolean []

 

 

jbyteArray

byte []

 

 

jcharArray

char []

 

 

jshortArray

short []

 

 

jintArray

int []

 

 

jlongArray

long []

 

 

jfloatArray

float []

 

 

jdoubleArray

double []

 

jthrowable

java.lang.Throwable

 

Figure 3-1 Reference Type Hierarchy

In C, all other JNI reference types are defined to be the same as jobject. For example:

typedef jobject jclass;

In C++, JNI introduces a set of dummy classes to enforce the subtyping relationship. For example:

class _jobject {};

class _jclass : public _jobject {};

...

typedef _jobject *jobject;

typedef _jclass *jclass;

The jvalue union type is used as the element type in argument arrays. It is declared as follows:

typedef union jvalue {

    jboolean z;

    jbyte    b;

    jchar    c;

    jshort   s;

    jint     i;

    jlong    j;

    jfloat   f;

    jdouble  d;

    jobject  l;

} jvalue;

A continuación puedes evaluarlo:

Regístrate para evaluarlo

Por favor, vota +1 o compártelo si te pareció interesante

Share |
Anímate y coméntanos lo que pienses sobre este TUTORIAL:

Fecha publicación: 2007-08-09-07:57:20

Autor:

[juan ignacio] muy buena la informacion y la forma didactida de redactarla!!! exelente!

Fecha publicación: 2006-06-17-06:06:09

Autor:

[claudio chaucca] muy buena !!..my brother

Fecha publicación: 2006-04-02-01:13:41

Autor:

[max mena] me parece excelente voy a seguir visitando el sitio y me gustaria mantenerme en contaco con ustedes para hablar sobre mas temas