class HelloWorld {2.- Compilación usando javac.
private native void print(); // notar uso de palabra reservada "native", su uso permite no implementar método en Java.
public static void main(String[] args) {
new HelloWorld().print();
}
static { // asegura la carga de la biblioteca dinámica al inicio del programa.
System.loadLibrary("HelloWorld"); // OJO: en Linux la biblioteca debe llamarse libHelloWord.so
}
}
$ javac HelloWorld.javaSe genera archivo HelloWorld.class
$ javac -h . HelloWorld.javaSe genera archivo HelloWorld.h. En este caso su contenido esencial es:
JNIEXPORT void JNICALL Java_HelloWorld_printLa implementación C del método tiene un nombre que alude al código Java de origen, nombre de la clase y nombre del método. Aún cuando el método Java no tiene argumentos, su implementación considera dos. El primer argumento de toda implementación de método nativo es un puntero a una variable tipo JNIEnv, el segundo es una referencia al objeto HelloWorld mismo (como el puntero "this" en C++).
(JNIEnv *, jobject);
#include <jni.h>Se debe incluir encabezados según la implementación que hagamos. El código es C convencional.
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL // JNIEXPORT y JNICALL corresponde a macros requeridas, en medio el tipo retornado.
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
$cc -shared -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -o libHelloWorld.so HelloWorld.cSe debe cambiar el directorio según donde esté instalado Java. Aquí funcionan ambos porque hay dos versiones de Java instaladas en este computador. -fPIC fue agregado a sugerencia del compilador.
/* version 2014 , Java 7 */
$cc -shared -fPIC -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux -o libHelloWorld.so HelloWorld.c
/* version 2015, java version "1.7.0_91" */
$cc -shared -fPIC -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux -o libHelloWorld.so HelloWorld.c
Funcionó, tambien funcionó:
$cc -shared -fPIC -I/usr/lib/jvm/java-7-openjdk-amd64/include -I/usr/lib/jvm/java-7-openjdk-amd64/include/linux -o libHelloWorld.so HelloWorld.c
/* version 2016, java version "1.8.0_77" */
$cc -shared -fPIC -I/home/agustin/tools/Java/jdk1.8.0_77/include -I/home/agustin/tools/Java/jdk1.8.0_77/include/linux -o libHelloWorld.so HelloWorld.c
/* version 2018 en servidor aragorn, OpenJDK 64-Bit Server VM (build 24.151-b01, mixed mode) "$javac -version" arroja "javac 1.7.0_151"*/
$cc -shared -fPIC -I/usr/lib/jvm/java-7-openjdk-amd64/include -I/usr/lib/jvm/java-7-openjdk-amd64/include/linux -o libHelloWorld.so HelloWorld.c
/* version 2021 en servidor aragorn, "$javac -version" arroja OpenJDK 64-Bit Server VM 21.9 (build 17+35, mixed mode, sharing)*/
cc -shared -fPIC -I/usr/lib/jvm/java-17-openjdk-17.0.0.0.35-1.rolling.el7.x86_64/include -I/usr/lib/jvm/java-17-openjdk-17.0.0.0.35-1.rolling.el7.x86_64/include/linux -o libHelloWorld.so HelloWorld.c
/* version 2022 en servidor aragorn, "$javac -version" arroja javac 17.0.2*/
cc -shared -fPIC -I/usr/lib/jvm/java-17-openjdk/include -I/usr/lib/jvm/java-17-openjdk/include/linux -o libHelloWorld.so HelloWorld.c
/* En general usar:*/
cc -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -o libHelloWorld.so HelloWorld.c
Como resultado se genera la biblioteca libHelloWorld.so
Notar la diferencia entre el nombre de la biblioteca y el nombre usado para cargarla en programa Java. Por convención como nombre de archivo se usa lib{NombreDeBiblioteca}.so Usamos .so para indicar que se trata de un Shared Object, por eso se debe usar opción -shared.$ export LD_LIBRARY_PATH=.Luego podemos ejecutar la aplicación:
$ java HelloWorldJava también permite especificar el path de la biblioteca en la línea de comando de la ejecución, en este caso poniendo la opción -Djava.library.path=. Es decir la variable java.library.path adopta la ruta del directorio actual.
Hello World ELO330!
$
$ java -Djava.library.path=. HelloWorld
Hello World ELO330!
$
class Prompt {Luego de compilar y correr javah, se genera el archivo Prompt.h, cuyo contenido fundamental es:
// native method that prints a prompt and reads a line
private native String getLine(String prompt, int i);
public static void main(String args[]) {
int i = 20; // sólo para agregar otro tipo de dato
Prompt p = new Prompt();
String input = p.getLine("Type a line: ", i);
System.out.println("User typed: " + input);
}
static {
System.loadLibrary("Prompt");
}
}
JNIEXPORT jstring JNICALL Java_Prompt_getLineString son objetos en Java, en C corresponden a char *. La implementación del método nativo está en Prompt.c. Para acceder adecuadamente a este objeto debemos hacer una conversión.
(JNIEnv *, jobject, jstring, jint);
#include <jni.h>A través del puntero env es posible acceder a funciones de JNI para efectuar conversiones. En esta conversión GetStringUTFChars solicita memoria para str. Una vez finalizado el uso de str, debemos retornar el espacio de memoria a través de ReleaseStringUTFChars.
#include <stdio.h>
#include "Prompt.h"
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt, jint i)
{
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("Number:%d, %s", i, str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
gets(buf);
return (*env)->NewStringUTF(env, buf);
}