В прошлой статье, посвященной консольным приложениям, я рассматривал случаи, когда работа с консольными приложениями не только необходима, но и неизбежна. Это случай работы с математическими моделями, которые разрабатываются математиками на Фортране. Нет, конечно, для любой задачи может быть, как минимум, два решения. Например, можно открыть исходник реализации модели и заново его переписать на Delphi. Но зачем? Можно просто научиться взаимодействовать с готовых консольным приложением и решить задачу намного проще. И именно этим мы сегодня и займемся — работой с консольными приложениями в Delphi, а именно — научимся работать с потоком ввода/вывода.
Когда под «взаимодействием» с консольным приложением подразумевается обычный его (консольного приложения) запуск, то, на мой взгляд, такой вопрос можно решить одним простеньким запросом в Гугл и посмотреть, например, как работать с ShellExecute или CreateProcess.
Совсем другое дело, когда нам необходимо не только запустить exe-файл, но и, по мере необходимость, передавать консольному приложению какие-либо значения, считывать вывод и определять, когда приложение завершает свою работу и так далее. При этом не плохо было бы работать с консольным приложением в отдельном потоке. Здесь уже не обойтись без серьезного изучения вопроса по работе с потоком ввода/вывода.
Тема, в принципе, столь же стара, как и Delphi, а то и ещё старее, поэтому я не буду долго копаться в теории, расписывать процесс взаимодействия по шагам и так далее (всё это прекрасно изучено и рассказано), а перейду сразу к рассмотрению готового решения для Delphi.
Russell Libby
Russel Libby — это набор из трех компонентов: TPipeClient, TPipeServer и TPipeConsole. С некоторыми доработками эти компоненты можно сказать здесь или моей страницы «Исходники». Компоненты появились в, теперь уже, далеком 2003 году, однако на сегодняшний день сайт разработчика не доступен, а разработку в своё время продолжал Francois Piette. Компоненты очень хорошие, просты в работе и имеют минимум свойств, поэтому, разобраться с ними достаточно просто и легко.
После установки в палитре компонентов Delphi появится новая вкладка с тремя перечисленными выше компонентами:
Сегодня я буду рассматривать компонент TPipeConsole.
Консольное приложение на Delphi
Прежде, чем приступать к взаимодействию со сложными консольными приложениями, напишем простенькое самостоятельно. Я не стал долго мудрить и написал консольное приложение, которое выводит таблицу умножения для любого числа в диапазоне от 0 до 9:
program test; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.StrUtils; var Number: integer; i:integer; begin try WriteLn('Введите любое целое число от 1 до 9: '); Flush(Output); Read(Number); if (Number<1) or (Number>9) then raise Exception.Create('Неверное число'); WriteLn('Таблица умножения для ' + IntToStr(Number)); for I := 1 to 9 do Writeln(Format('%d х %d = %d',[Number, I, Number*1])); Readln; except on e: Exception do begin WriteLn(ErrOutput, e.Message); ExitCode := 1; end; end; end.
Если пользователь вводит неверное число, например, 0 или 10, приложение сообщит об этом и завершит работу с кодом ошибки 1. Наша задача состоит в том, чтобы из VCL-приложения запустить консольное, ввести число и прочитать результат работы консольного приложения, включая и возможную ошибку ввода.
VCL Application
Тестовое VCL-приложение выглядит следующим образом:
На форме расположены:
- TEdit и TUpDown для задания числа
- TMemo для вывода результатов работы консольного приложения
- TButton для запуска консольного приложения
- TPipeConsole для взаимодействия с консольным приложением.
Использование TPipeConsole
Договоримся, что exe-файл консольного приложения будет располагаться в той же директории, что и exe-файл vcl-приложения.
Первое, что мы сделаем — это запустим консольное приложение и прочитаем поток ввода/вывода. Запустить консольное приложение можно следующим образом:
procedure TForm8.btnRunClick(Sender: TObject); begin PipeConsole1.Start('test.exe',EmptyStr); end;
Метод Start имеет следующее описание:
function Start(Application, CommandLine : string) : Boolean;
Application — путь к exe-файлу консольного приложения (в нашем случае это строка «test.exe»), а в CommandLine можно использовать, если консольному приложению при запуске необходимо передавать какие-либо параметры.
После запуска vcl-приложения и нажатия на кнопку «Запуск» ничего не произойдет. Точнее даже так: Вы не увидите, что происходит. Для того, чтобы окно консольного приложения стало видимым, необходимо изменить свойство Visible компонента TPipeConsole, сделав его равным True:
Теперь, если снова запустить приложение и нажать кнопку «Запуск», можно увидеть примерно следующее:
Консольное приложение запускается. Теперь необходимо прочитать поток ввода/вывода. Для этого, у компонента TPipeConsole определено событие:
property OnOutput : TOnConsole read FOnOutput write FOnOutput; type TOnConsole = procedure(Sender : TObject; Stream : TStream) of object;
Воспользуемся этим событием. Пишем следующий обработчик события OnOutput:
procedure TForm8.PipeConsole1Output(Sender: TObject; Stream: TStream); begin Memo1.Lines.LoadFromStream(Stream); end;
Снова запускаем vcl-приложение и смотрим на результат:
Как видите, поток ввода/вывода был прочитан вплоть до момента, когда консольное приложение ожидает ввода числа.
Прежде, чем начнем передавать консольному приложению какие-либо значения, определим также обработчик события OnError для вывода сообщений об ошибках. Событие OnError имеет тот же тип, что и OnOutput. Можете определить его как угодно, например, так
procedure TForm8.PipeConsole1Output(Sender: TObject; Stream: TStream); begin Memo1.Lines.LoadFromStream(Stream); end;
Событие OnStop вызывается при остановке консольного приложение и имеет следующее описание:
property OnStop : TOnConsoleStop read FOnStop write FOnStop; type TOnConsoleStop = procedure(Sender : TObject; ExitValue : LongWord) of object;
ExitValue— код с которым завершается приложение.
Для события OnStop напишем такой обработчик:
procedure TForm8.PipeConsole1Stop(Sender: TObject; ExitValue: Cardinal); begin Memo1.Lines.Add('Приложение завершило работу с кодом: '+ExitValue.ToString); end;
Теперь, определив все необходимые события, научимся передавать консольному приложению какие-либо значения. Для этого, у TPipeConsole имеется специальный метод:
procedure Write(const Buffer; Length : Integer);
Чтобы передать число из TEdit vcl-приложения в поток ввода/вывода консольного приложения допишем обработчик события OnOutput следующим образом:
procedure TForm8.PipeConsole1Output(Sender: TObject; Stream: TStream); var bytes: TBytes; begin Memo1.Lines.LoadFromStream(Stream); if Pos('Таблица', Memo1.Lines.Text)=0 then begin bytes := TEncoding.GetEncoding('Windows-1251').GetBytes(edNumber.Text + #13#10); PipeConsole1.Write(bytes[0], Length(bytes)); end; end;
Теперь, если в Memo будет содержаться только запрос на ввод числа, то оно передастся в консольное приложение и далее приложение продолжит свою работу или же, если число будет не верным, то в Memo появится текст и код ошибки. Проверим так ли это. Во-первых, передадим в приложение значение «0» — это должно привести к выводу сообщения об ошибке:
Как видите, всё в порядке — передача консольному приложению числа ноль привело к ошибке. Теперь передадим любое другое верное значение. Снова запускаем vcl-приложение, устанавливаем в
число и жмем кнопку «Запуск». результат:
Как видите, консольное приложение корректно получило значение «9» и вывела необходимые значения, а TPipeConsole эти значения перехватил и передал нам.
В целом, компонент TPipeConsole оказался достаточно удобным и простым в работе. И, в настоящее время, именно этот компонент я использую при разработке GUI для большой модели Aermod, работа которой разнесена сразу по 5 различным консольным приложениям.
Спасибо посмотрим. К слову я использовал из JEDI компонет TJvCreateProcess с похожим функционалом (правда с консоли не вводил)
Я с JEDI давно не работал, поэтому даже и не вспомнил про них. Есть ещё DOSCommand, но после TPipeConsole он показался мне каким-то слабоватым в плане работы.