javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

10 Eksterne kald og JNI


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:

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).

10.1 Kalde eksterne programmer

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

10.1.1 Kommunikation med det eksterne program

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'.

10.2 Kald til andre programmeringssprog

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).

10.2.1 JNI - Java Native Interface

JNI giver mulighed for at:

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.

10.2.2 Skrive Java-klasser med maskinkode

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++ !

10.2.3 Skrive en metode i 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.

10.2.4 Oversætte til maskinkode

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).

10.2.5 Køre programmet

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.

10.2.6 Kommunikation fra C-koden til Java

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.

10.2.7 Henvisninger

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (71% af værket).

Ønsker du at se de sidste 29% af dette værk (362838 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.