Difference between revisions of "Executing External Programs/ru"
m |
|||
Line 239: | Line 239: | ||
− | |||
[[Category:Russian (unfinished translation)]] | [[Category:Russian (unfinished translation)]] | ||
− | |||
− | |||
− | |||
− | |||
− |
Revision as of 10:06, 19 June 2015
│
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.
Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.
Усовершенствованный пример
Это все замечательно, но как я могу получить вывод программы, которую я запустил?
Хорошо, пусть наш пример немного увеличится и теперь будет выглядеть так:
// Это демо-программа, показывающая, как запускать
// внешнюю программу и читать ее вывод
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, для работы и общения посредством обмена данными с другой программой.