kramann.info
© Guido Kramann

Login: Passwort:










4.4 Umsetzung mit JNI / Kommunikation mit Java

4.4 (EN google-translate)

4.4 (PL google-translate)

Es ist möglich Strings zwischen Java und einer Native-Funktion auszutauschen. In unserem Fall wird dann generell von Java der auszuführende Konsolenbefehl an die Native-Funktion übergeben. Diese wird stets so aufgerufen, dass eventuelle Standard- und Fehlerausgaben in die Dateien out.txt und err.txt geschrieben werden und diese dann eingelesen, zu einem einzigen String zusammengefasst und an Java zurückgegeben werden.

generalcall Umsetzung mit JNI

Im folgenden werden die hierzu notwendigen Schritte durchgegangen.

1. Schreiben und Kompilieren der Wrapper-Class in Java, die die Native-Funktion aufrufen soll:
//Zur später leichteren Einbettung in größere Java-Programme, wird
//das hier entstehende Programm in einem Package organisiert.
//D.h. es muss ein Ordner nativ existieren und in ihm ein Ordner generalcall
//Kompiliert wird oberhalb des Ordners nativ mit dem Befehl:
//javac javac nativ/generalcall/Generalcall.java 

package nativ.generalcall; 

public class Generalcall
{
    static 
    {
        //Laden der dynamischen Bibliothek von /mnt-system.
        //Die dynamische Bibliothek muß vorher dorthin kopiert worden sein! 
        System.load("/mnt-system/fhb_knoppix/start_skripte/Generalcall.so");
    }
    public static native String generalcall(String befehl);
}

Code 4.4-1: Generalcall - Java-Wrapper-Class zu generalcall.c (siehe weiter unten).

2. Schablone für die C-Header-Datei erzeugen:
/opt/jdk1.7.0/bin/javah -jni -o ./nativ/generalcall/Generalcall.h nativ.generalcall.Generalcall

Code 4.4-2: Schablone für die C-Header-Datei erzeugen.

Es entsteht hierdurch folgende C-Header-Datei, die nicht von Hand geändert werden darf: Der komplizierte Funktionsname und die Namen der Übergabeparameter folgen einem Schema, das es der JVM erlaubt zu erkennen, welchem Package und welcher Java-Klasse die Native-Methode zugeordnet ist.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class nativ_generalcall_Generalcall */

#ifndef _Included_nativ_generalcall_Generalcall
#define _Included_nativ_generalcall_Generalcall
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     nativ_generalcall_Generalcall
 * Method:    generalcall
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_nativ_generalcall_Generalcall_generalcall
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

Code 4.4-3: Generalcall.h

3. Aus Generalcall.h Funktionskopf für selbst zu schreibenden C-Quellcode in Generalcall.c auslesen:

Nun wird die Implementierung der c-Funktion Java_nativ_generalcall_Generalcall_generalcall gemäß der Header-Datei in die Datei Generalcall.c geschrieben. Die weiter oben besprochene Funktionalität wird hier umgesetzt:

#include<jni.h>
#include "Generalcall.h"
#include <stdlib.h>
#include <stdio.h>

JNIEXPORT jstring JNICALL Java_nativ_generalcall_Generalcall_generalcall(JNIEnv *env, jclass clazz, jstring in_befehl)                                                                   
{
    FILE* f;
    int i;
    int c;
    const char* ergaenzung = " > /mnt-system/out.txt 2> /mnt-system/err.txt";
    char *puffer1;
    char *puffer2;
    int anz,anzout,anzerr;

    //Von Java übergebenes String mit dem auszuführenden Konsolenbefehl in char* wandeln:
    const char *befehl = (*env)->GetStringUTFChars(env, in_befehl, 0);

    system(befehl);


    //Zeichenanzahl in err.txt und out.txt feststellen:
    f = fopen("/mnt-system/out.txt","r");
    c = fgetc(f);
    anzout=0;    
    while(c>=0)
    {
        //puffer[anz]=c;
        c = fgetc(f);
        anzout++;
    }
    fclose(f);    

    f = fopen("/mnt-system/err.txt","r");
    c = fgetc(f);
    anzerr=0;    
    while(c>=0)
    {
        //puffer[anz]=c;
        c = fgetc(f);
        anzerr++;
    }
    fclose(f);    

    //Speicher in passender Größe allokieren, damit die Dateien out.txt und err.txt darin Platz haben.
    //Es wird zuerst err.txt eingelesen und dann out.txt
    //Die Dateien werden durch einen klar erkennbaren Trenner unterteilt, für den auch noch Speicherplatz
    //reserviert werden muß.
    //Der Trenner heißt: !&$%#
    //Falls jemand auf die Idee käme, genau diese Zeichenfolge ausgeben zu lassen in dem Konsolenprogramm,
    //dann wäre das großes Pech.
    puffer2 = (char*)malloc((anzout+5+anzerr+1)*sizeof(char));

    //Jetzt werden die Daten in den puffer2 übertragen:    
    f = fopen("/mnt-system/out.txt","r");
    c = fgetc(f);
    anzout=0;    
    while(c>=0)
    {
        puffer2[anzout]=c;
        c = fgetc(f);
        anzout++;
    }
    fclose(f);    


    puffer2[anzout] = '!';
    puffer2[anzout+1] = '&';
    puffer2[anzout+2] = '$';
    puffer2[anzout+3] = '%';
    puffer2[anzout+4] = '#';
    f = fopen("/mnt-system/err.txt","r");
    c = fgetc(f);
    anzerr=0;    
    while(c>=0)
    {
        puffer2[anzout+5+anzerr]=c;
        c = fgetc(f);
        anzerr++;
    }
    fclose(f);    


    puffer2[anzout+5+anzerr]='';

    return (*env)->NewStringUTF(env, puffer2);  //Rückgabe an Java der Dateiinhalte von out.txt und err.txt
}

Code 4.4-4: Generalcall.c - Übernahme eines Konsolenbefehls von C, ausführen und Rückgabe der Konsolenausgaben an java als String.

Kompilieren der C-Funktion mit:

  • cd /mnt-system/Z_Vorlesungen/informatik3/zwischenprojekt/nativ/generalcall
  • gcc -o Generalcall.so -shared -Wl,-soname,Generalcall.so -I/opt/jdk1.7.0/include -I/opt/jdk1.7.0/include/linux Generalcall.c -static -lc

Die entstehende Datei Generalcall.so wird dann in das Verzeichnis /mnt-system/fhb_knoppix/start_skripte/Generalcall.so kopiert, da die Wrapper-Java-Class dies so erwartet.

Testen des Nativ-Aufrufs mit Hilfe einer einfachen Java-Anwendung:

import nativ.generalcall.Generalcall;

public class TestGeneralcall
{
    public static void main(String[] args)
    {
        String ergebnis = Generalcall.generalcall("avrdude -c ponyser -p m32 -P /dev/ttyS0 -v > /mnt-system/out.txt 2> /mnt-system/err.txt");
        System.out.println(ergebnis);
    }
}

Code 4.4-5: TestGeneralcall.java - liegt in der Ebene, wo auch der Ordner nativ liegt.