21.01.08
9.28
Да-а-а-а... "совсем ерунда - перенаправить вывод программ куда-нибудь...". Все оказалось не так просто, как я думал. В тех книжках, где я имел счастье видеть примеры запуска внешних программ либо не было сказано, как это делать вообще, либо описано это очень неточно и неполно. К такому выводу я пришел, когда у меня наконец-то все заработало.
Итак, сперва я решил просто запустить внешнюю программу, но не notepad, как в большинстве примеров в книгах по java (на мой взгляд, более бесполезного примера трудно придумать), а внешнюю java-программу.Строка запуска должна быть вот такой:
java -cp . -ea HelloWorld
где программка HelloWorld выводит всем известную со времен Кернигана и Ричи фразу :). Сам текст внешней программки:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
System.out.println("Hello, world!");
System.exit(2);
}
}
Дело видете ли в том, что мне помимо запуска внешней программы надо получить код, возвращаемый программой, и если он отличается от 0 (т.е. произошла ошибка), вывести сообщение, которое вывела внешняя программа. В данном случае возвращаю 2, что бы было понятно что получившееся значение - результат работы именно этой программы, а не что-либо еще (например, код ошибки, возвращаемый java-интерпретатором). Вот мое первое творение (весьма далекое от того, что должно быть, поскольку в кач-ве образца использовался простейший пример с notepad-ом из книги "Полный справочник по Java" Г.Шилдта):import java.lang.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello", "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Запускаем RunHello и пока все отлично - программа работает, только сообщение "Hello, world!" не выводится.
Теперь немножко изменим программку HelloWorld:
import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
System.out.println("Hello, world! " + i);
System.exit(2);
}
}
Теперь сообщение должно вывестись 40 раз подряд, вместе с порядковым номером вывода. Запускаем RunHello и.. ничего не происходит - программа благополучно повисает. Дабы этого не происходило, нужно читать вывод программы. То есть нужно читать из потоков, возвращаемых методами getInputStream() и getErrorStream(), определенных в классе Process.
Модифицируем нашу программку RunHello:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream()));
int m;
do{
m = stdinput.read();
System.out.print((char)m);
}while(m >= 0);
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Вывод дочернего процесса естественно, не обязательно печатать. Все замечательно работает, и нет проблем. Но пока рано радоваться :) Теперь немножко модифицируем программу HelloWorld. Помимо стандартного устройства вывода будем печатать еще и на стандартное устройство ошибок.import java.lang.*;
public class HelloWorld
{
static public void main(String [] args)
{
for (int i = 1; i <= 40; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
System.exit(2);
}
}
Теперь наша программа выведет некоторое число сообщений "Hello, world! " и встанет, поскольку мы в програме RunHello не читаем из стандартного устройства ошибок. Что ж, надо читать :)import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Java\\Projects\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int m1, m2;
do{
m1 = stdinput.read();
if(m1 > 0)
System.out.print((char)m1);
m2 = stderror.read();
}while((m1 >= 0) || (m2 >= 0));
int res = p.waitFor();
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
Пока у меня на Win98 все отлично работает. Попробуем в программе HelloWorld количество циклов увеличить с 40 до 80:for (int i = 1; i <= 80; i++)
{
System.out.println("Hello, world! " + i);
if(i < 30)
System.err.println("It's prints to stderr" + i);
}
Как видим, приключения еще не кончились! :) Опять программа подвисает, у меня - после вывода 40-го сообщения. Если честно, пока я проходил все эти стадии прозрения, мой стол немного пострадал, поскольку я лупил по нему кулаком от злости :) Так же немного пострадал мой кулак :).Но все же есть выход из положения, позволяющий запускать любые внешние программы, что угодно печатающие на консоль и в стандартное устройство ошибок. Прежде чем читать что-то из потоков stderror или stdinput, необходимо проверить, можно ли производить чтение и не приведет ли это к блокировке процессов. Это можно сделать, вызывая для потоков метод ready(). Он возвращает true(истину), если читать из потока можно. Итак, окончательная версия программки RunHello у меня получилась такой:
import java.lang.*;
import java.io.*;
public class RunHello
{
static public void main(String [] args)
{
String cmdline[] = {"java", "-cp", "C:\\Projects\\Java\\hello" , "HelloWorld"};
Runtime rt = Runtime.getRuntime();
Process p = null;
try{
p = rt.exec(cmdline);
BufferedReader stdinput = new BufferedReader(new InputStreamReader(p.getInputStream())),
stderror = new BufferedReader(new InputStreamReader(p.getErrorStream()));
int res=0;
int m1;
do
{
if( stderror.ready() || stdinput.ready())
{
if(stdinput.ready())
{
m1 = stdinput.read();
if(m1 >= 0)
System.out.print( (char)m1 );
}
if(stderror.ready())
stderror.read();
}
else
try{
res = p.exitValue();
break;
}catch(java.lang.IllegalThreadStateException ex){}
}while(true);
System.out.println("Exit code = " + res);
}catch(Exception e)
{
System.out.println("Runtime error with message: " + e);
}
}
}
В цикле сначала проверяем, готов ли какой-либо поток к чтению из него. Если оба потока не готовы - есть вероятность, что программа закончила свою работу (но это еще не факт!! Как корректно проверить, завершил процесс работу или нет, я так и не нашел в документации на Java API. Если кто знает - напишите, буду весьма признателен! :) ) После пробуем получить значение, возвращаемое внешней программой. Если внешняя программа завершила работу - завершаем цикл и печатаем возвращаемый код.
4 коммент.:
Я же говорила, что писатель у нас - муж! Короче, много букофф ты, любимый, настрочил. Вот только все слова мне непонятные:)))
Так чем плох способ определения завершения программы, приведенный в тексте? try{
m1=0;p.exitValue();
// break; }catch(java.lang.IllegalThreadStateException ex){m1=1;}Исключение генерится как раз если процесс "р" не завершен. Во "while" вместо "true" можно поставить условие "m1>0" оно неплохо земенит "break" выше по тексту.
Да, действительно, exitValue() генерит это исключение только при условии, если процесс не завершен. Так что такой способ проверки завершения процесса вполне подходит :)
Вот тут слегка другое предложение
http://inotroot.by.ru/programming/java/14chapt.htm
public abstract int waitFor() throws InterruptedException
Сколь угодно долго ожидает завершения процесса-потомка и возвращает значение, переданное им методу System.exit или его аналогу (ноль означает успешное завершение, все остальное — неудачу). Если процесс уже завершился, то просто возвращается значение.
Отправить комментарий