Executing External Programs/ru

From Lazarus wiki
Revision as of 18:20, 23 March 2012 by Eastorwest (talk | contribs) (Простой пример)

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)

Введение

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

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

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

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.

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

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

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

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

<delphi>

// Это демо-программа, показывающая, как запускать
// внешнюю программу и читать ее вывод
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.

</delphi>

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

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

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

<delphi> 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.</delphi>

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

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

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

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

Например: <delphi> {...}

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

Example of "talking" with aspell process

Inside pasdoc source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:

  • PasDoc_ProcessLineTalk.pas unit implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.
  • PasDoc_Aspell.pas units implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.

Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.