10.1 Kalde eksterne programmer 146
10.1.1 Kommunikation med det eksterne program 147
10.2 Kald til andre programmeringssprog 147
10.2.1 JNI - Java Native Interface 147
10.2.2 Skrive Java-klasser med maskinkode 147
10.2.3 Skrive en metode i C++ 148
10.2.4 Oversætte til maskinkode 149
10.2.5 Køre programmet 149
10.2.6 Kommunikation fra C-koden til Java 150
10.2.7 Henvisninger 150
Undertiden får man brug for at kalde noget kode, der ikke er skrevet i Java. Man kan stå i to situationer:
Man ønsker at aktivere en kommando eller et eksternt program fra Java.
Man ønsker af den ene eller anden grund at skrive noget af sit program i et andet programmeringssprog end Java, f.eks. for at bruge et programbibliotek, der ikke findes til Java.
Det første er enklere end det sidste, hvorfor man bør overveje, om ens problemstilling kan løses ved at lave et eksternt program og kalde det.
I begge tilfælde skal man være opmærksom på, at man mister platformsuafhængigheden, dvs. låser brugeren fast på en bestemt platform, hvis man kalder et program eller et programbibliotek, som kun findes på en enkelt platform (hvis man f.eks. vil bruge Windows' win32-API-kald, har man låst brugeren af programmet fast på Windows-platformen).
Hvis man vil aktivere et eksternt program fra Java, kan man gøre dette med metoden exec(), der findes i Runtime-klassen. I parameteren til exec() kan man skrive kommandoen, ligesom hvis programmet var aktiveret fra kommandolinjen.
Her er et program, der kalder det eksterne program 'sort' (der er valgt, fordi det findes under både UNIX/Linux og DOS/Windows):
import java.io.*; public class KaldEksterntProgram { public static void main(String[] args) throws Exception { // Start programmet 'sort', der sorterer linerne i en fil Process proces = Runtime.getRuntime().exec("sort"); // sort kan læse data fra standard input og skriver dem på standard output PrintWriter s = new PrintWriter(proces.getOutputStream()); BufferedReader l = new BufferedReader( new InputStreamReader(proces.getInputStream())); s.println("En"); s.println("snegl"); s.println("på"); s.println("vejen"); s.println("er"); s.println("tegn"); s.println("på"); s.println("regn"); s.println("i"); s.println("Spanien"); s.close(); // luk datastrømmen (sort sorterer først når den har alle data) // Læs resultatet fra programmets standard output og udskriv det String lin; while ((lin = l.readLine()) != null) System.out.println("Fra sort: "+lin); } }
Fra sort: En Fra sort: er Fra sort: i Fra sort: på Fra sort: på Fra sort: regn Fra sort: snegl Fra sort: Spanien Fra sort: tegn Fra sort: vejen
Programmet 'sort' læser data fra standard input (normalt fra tastaturet eller omdirigeret fra en fil) og skriver dem på standard output (normalt til skærmen eller omdirigeret til en fil).
I eksemplet får vi fat i processens standard input med getOutputStream() (det, vi skriver til programmet, bliver jo programmets input) og standard output med getInputStream().
Man skal være opmærksom på, at hvis ens javaprogram forsøger at læse noget fra et eksternt programs standard output, inden det har skrevet noget, vil læsekaldet 'hænge', indtil der kommer data fra det eksterne program (det kan undertiden forvirre den mindre erfarne programmør).
Vil man blot vente på, at det eksterne program er kørt færdigt, kan man bruge kaldet:
proces.waitFor(); // vent på at processen er færdig
Det er der et eksempel på i afsnit 11.4.1, Kalde oversætteren som eksternt program. Samme sted kan man se, hvordan man kan overføre parametre til det eksterne program ved simpelt hen at skrive dem efter programnavnet:
Process p = r.exec("javac UndersoegKlasse.java");
I dette tilfælde får javac parameteren 'UndersoegKlasse.java'.
Hvis man af den ene eller anden grund ønsker at skrive noget af sit program i et andet programmeringssprog end Java, f.eks. for at bruge et programbibliotek, der ikke findes til Java, skal man bruge JNI (Java Native Interface).
JNI giver mulighed for at:
Kalde andre sprog fra Java, dvs. at kalde maskinkode fra Java. Denne maskinkode kan være genereret ud fra kildetekst skrevet i andre programmeringssprog, f.eks. C, C++ eller Pascal.
Kalde Java fra andre sprog, dvs. starte Javas virtuelle maskine op, indlæse klasser og kalde metoder i dem fra kildetekst skrevet i andre programmeringssprog.
I det følgende vil kun den første mulighed (kalde andre sprog fra Java) blive behandlet overfladisk. Ønsker man at vide mere om JNI, findes der nogle henvisninger i afsnit 10.2.7.
Man kan implementere metoder i en klasse i f.eks. C, C++ eller direkte i maskinkode ved at markere dem med nøgleordet native i javakoden:
package vp; public class HejVerdenFraCKode { public native void hejVerden(); }
Her er et program, der benytter klassen HejVerdenFraCKode efter at have indlæst den nødvendige maskinkode:
package vp; public class BenytHejVerdenFraCKode { public static void main(String[] args) { System.out.println("Indlæser maskinkoden."); // Indlæs maskinekodebiblioteket libHej.so (UNUX) eller hej.dll (Windows) try { System.loadLibrary("Hej"); } catch (Error e) { System.err.println("Fejl ved indlæsning af maskinkodeblok."); e.printStackTrace(); System.exit(1); // afslut programmet } System.out.println("Opretter objekt."); HejVerdenFraCKode objekt = new HejVerdenFraCKode(); System.out.println("Kalder metode implementeret i maskinkode."); objekt.hejVerden(); } }
Indlæser maskinkoden. Opretter objekt. Kalder metode implementeret i maskinkode. Hej Verden fra C++ !
Efter at have skrevet klassen skal den oversættes til bytekode, dvs. fra .java-fil til .class-fil. Det kan gøres i et udviklingsværktøj eller fra kommandolinjen med kommandoen:
javac vp/HejVerdenFraCKode.java
Husk, at da klassen ligger i pakken 'vp', skal den også ligge i underkataloget 'vp'.
Nu har vi filen vp/HejVerdenFraCKode.class. Så skal der genereres en C-headerfil med:
javah -jni vp.HejVerdenFraCKode
Denne headerfil (der kommer til at hedde vp_HejVerdenFraCKode.h) indeholder kun navnet på metoden hejVerden(), der i C-koden bliver omdøbt til at indeholde pakkenavnet og klassenavnet (Java_vp_HejVerdenFraCKode_hejVerden).
I den genererede headerfil står:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class vp_HejVerdenFraCKode */ #ifndef _Included_vp_HejVerdenFraCKode #define _Included_vp_HejVerdenFraCKode #ifdef __cplusplus extern "C" { #endif /* * Class: vp_HejVerdenFraCKode * Method: hejVerden * Signature: ()V */ JNIEXPORT void JNICALL Java_vp_HejVerdenFraCKode_hejVerden (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
Denne metode skal vi selv implementere i f.eks. C++. Vi opretter derfor en fil kaldet vp_HejVerdenFraCKode.cpp og skriver C++-koden, der implementerer metoden:
#include "vp_HejVerdenFraCKode.h" #include <stdio.h> JNIEXPORT void JNICALL Java_vp_HejVerdenFraCKode_hejVerden (JNIEnv *e, jobject o) { printf("Hej Verden fra C++ !\n"); }
Denne kode skal oversættes til et bibliotek med maskinkode (eng.: shared library), der kan indlæses fra Java. Præcist hvordan dette gøres afhænger af styresystemet.
Fra UNIX/Linux kunne det gøres ved at kalde standard C++-oversætteren:
g++ -shared -g -I/usr/include/java vp_HejVerdenFraCKode.cpp -o libHej.so
Man skal huske, at '/usr/include/java' skal pege hen på, hvor ens Java-headerfiler findes. Det kan også være, at man er nødt til at inkludere flere kataloger med yderligere headerfiler, f.eks.:
g++ -shared -g -I/usr/local/java/include -I/usr/local/java/include/linux \
vp_HejVerdenFraCKode.cpp -o libHej.so
Under UNIX/Linux genereres en .so-fil (som her hedder libHej.so). Under Windows skal der genereres en .DLL-fil (dynamic link library), der tilsvarende ville hedde Hej.DLL (præcis hvordan man gør afhænger af, hvilken C++-oversætter man bruger).
Når kommandoen System.loadLibrary("Hej") kaldes i eksemplets main()-metode ovenfor bliver filen libHej.so eller Hej.DLL (afhængig af styresystemet) indlæst af den virtuelle maskine og lænket sammen med resten af programmet.
Vi starter programmet med:
java vp.HejVerdenFraCKode
Hvis den genererede maskinkode ikke ligger der hvor systemet i øvrigt lægger sine maskinkodebiblioteker (dvs. i /usr/lib/ under UNIX og c:\win\system\ under Windows), får man fejlen:
Indlæser maskinkoden. Fejl ved indlæsning af maskinkodeblok. java.lang.UnsatisfiedLinkError: no Hej in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1403) at java.lang.Runtime.loadLibrary0(Runtime.java:788) at java.lang.System.loadLibrary(System.java:832) at vp.HejVerdenFraCKode.main(HejVerdenFraCKode.java:11)
Da kan man fortælle den virtuelle maskine, at den skal lede efter maskinkode i det aktuelle katalog ('.') med:
java -Djava.library.path=. vp.HejVerdenFraCKode
Nu udskriver programmet:
Indlæser maskinkoden. Opretter objekt. Kalder metode implementeret i maskinkode. Hej Verden fra C++ !
Den sidste linje bliver udskrevet fra C++, så det lykkedes os at bruge JNI.
Ser man nærmere på erklæringen af metoden opdager man, at selvom java-metoden ikke havde nogle parametre, så har C-koden to parametre:
JNIEXPORT void JNICALL Java_vp_HejVerdenFraCKode_hejVerden (JNIEnv *e, jobject o)
Den første er en pointer til den virtuelle maskine. Den skal bruges, hvis man ønsker at kommunikere med den virtuelle maskine, f.eks. hvis man vil oprette nye Java-objekter fra C-koden.
Den anden variabel er en pointer til objektet (instansen af HejVerdenFraCKode), som metoden blev kaldt på (eller klassen, hvis metoden hejVerden havde været en klassemetode).
Disse to pointere bruges i udstrakt grad i JNI til at få adgang til metoder eller variabler i objektet eller klassen, til at oprette objekter etc. etc.
Man skal være opmærksom på, at den virtuelle maskine kan finde på at flytte rundt på objekterne i hukommelsen, når den rydder op i ubrugte objekter (garbage collection). Derfor skal man fra C-koden fortælle den virtuelle maskine, hvilke objekter man er i gang med at arbejde i, så den ikke flytter dem samtidig.
Suns dokumentation af JNI
findes på
http://java.sun.com/products/jdk/1.3/docs/guide/jni/
Ønsker du for alvor at bruge JNI har Kristian Hansens bog 'Avanceret Java-programmering', der kan hentes gratis på http://bog.ing.dk/, en meget grundig behandling af emnet.