Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 16 - Utility-Klassen I

## 16.4 Die Klasse RunTime

- 16.4 Die Klasse RunTime
  - 16.4.1 Grundlagen
  - 16.4.2 Interaktion mit dem externen Programm

### 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 /* 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

Listing 16.8: 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.9: 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.4\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 | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de