Difference between revisions of "Executing External Programs/ru"

From Free Pascal wiki
Jump to navigationJump to search
Line 39: Line 39:
 
|Cross-Platform '''Requires FPC 2.6.2+'''
 
|Cross-Platform '''Requires FPC 2.6.2+'''
 
|Yes
 
|Yes
|Covers common TProcess usage
+
|Покрывает стандартное использование TProcess
 
|-
 
|-
 
|[[Executing External Programs#LCLIntf Alternatives|OpenDocument]]
 
|[[Executing External Programs#LCLIntf Alternatives|OpenDocument]]

Revision as of 21:14, 25 January 2016

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) Nederlands (nl) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Введение: сравнение

В библиотеках RTL, FCL, LCL есть разные способы выполнить внешнюю программу.

Метод Библиотека Платформы Одной строкой? Особенности
ExecuteProcess RTL Cross-Platform Да Очень ограничен, синхронен
ShellExecute WinAPI Только MS Windows Да Много. Может запускать программы с правами администратора
fpsystem, fpexecve Unix Unix only
TProcess FCL Cross-Platform Нет Все
RunCommand FCL Cross-Platform Requires FPC 2.6.2+ Yes Покрывает стандартное использование TProcess
OpenDocument LCL Cross-Platform Yes Только открывает документ. Документ будет открыт стандартной программой

Если вы использовали ShellExecute и/или WinExec в Delphi, то вы можете начать использовать TProcess как альтернативу в FPC/Lazarus (это верно и в случае использования Lazarus на Linux, потому что TProcess является кроссплатформенным компонентом).

Примечание: FPC/Lazarus поддерживает ShellExecute и WinExec, но только в среде Win32. Если вы пишете кросс-платформенную программу, то лучшим путем будет использование TProcess!

(Process.)RunCommand

В FPC 2.6.2, некоторые вспомогательные функции для TProcess были добавлены в модуль process основанный на обертке использованной в fpcup. Эти функции могут быть для базового и среднего уровня использования и могот захватить вывод как одной строки, так и огромного количества выводимой информации.

Простой пример:

uses Process;
...
var s : ansistring;
...
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
   writeln(s);

Перегруженный вариант функции может возвращать код выхода программы. RunCommandInDir запускает программу в заданной папке.

function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;

SysUtils.ExecuteProcess

Простейший путь, если вам не нужно общение с процессом - это просто использовать вызов SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']);

TProcess

Вы можете использовать TProcess для запуска внешних программ. Самыми полезными вещами при этом будут:

  • Платформонезависимость
  • Способность читать из stdout и писать в stdin.

Примечание: TProcess не оболочка! И не терминал! Вы не можете напрямую исполнять скрипты или перенаправлять вывод используя такие операторы, как "|", ">", "<", "&" и т.д. Но возможно получить те же самые результаты используя TProcess, далее будут приведены некоторые примеры..

Важно: Вы должны определять полный путь к исполняемому файлу. Например '/bin/cp' вместо 'cp'. Если программа находится где-либо в переменной PATH, то вы можете использовать функцию FindDefaultExecutablePath из модуля LCL FileUtil.

Простой пример

 // Демо-программа, показывающая, как можно запустить
 // внешнюю программу
 program launchprogram;
 
 // Подключаем модули с требуемыми
 // нам процедурами и функциями.
 uses 
   Classes, SysUtils, Process;
 
 // Опишем переменную "AProcess" 
 // типа "TProcess"
 var 
   AProcess: TProcess;
 
 // Здесь наша программа начинается
 begin
   // Создаем объект  TProcess и
   // присваиваем его переменной AProcess.
   AProcess := TProcess.Create(nil);
 
   // Сообщим AProcess сомандную строку для запуска
   // Let's use the FreePascal compiler
   AProcess.CommandLine := 'ppc386 -h';
 
   // Необходимо описать опции программы для запуска
   // Эта опция не позволит нашей программе выполнятся до тех пор, пока 
   // запущенная программа не закончится
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   // Теперь AProcess знает командную строку
   // и мы ее запускаем
   AProcess.Execute;
 
   // Пока ppc386 не прекратит работу, мы досюда не дойдем
   AProcess.Free;   
 end.

Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.

Усовершенствованный пример

Это все замечательно, но как я могу получить вывод программы, которую я запустил?

Хорошо, пусть наш пример немного увеличится и теперь будет выглядеть так:

 // Это демо-программа, показывающая, как запускать
 // внешнюю программу и читать ее вывод
 program launchprogram;
 
 // Подключаем модули
 uses 
   Classes, SysUtils, Process;
 
 // Опысываем переменную "AProcess"
 // И добавляем список строк TStringList для сбора данных
 // из вывода программы
 var 
   AProcess: TProcess;
   AStringList: TStringList;
 
 // Начинаем нашу программу
 begin
   // Создаем объект TProcess 
   AProcess := TProcess.Create(nil);
 
   // Создаем объект TStringList
   AStringList := TStringList.Create;
 
   // Зададим командную строку
   AProcess.CommandLine := 'ppc386 -h';
 
   // Установим опции программы. Первая из них не позволит нашей программе
   // выполнятся до тех пор, пока не закончит выполнение запущенная программа
   // Также добавим опцию, которая говорит, что мы хотим прочитать 
   // вывод запущенной программы
   AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
   // Теперь запускаем программу
   AProcess.Execute;
   
   // Пока запущенная программа не закончится, досюда мы не дойдем
 
   // А теперь прочитаем вывод в список строк TStringList.
   AStringList.LoadFromStream(AProcess.Output);
   
   // Сохраним вывод в файл
   AStringList.SaveToFile('output.txt');
 
   // После сохранения файла мы можем уничтожить
   // TStringList и TProcess.
   AStringList.Free;
   AProcess.Free;   
 end.

Чтение больших объемов вывода

В предыдущем примере мы ждали завершения запущенной программы. После этого мы считывали все, что программа записала в выходной поток. Но ведь может оказаться и так, что программа выведет много данных, канал заполнится и вывод остановится, при этом запустившая программа ждет завершения запущенной программы, которая в свою очередь не может завершить работу, пока не выведет все данные. Возникает коллизия, dead-lock.

Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList.

 program procoutlarge;
 {
     Copyright (c) 2004 by Marc Weustink
 
     Этот пример был создан в надежде быть вам полезным,
     но без каких-либо подразумеваемых или явных  гарантий; 
 }
 
 uses
   Classes, Process, SysUtils;
 
 const
   READ_BYTES = 2048;
   
 var
   S: TStringList;
   M: TMemoryStream;
   P: TProcess;
   n: LongInt;
   BytesRead: LongInt;
 
 begin
   // Мы не можем использовать poWaitOnExit не зная размер вывода
   // В Linux размер выходного канала равен 2 kB. Если размер выводных
   // данных больше, то мы должны считывать данные. 
   // Пока мы ждем, чтение невозможно. Соответственно, мы получаем 
   // коллизию.
   //
   // Используем для буфера временный  поток Memorystream 
   
   M := TMemoryStream.Create;
   BytesRead := 0;
 
   P := TProcess.Create(nil);
   P.CommandLine := 'ppc386 -va bogus.pp';
   P.Options := [poUsePipes];
   WriteLn('-- executing --');
   P.Execute;
   while P.Running do
   begin          
     // Убедимся, что нам хватит места
     M.SetSize(BytesRead + READ_BYTES);
     
     // попытаемся прочитать данные
     n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
     if n > 0 
     then begin
       Inc(BytesRead, n);
       Write('.')
     end
     else begin     
       // нет данных, ждем 100 ms
       Sleep(100); 
     end;
   end;
   // читаем последний блок
   repeat
     // убедимся, что хватает места
     M.SetSize(BytesRead + READ_BYTES);
     // пытаемся прочитать
     n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
     if n > 0 
     then begin
       Inc(BytesRead, n);
       Write('.');
     end;
   until n <= 0;
   if BytesRead > 0 then WriteLn;
   M.SetSize(BytesRead); 
   WriteLn('-- executed --');
   
   S := TStringList.Create;
   S.LoadFromStream(M);
   WriteLn('-- linecount = ', S.Count, ' --');
   for n := 0 to S.Count - 1 do
   begin
     WriteLn('| ', S[n]);
   end;
   WriteLn('-- end --');
   S.Free;
   P.Free;
   M.Free;
 end.

Использование ввода и вывода TProcess

Смотри демо пример на Lazarus-CCR SVN.

Некоторые подсказки при использовании TProcess

Если вы создаете кроссплатформенную программу, вы можете изменять командную строку применительно к каждой ОС использую директивы "{$IFDEF}" и "{$ENDIF}".

Например:

 {...}
   AProcess:TProcess.Create(nil)
   {$IFDEF WIN32}
   AProcess.CommandLine := 'calc.exe'; //Windows калькулятор
   {$ENDIF}
   {$IFDEF LINUX}
   AProcess.CommandLine := 'kcalc'; //KDE калькулятор
   {$ENDIF}
   AProcess.Execute; //как альтернативу, вы можете использовать AProcess.Active:=True
 {...}

Пример "общения" с процессом aspell

Внутри исходного кода pasdoc расположены два модуля, выполняющие проверку орфографии, "общаясь" между собой посредством передачи управления процессу в котором они выполняются:

  • PasDoc_ProcessLineTalk.pas - Данный модуль реализует класс TProcessLineTalk являющегося потомком TProcess. Он может быть использован для обмена данными с любым процессом на его основе.
  • PasDoc_Aspell.pas - Реализует класс TAspellProcess, который выполняет проверку орфографии и наследуется от TProcessLineTalk, для выполнения aspell и обращения к запустившему его процессу.

Оба модуля являются независимыми от остальных исходных кодов(модулей) pasdoc, поэтому они могут служить в качестве реальных примеров использования TProcess, для работы и общения посредством обмена данными с другой программой.