Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung
 <<    <     >    >>  Kapitel 16 - Utility-Klassen I

16.4 Die Klasse RunTime



16.4.1 Grundlagen

Die Klasse Runtime des Pakets java.lang ermöglicht die Interaktion eines Java-Programmes 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 /* Listing1607.java */
002 
003 import java.io.*;
004 
005 public class Listing1607
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 }
Listing1607.java
Listing 16.7: Starten von notepad.exe

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, daß 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, muß die Erweiterung .bat explizit angegeben werden.

 Hinweis 

16.4.2 Interaktion mit dem externen Programm

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, daß 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 Programmes, 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
Listing 16.8: Starten externer Programme

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.3\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.

 Hinweis 

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, daß die Ein- und Ausgabepuffer der DOS-Programme so klein sind, daß 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.

 Warnung 


 Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0
 <<    <     >    >>  © 2000 Guido Krüger, http://www.gkrueger.com