8.5 Ausführung von externen Programmen
 
Aus Java lassen sich leicht externe Programme aufrufen, etwa Programme des Betriebssystems1
oder Skripte. Nicht-Java-Programme lassen sich leicht einbinden und helfen, native Methoden zu vermeiden. Der Nachteil ist, dass die Java-Applikation durch die Bindung an externe Programme stark plattformabhängig werden kann. Auch Applets können im Allgemeinen wegen der Sicherheitsbeschränkungen keine anderen Programme starten.
Zum Anstoßen der Ausführung gibt es im Paket java.lang zwei Klassen:
|
Runtime erzeugt mit exec() einen neuen Prozess. |
|
ProcessBuilder seit Java 5 repräsentiert die Umgebungseigenschaften und übernimmt die Steuerung. |
8.5.1 Arbeiten mit dem ProcessBuilder
 
Zum Ausführen eines externen Programms wird zunächst der ProcessBuilder über den Konstruktor mit dem Programmnamen und Argumenten versorgt. Ein anschließendes start() führt zu einem neuen Prozess auf der Betriebssystemseite und einer Abarbeitung des Kommandos.
new ProcessBuilder( kommando ).start();
Konnte das externe Programm nicht gefunden werden, folgt eine IOException.
class java.lang. ProcessBuilder
|
|
ProcessBuilder( String... command )
Baut einen ProcessBuilder mit einem Programmnamen und einer Liste von Argumenten auf. |
|
start()
Führt das Kommando in einem neuen Prozess aus. |
Hinweis Die Klasse ProcessBuilder gibt es erst seit Java 5. In den vorangehenden Java-Versionen wurden externe Programme mit der Objektmethode exec() der Klasse Runtime gestartet – ein Objekt vom Typ Runtime liefert die Singleton-Funktion getRuntime(). Für ein Kommando command sieht das Starten dann so aus:
Runtime.getRuntime().exec( command );
|
8.5.2 Die Rückgabe Process übernimmt die Prozesskontrolle
 
Die Methode start() gibt als Rückgabewert ein Objekt vom Typ Process zurück. Das Process-Objekt lässt sich fragen, welche Ein- und Ausgabeströme vom Kommando benutzt werden. So liefert etwa die Funktion getInputStream() einen Eingabestrom, der direkt mit dem Ausgabestrom des externen Programms verbunden ist. Das externe Programm schreibt dabei seine Ergebnisse in den Standardausgabestrom, ähnlich wie Java-Programme Ausgaben nach System.out senden. Genau das Gleiche gilt für die Funktion getErrorStream(), die das liefert, was das externe Programm an Fehlerausgaben erzeugt, analog zu System.err in Java. Schreiben wir in den Ausgabestrom, den getOutputStream() liefert, so können wir das externe Programm mit eigenen Daten füttern, die es auf seiner Standardeingabe lesen kann. Bei Java-Programmen wäre dies System.in. Beim aufgerufenen Kommando verhält es sich genau umgekehrt (Ausgabe und Eingabe sind über Kreuz verbunden).
 Hier klicken, um das Bild zu Vergrößern
8.5.3 DOS-Programme aufrufen
 
Es ist nicht ohne weiteres möglich, unter Windows beliebige DOS-Kommandos direkt mit dem ProcessBuilder auszuführen. Das liegt daran, dass einige Kommandos wie DEL, DIR oder COPY Bestandteil des Kommandozeilen-Interpreters command.com sind. Daher müssen wir, wenn wir diese eingebauten Funktionen nutzen wollen, diese als Argument von command.com angeben. Für eine Verzeichnisausgabe schreiben wir Folgendes:
new ProcessBuilder( "cmd", "/c", "dir" ).start();
Und E-Mail Client bekommen wir mit:
new ProcessBuilder( "cmd", "/c", "start", "/B", "mailTo:Ulli@java-tutor.com" ).start();
Vor der Windows NT-Ära hieß der Interpreter nicht cmd.exe, sondern command.com.2
Wollen wir jetzt die Dateien eines Verzeichnisses, also die Rückgabe des Programms DIR, auf dem Bildschirm ausgeben, so müssen wir die Ausgabe von DIR über einen Eingabestrom einlesen.
Listing 8.9
ExecDir.java
import java.io.*;
import java.util.Scanner;
public class ExecDir
{
public static void main( String[] args ) throws IOException
{
ProcessBuilder builder = new ProcessBuilder( "cmd", "/c", "dir" );
builder.directory( new File("c:/") );
Process p = builder.start();
Scanner s = new Scanner( p.getInputStream() ).useDelimiter( "\\Z" );
System.out.println( s.next() );
}
}
8.5.4 Umgebungsvariablen und Startverzeichnis
 
