Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 5. Auflage |
<< | < | > | >> | API | Kapitel 16 - Utility-Klassen I |
Die Klasse Runtime des Pakets java.lang ermöglicht die Interaktion eines Java-Programms mit dem Bestandteil seiner Laufzeitumgebung, der dafür verantwortlich ist, Programme zu starten und zu beenden. Dabei ist es insbesondere möglich, mit seiner Hilfe externe Programme und Kommandos zu starten, mit ihnen zu kommunizieren und ihren Zustand zu überwachen.
Objekte der Klasse Runtime dürfen vom Programm nicht selbst instanziert werden, sondern werden über die statische Methode getRuntime der Klasse Runtime beschafft:
public static Runtime getRuntime() |
java.lang.Runtime |
Um ein externes Programm zu starten, gibt es vier Methoden mit dem Namen exec:
public Process exec(String command) throws IOException public Process exec(String command, String[] envp) throws IOException public Process exec(String[] cmdarray) throws IOException public Process exec(String[] cmdarray, String[] envp) throws IOException |
java.lang.Runtime |
Das auszuführende Kommando kann wahlweise als Einzelstring angegeben werden (Kommandoname plus Argumente, getrennt durch Leerzeichen), oder die einzelnen Bestandteile können in einem Array übergeben werden. Das optionale zweite Argument envp ermöglicht es, dem zu startenden Programm eine Liste von Umgebungsvariablen zu übergeben.
Das folgende Listing zeigt die einfachste Form der Anwendung von exec. Das Programm startet den notepad-Editor unter Windows:
001 /* Listing1608.java */ 002 003 import java.io.*; 004 005 public class Listing1608 006 { 007 public static void main(String[] args) 008 { 009 try { 010 Runtime.getRuntime().exec("notepad"); 011 } catch (Exception e) { 012 System.err.println(e.toString()); 013 } 014 } 015 } |
Listing1608.java |
Leider macht die API-Dokumentation keine Angaben darüber, in welchen Verzeichnissen nach dem auszuführenden Programm gesucht wird. In den aktuellen Implementierungen des JDK scheint es jedoch so zu sein, dass alle Verzeichnisse durchsucht werden, die in der PATH-Umgebungsvariablen angegeben sind. Unter Windows werden dabei aber offensichtlich nur Dateien mit den Erweiterungen .com und .exe automatisch berücksichtigt. Soll dagegen eine .bat-Datei gestartet werden, muss die Erweiterung .bat explizit angegeben werden. |
|
exec gibt ein Objekt des Typs Process zurück, das dazu verwendet werden kann, mit dem gestarteten Programm zu interagieren. Dazu definiert Process folgende Methoden:
public abstract int waitFor() throws InterruptedException public abstract int exitValue() public abstract void destroy() |
java.lang.Process |
Ein Aufruf von waitFor terminiert erst, wenn der zugehörige Prozess beendet wurde. Diese Methode kann also dazu verwendet werden, auf das Ende des gestarteten Programms zu warten. Das ist beispielsweise sinnvoll, um den Rückgabewert des Programms auszuwerten oder um mit von ihm erzeugten oder veränderten Daten zu arbeiten. Wird waitFor nicht aufgerufen, kann auf aktuellen Betriebssystemen wohl davon ausgegangen werden, dass das externe Programm parallel zum Java-Programm gestartet wird und asynchron weiterläuft. Diese Aussage ist allerdings mit Vorsicht zu genießen, denn spezifiziert ist dieses Verhalten nicht. Im Zweifel hilft ausprobieren.
Nach Ende des externen Programms kann mit exitValue sein Rückgabewert abgefragt werden. Dieser gibt bei vielen Programmen an, ob es fehlerfrei ausgeführt werden konnte oder nicht. Das ist aber nicht zwangsläufig so. Unter Windows wird insbesondere beim Aufruf von Batch-Dateien und bei der expliziten Ausführung eines Kommandos in einen eigenen Kommandointerpreter der Rückgabewert nicht weitergegeben. In diesem Fall liefert exitValue immer den Wert 0. Wird exitValue aufgerufen, wenn der Prozess noch läuft, gibt es eine IllegalThreadStateException.
Die Methode destroy dient dazu, das externe Programm abzubrechen. Es handelt sich hierbei nicht um das normale Beenden eines Programms, sondern um einen harten Abbruch. Ungesicherte Änderungen gehen also verloren und es können Inkonsistenzen in manipulierten Daten entstehen.
Das Process-Objekt bietet zusätzlich die Möglichkeit, die Standardein- und -ausgabe des externen Kommandos umzuleiten und aus dem eigenen Programm heraus anzusprechen:
public OutputStream getOutputStream() public abstract InputStream getInputStream() public abstract InputStream getErrorStream() |
java.lang.Process |
getInputStream und getErrorStream liefern einen InputStream, mit dem die Ausgaben des Prozesses auf Standardausgabe und Standardfehler gelesen werden können. Von getOutputStream wird ein OutputStream zur Verfügung gestellt, mit dem Daten in die Standardeingabe des Prozesses geschrieben werden können. Auf diese Weise lassen sich Programme, die über Standardein- und -ausgabe kommunizieren, fernsteuern bzw. fernabfragen.
Das folgende Programm fasst die wichtigsten Möglichkeiten zusammen:
001 /* RunCommand.java */ 002 003 import java.io.*; 004 005 public class RunCommand 006 { 007 static final int MODE_UNCONNECTED = 0; 008 static final int MODE_WAITFOR = 1; 009 static final int MODE_CATCHOUTPUT = 2; 010 011 private static void runCommand(String cmd, int mode) 012 throws IOException 013 { 014 Runtime rt = Runtime.getRuntime(); 015 System.out.println("Running " + cmd); 016 Process pr = rt.exec(cmd); 017 if (mode == MODE_WAITFOR) { 018 System.out.println("waiting for termination"); 019 try { 020 pr.waitFor(); 021 } catch (InterruptedException e) { 022 } 023 } else if (mode == MODE_CATCHOUTPUT) { 024 System.out.println("catching output"); 025 BufferedReader procout = new BufferedReader( 026 new InputStreamReader(pr.getInputStream()) 027 ); 028 String line; 029 while ((line = procout.readLine()) != null) { 030 System.out.println(" OUT> " + line); 031 } 032 } 033 try { 034 System.out.println( 035 "done, return value is " + pr.exitValue() 036 ); 037 } catch (IllegalThreadStateException e) { 038 System.out.println( 039 "ok, process is running asynchronously" 040 ); 041 } 042 } 043 044 private static void runShellCommand(String cmd, int mode) 045 throws IOException 046 { 047 String prefix = ""; 048 String osName = System.getProperty("os.name"); 049 osName = osName.toLowerCase(); 050 if (osName.indexOf("windows") != -1) { 051 if (osName.indexOf("95") != -1) { 052 prefix = "command.com /c "; 053 } else if (osName.indexOf("98") != -1) { 054 prefix = "command.com /c "; 055 } 056 } 057 if (prefix.length() <= 0) { 058 System.out.println( 059 "unknown OS: don\'t know how to invoke shell" 060 ); 061 } else { 062 runCommand(prefix + cmd, mode); 063 } 064 } 065 066 public static void main(String[] args) 067 { 068 try { 069 if (args.length <= 0) { 070 System.out.println( 071 "Usage: java RunCommand [-shell] " + 072 "[-waitfor|-catchoutput] <command>" 073 ); 074 System.exit(1); 075 } 076 boolean shell = false; 077 int mode = MODE_UNCONNECTED; 078 String cmd = ""; 079 for (int i = 0; i < args.length; ++i) { 080 if (args[i].startsWith("-")) { 081 if (args[i].equals("-shell")) { 082 shell = true; 083 } else if (args[i].equals("-waitfor")) { 084 mode = MODE_WAITFOR; 085 } else if (args[i].equals("-catchoutput")) { 086 mode = MODE_CATCHOUTPUT; 087 } 088 } else { 089 cmd = args[i]; 090 } 091 } 092 if (shell) { 093 runShellCommand(cmd, mode); 094 } else { 095 runCommand(cmd, mode); 096 } 097 } catch (Exception e) { 098 System.err.println(e.toString()); 099 } 100 } 101 } |
RunCommand.java |
Das Hauptprogramm erwartet das zu startende Programm und seine Parameter als Argumente. Wird die Option »-catchoutput« angegeben, liest das Programm die Ausgaben des gestarteten Programms und gibt sie auf seiner eigenen Standardausgabe aus. Wird »-waitfor« angegeben, wartet das Programm auf das Ende des gestarteten Programms, ohne dessen Ausgaben anzuzeigen. In beiden Fällen wird schließlich der Rückgabewert des Programms ausgegeben. Durch Angabe der Option »-shell« kann das externe Programm mit einem separaten Kommandointerpreter gestartet werden. Das ist beispielsweise nützlich, um Shell-Kommandos auszuführen, die nicht als eigenständige Programmdateien existieren.
Der folgende Aufruf verwendet das Beispielprogramm, um das interne
MS-DOS-Kommando »set« auszuführen (es gibt die Inhalte
aller Umgebungsvariablen aus):
java RunCommand -shell -catchoutput set
Seine Ausgabe könnte etwa so aussehen:
Running command.com /c set
catching output
OUT> winbootdir=C:\WINDOWS
OUT> COMSPEC=C:\COMMAND.COM
OUT> TEMP=C:\tmp
OUT> TMP=c:\tmp
OUT> USER=guido
OUT> windir=C:\WINDOWS
OUT> PATH=C:\JDK1.6\BIN;C:\WINDOWS;C:\WINDOWS\COMMAND;
OUT> CLASSPATH=.;c:\arc\prog\java
OUT> PROMPT=$p--$g
done, return value is 0
Die Übergabe des Programms an einen separaten Kommandointerpreter erfolgt in der Methode runShellCommand ab Zeile 044. Wie ein derartiger Aufruf ausgeführt wird, ist natürlich betriebssystem- und konfigurationsabhängig. Das Beispielprogramm versucht, einige brauchbare Varianten für gängige Betriebssysteme vorzudefinieren. Weitere können leicht hinzugefügt werden. |
|
Während des Tests von RunCommand gab es mitunter Schwierigkeiten beim Ausführen interner DOS-Programme unter Windows 95 und 98. Während sich beispielsweise das Kommando set problemlos aufrufen ließ, gab es beim Aufruf von dir Hänger, nach denen die MS-DOS-Task hart abgebrochen werden mußte. Die JDK Bug Database listet eine ganze Reihe von Problemen in Zusammenhang mit dem Aufruf von 16-Bit-Programmen unter Windows 95 oder 98 auf. Sie rühren unter anderem daher, dass die Ein- und Ausgabepuffer der DOS-Programme so klein sind, dass die Programme mitunter schon blockieren, bevor die aufrufende Applikation die Chance hatte, eine Verbindung zu ihnen herzustellen. Echte Workarounds für diese Probleme scheinen nicht bekannt zu sein. Beim Aufruf von 32-Bit-Programmen treten die Probleme offenbar nicht auf. |
|
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 5. Auflage, Addison Wesley, Version 5.0.2 |
<< | < | > | >> | API | © 1998, 2007 Guido Krüger & Thomas Stark, http://www.javabuch.de |