Programmazione:Java/JNI Example

Da WikiSitech.
Vai alla navigazioneVai alla ricerca

<< Back to Java

JNI Tutorial

Per una guida abbastanza esaustiva su JNI, fare riferimento al documento presente sul sito SUN.

Esempio JNI

JNI è l'acronimo di Java Native Interface, questa tecnologia consente di interfacciarsi con funzioni di libreria scritte in codice nativo, in questo modo è possibile sfruttare da programmi Java librerie scritte appositamente per una piattaforma. I passi necessari per effettuare uuna chaiamata JNI sono:

  1. Dichiarare nella classe Java quali sono i metodi che invocheranno funzioni native con la keyword native;
  2. Caricare dalla classe Java la libreria per la funzione nativa;
  3. Compilazione della classe Java e generazione del file header (file .h) che rappresenti l’interfaccia della funzione nativa;
  4. Scrivere il codice nativo che implementi la funzione del file header generato;

Vediamo un esempio con una classe Java che legge e visualizza sullo standard output il contenuto di un file utilizzando funzioni C per l'accesso al filesystem:

package com.netsitech.jni.test;

public class ReadFile {
	// Native method declaration
	native byte[] loadFile(String name);

	// Load the library
	static {
		try {
			System.loadLibrary("_ReadFile");
		} catch (UnsatisfiedLinkError e) {
			System.out.println("Could not load native code for resd file.");
			System.exit(1);
		}
	}

	public static void main(String args[]) {
		try {
			if (args.length == 0) {
				System.out.print("Error file name not found in command line!");
			}
			byte buffer[];
			// Create class instance
			ReadFile mappedFile = new ReadFile();
			// Call native method to load ReadFile.java
			buffer = mappedFile.loadFile(args[0]);
			// Print contents of ReadFile.java
			for (int i = 0; i < buffer.length; i++) {
				System.out.print((char) buffer[i]);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Dichiarazione del metodo nativo

La dichiarazione del metodo loadFile con la keyword native indica alla JVM il mapping con la funzione C chiamata Java_com_netsitech_jni_test_ReadFile_loadFile.

  native byte[] loadFile(String name);

Caricamento della libreria con il codice nativo

  static {
    System.loadLibrary("_ReadFile");
  }

Compilazine della classe Java e generazione del Header File

/develop/workspace_jni/_Scrapbook/src>javac com.netsitech.jni.test.ReadFile.java
/develop/workspace_jni/_Scrapbook/bin>javah -d /develop/workspace_jni_test/_Scrapbook/src -jni com.netsitech.jni.test.ReadFile

La signature generata del metodo nativo avrà alcuni parametri il cui significato è riportato sotto:

  /*
   * Class:     com_netsitech_jni_test_ReadFile
   * Method:    loadFile
   * Signature: (Ljava/lang/String;)[B
   */
  JNIEXPORT jbyteArray JNICALL Java_com_netsitech_jni_test_ReadFile_loadFile
    (JNIEnv *, jobject, jstring);

Osservazioni sulla signature

La firma del metodo nativo è mappato Java-C/C++

<Tipo di ritorno> Java_<nome package>_<Classe>_<Metodo>

le JNIEXPORT e JNICALL sono delle macro dipendenti dal sistema operativo definite in jni.h, il primo parametro JNIEnv è il puntatore all'environment JNI, il secondo è il puntatore alla classe stessa (cioè 'this'), poi segue la lista dei parametri del metodo.

Implementazione del metodo nativo

//#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
#include <jni.h>
#include "com_netsitech_jni_test_ReadFile.h"

inline void prepareException(JNIEnv *env, char* exname, char *des){
	jclass ex = env->FindClass(exname);
	if (ex != 0){
		env->ThrowNew(ex, des);
	}
}

/*
* Class:     com_netsitech_jni_test_ReadFile
* Method:    loadFile
* Signature:
*/

JNIEXPORT jbyteArray JNICALL Java_com_netsitech_jni_test_ReadFile_loadFile
(JNIEnv *env, jobject jobj, jstring name) {
	jbyteArray jb;
	jboolean iscopy = true;
	int result;
	char *m;

	//Cpp Version GetStringUTFChars
	const char *mfile = env->GetStringUTFChars(name, &iscopy);
	//Open in read_only and binary mode to skip CFLF system difference
	FILE* fd = fopen(mfile, "rb");

	if (fd == NULL) {
		char des[255] = "File not found: ";
		strcat(des, mfile);
		prepareException(env, "java/io/FileNotFoundException", des);
		return (0);
	}

	result = fseek(fd, 0, SEEK_END);
	if (result != 0){
		prepareException(env, "java/io/IOException", "Problem getting file size information");
		return (0);
	}
	int size = ftell(fd);

	result = fseek(fd, 0, SEEK_SET);
	if( result != 0 ) {
		prepareException(env, "java/io/IOException", "Problem getting file information");
		return (0);
	}

	m = (char*) malloc (size);
	memset(m,0,size);
	if (size != fread(m, sizeof (char), size, fd))	{
		free (m);
		fclose(fd);
		prepareException(env, "java/io/IOException", "Problem during file reading");
		return (0);
	}

	jb=env->NewByteArray(size);
	env->SetByteArrayRegion(jb, 0, size, (jbyte *)m);
	fclose(fd);
	env->ReleaseStringUTFChars(name, mfile);
	free (m);
	return (jb);
}

Gnu C/Linux

g++  -o lib_ReadFile.so -shared -Wl,-soname,lib_ReadFile.so
     -I/usr/java/jdk1.5.0_15/include
     -I/usr/java/jdk1.5.0_15/include/linux src/com_netsitech_jni_test_ReadFile.cpp
     -static -lc

Win32

cl -I "C:\Programmi\Java\jdk1.5.0_15\include"
   -I "C:\Programmi\Java\jdk1.5.0_15\include\win32"
   -LD "src\com_netsitech_jni_test_ReadFile.cpp" -Fe "Debug\_ReadFile.dll"

Nota: Con l'ambiente MS Visual Studio è utile definire una environment variabile JAVA_HOME e aggiungere al progetto nella property "Additional Include Directories" il path "$(JAVA_HOME)/include";"$(JAVA_HOME)/include/win32" per i jni*.h headers.

Avviare l'esempio

Per poter avviare l'esempio è necessario indicare alla JVM dove ricercare la libreria nativa per renderla diponibile all'applicazione.

Unix or Linux:

 LD_LIBRARY_PATH=`pwd`
 export LD_LIBRARY_PATH

Windows:

 set PATH=%path%;.

Nota: Se la JVM non trova la libreria riporta il segeunte errore,

java.lang.UnsatisfiedLinkException: no _ReadFile in java.library.path

con la stampa dello stack delle chiamate.

 /develop/workspace_jni/_Scrapbook/src>java com/netsitech/jni/test/ReadFile.java

Script Ant per generazione header

<?xml version="1.0" encoding="UTF-8"?>
<project name="ReadFile_ANT" basedir=".">
	<description>
    	Script Ant per generare gli header C/C++ per la classe ReadFile
    </description>

	<target name="jni_ReadFile" description="Genera gli header C/C++ per la classe ReadFile">
		<javah destdir="./src" classpath="../_ReadFile/bin" verbose="true">
			<class name="com.netsitech.jni.test.ReadFile" />
		</javah>
	</target>

</project>