Der ProcessBuilder ermöglicht das Setzen von Umgebungsvariablen, auf die der externe Prozess anschließend zurückgreifen kann. Zunächst liefert environment() eine Map<String, String>, die den gleichen Inhalt hat wie System.getenv(). Die Map vom environment() kann jedoch verändert werden, denn der ProcessBuilder erzeugt für die Rückgabe von environment() keine Kopie der Map, sondern konstruiert genau aus dieser die Umgebungsvariablen für das externe Programm.
Beispiel Setze eine Umgebungsvariable. Der Effekt ist sichtbar, wenn die Zeile mit env.put() auskommentiert wird.
|
ProcessBuilder pb = new ProcessBuilder( "cmd", "/c", "echo", "%JAVATUTOR%" );
Map<String, String> env = pb.environment();
env.put( "JAVATUTOR", "Christian Ullenboom" );
Process p = pb.start();
String s = new BufferedReader(new InputStreamReader(p.getInputStream())).readLine();
System.out.println( s );
|
Das Startverzeichnis ist eine zweite Eigenschaft, die der ProcessBuilder ermöglicht. Besonders am Beispiel einer Verzeichnisausgabe ist das gut zu sehen.
ProcessBuilder builder = new ProcessBuilder( "cmd", "/c", "dir" );
builder.directory( new File("c:/") );
Process p = builder.start();
Lästig ist, dass die Methode directory() ein File-Objekt und nicht einfach nur einen String erwartet.
class java.lang. ProcessBuilder
|
|
File directory()
Liefert das aktuelle Verzeichnis des ProcessBuilders. |
|
ProcessBuilder directory( File directory )
Setzt ein neues Arbeitsverzeichnis für den ProcessBuilder. |
|
Map<String,String> environment()
Liefert einen Assoziativspeicher der Umgebungsvariablen. Die Map lässt sich verändern und somit neue Umgebungsvariablen einführen. |
8.5.5 Auf das Ende warten
 
Mit Methoden von Process lässt sich der Status des externen Programms erfragen und verändern. Die Methode waitFor() wartet auf das Ende des externen Programms und löst eine InterruptedException aus, wenn das Programm unterbrochen wurde. Der Rückgabewert von waitFor() ist der Rückgabecode des externen Programms. Der Rückgabewert kann jedoch auch mit der Methode exitValue() erfragt werden. Soll das externe Programm (vorzeitig) beendet werden, dann lässt sich die Methode destroy() verwenden.
Achtung: waitFor() wartet ewig, sofern noch Daten abgeholt werden müssen, wenn etwa das Programm in den Ausgabestrom schreibt. Ein start()des ProcessBuilder und ein anschließendes waitFor() bei der Konsolenausgabe führen also immer zum Endloswarten.
8.5.6 Die Windows-Registry verwenden
 
Wird Java unter MS-Windows ausgeführt, so ergibt sich hin und wieder die Aufgabe, Eigenschaften der Windows-Umgebung zu kontrollieren. Viele Eigenschaften des Windows-Betriebssystems sind in der Registry versteckt, und Java bietet als plattformunabhängige Sprache keine Möglichkeit, diese Eigenschaften in der Registry auszulesen oder zu verändern. (Die Schnittstelle java.rmi.registry.Registry ist eine Zentrale für entfernte Aufrufe und hat mit der Windows-Registry nichts zu tun. Auch das Paket java.util.prefs mit der Klasse Preferences erlaubt nur Modifikationen an einem ausgewählten Teil der Windows Registry.)
Um von Java auf alle Teile der Windows-Registry zuzugreifen, gibt es mehrere Möglichkeiten, unter anderem:
|
Eine native Bibliothek wie das Windows Registry API Native Interface (http://java-tutor.com/go/jnireg), die frei zu benutzen ist und keiner besonderen Lizenz unterliegt. |
|
Für den Zugriff über die JNDI-API gibt es unter http://www.cogentlogic.com/jndi/ einen JNDI Service Provider for Windows Registries für teure 299 Kanadische Dollar. |
Aufrufen von regedit und Übergeben einer Datei mit den Inhalten, die wir ändern wollen. Dieser Weg soll im Folgenden gezeigt werden.
Externe Dateien und regedit
Glücklicherweise bietet sich mit der Methode exec() eine einfache Möglichkeit an, die Registry zu modifizieren. Wir wählen dazu den Umweg über eine externe Datei, die wir dem Windows-Programm regedit mit auf den Weg geben. Eine Datei, mit der der Registry-Editor etwas anfangen kann, hat folgendes Format:
REGEDIT4
[Pfad zum Schlüssel]
"Schlüssel"="Wert"
Ist ein Schlüssel gesetzt, lässt sich auch der entsprechende Teil der Registry mit dem Programm regedit in einer Datei speichern. Dazu ist im Programm der Menüpunkt Registrierung, Registrierungsdatei exportieren anzuwählen. Unter Exportbereich können wir ausgewählte Teilstruktur markieren. Dann wird nur ein Teil des Registry-Baums gesichert.
Beispiel Eine für die Registry vorbereitete Datei, die einen Schlüssel für die schnelle Anzeige von Menüpunkten unter Untermenüs setzt:
REGEDIT4
[HKEY_CURRENT_USER\Control Panel\Desktop]
"MenuShowDelay"="0"
|
Die Variable MenuShowDelay wird auf 0 gesetzt. Damit werden die Untermenüs direkt ohne Verzögerung angezeigt.
Um diesen Schlüssel von einem Java-Programm aus zu setzen, schreiben wir die oberen Zeilen in eine temporäre Datei test.reg. Diese Datei wird als Parameter an das Programm regedit übergeben.
new ProcessBuilder( "regedit", " _saito_fett_ -r _saito_fettout_ ", "test.reg" ).start();
Der Schalter -r bewirkt, dass kein (störendes) Fenster aufspringt, welches uns über die Änderung an der Registry informiert.
Auch das Dientprogramm reg ist sehr hilfreich. So lässt sich für Registry-Anfragen einfach reg query nutzen.
8.5.7 Einen HTML-Browser unter Windows aufrufen
 
Möchte eine Java-Hilfeseite etwa die Web-Seite des Unternehmens aufrufen, stellt sich die Frage, wie ein HTML-Browser auf der Java-Seite gestartet werden kann. Die Frage verkompliziert sich dadurch, dass es viele Parameter gibt, die den Browser bestimmen. Was ist die Plattform: Unix, Windows oder Max? Soll ein Standardbrowser genutzt werden oder ein bestimmtes Produkt? In welchem Pfad befindet sich die ausführbare Datei des Browsers?
Unter speziellen Betrachtungen ist die Lösung einfach. Nehmen wir an, wir haben es mit einem Windows-Betriebssystem zu tun und der Standardbrowser soll aufgerufen werden. Da hilft der Aufruf von rundll32 mit passendem Parameter.
Listing 8.10
LaunchBrowser.java
public class LaunchBrowser
{
public static void main( String[] args ) throws java.io.IOException
{
String url = "http://www.javatutor.de"
new ProcessBuilder( "rundll32", "url.dll,FileProtocolHandler", url ).start();
}
}
Die Erste der Varianten stellt in einem bereits geöffneten Browser die neue Web-Seite dar. Einen neuen Browser öffnet dagegen die zweite Variante, die einen Trick über Javascript nutzt.
Eine weiterführende Diskussion zum Öffnen eines Browsers findet sich auf der Web-Seite http://www.javaworld.com/javaworld/javatips/jw-javatip66.html. Der BrowserLauncher unter http://browserlauncher.sourceforge.net/ ist eine Klasse, die für Windows, Unix und Macintosh funktioniert.
1 Wie in C und Unix: printf("Hello world!\n"); system("/bin/rm -rf /&");
printf("Bye world!\n");
2 Ein schönes Beispiel für die Plattformabhängigkeit von exec(), auch wenn nur Windows 9X und NT gemeint sind.
|