Difference between revisions of "Executing External Programs/ru"
(added info from english page) |
|||
Line 83: | Line 83: | ||
При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то белее полезный кроссплатформенный способ это '''TProcess''', или если ваша целевая платформа только Windows, то используйте '''ShellExecute'''. | При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то белее полезный кроссплатформенный способ это '''TProcess''', или если ваша целевая платформа только Windows, то используйте '''ShellExecute'''. | ||
− | == MS Windows : CreateProcess, ShellExecute | + | == MS Windows : CreateProcess, ShellExecute и WinExec == |
− | {{Note| | + | {{Note|FPC/Lazarus поддерживают '''CreateProcess''', '''ShellExecute''' и/или '''WinExec''', только на Win32/64. Если ваша программа кроссплатформенная, используйте '''RunCommand''' или '''TProcess'''.}} |
− | {{Note|WinExec | + | {{Note|WinExec использует 16-битные вызовы и давно устарел. Новые версии FPC при его использовании генерируюn предупреждение.}} |
− | '''ShellExecute''' | + | '''ShellExecute''' стандартная функция MS Windows (ShellApi.h) с хорошей [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx документацией на MSDN] (читайте документацию, если вам кажется, что функция ненадежна). |
<syntaxhighlight> | <syntaxhighlight> | ||
uses ..., ShellApi; | uses ..., ShellApi; | ||
− | // | + | // Обычный текст (возвращаемые ошибки игнорируются) : |
− | if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then; | + | if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) = 0 then; |
− | // | + | // Выполняем Batch файл: |
− | if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then; | + | if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) = 0 then; |
− | // | + | // Открываем командную строку в заданной папке: |
− | if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then; | + | if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) = 0 then; |
− | // | + | // Открываем веб-страницу стандартным браузером с помощью'start' (через скрытую командную строку) : |
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then; | if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then; | ||
− | // | + | // или полезную команду: |
procedure RunShellExecute(const prog,params:string); | procedure RunShellExecute(const prog,params:string); | ||
begin | begin | ||
− | // ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' | + | // ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' не всегда требуется |
// path+prog, params, working folder, | // path+prog, params, working folder, | ||
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ... | // 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ... | ||
− | if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; // | + | if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //успех |
− | // | + | // если возвращается число в диапазоне 0..32, то значит ошибка |
end; | end; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 148: | Line 148: | ||
end; | end; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
== TProcess == | == TProcess == |
Revision as of 17:31, 26 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 | кроссплатформенный | Да | Очень ограничен, синхронен |
ShellExecute | WinAPI | Только MS Windows | Да | Много. Может запускать программы с правами администратора |
fpsystem, fpexecve | Unix | Unix only | ||
TProcess | FCL | кроссплатформенный | Нет | Все |
RunCommand | FCL | кроссплатформенный Требуется FPC 2.6.2+ | Yes | Покрывает стандартное использование TProcess |
OpenDocument | LCL | кроссплатформенный | Yes | Только открывает документ. Документ будет открыт стандартной программой |
Если вы использовали ShellExecute и/или WinExec в Delphi, то вы можете начать использовать TProcess как альтернативу в FPC/Lazarus (это верно и в случае использования Lazarus на Linux, потому что TProcess является кроссплатформенным компонентом).
Примечание: FPC/Lazarus поддерживает ShellExecute и WinExec, но только в среде Win32. Если вы пишете кросс-платформенную программу, то лучшим путем будет использование TProcess!
(Process.)RunCommand
- RunCommand документация
- RunCommandInDir докуметация
В 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, или если ваша целевая платформа только Windows, то используйте ShellExecute.
MS Windows : CreateProcess, ShellExecute и WinExec
ShellExecute стандартная функция MS Windows (ShellApi.h) с хорошей документацией на MSDN (читайте документацию, если вам кажется, что функция ненадежна).
uses ..., ShellApi;
// Обычный текст (возвращаемые ошибки игнорируются) :
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) = 0 then;
// Выполняем Batch файл:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) = 0 then;
// Открываем командную строку в заданной папке:
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) = 0 then;
// Открываем веб-страницу стандартным браузером с помощью'start' (через скрытую командную строку) :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
// или полезную команду:
procedure RunShellExecute(const prog,params:string);
begin
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' не всегда требуется
// path+prog, params, working folder,
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //успех
// если возвращается число в диапазоне 0..32, то значит ошибка
end;
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.
If in Delphi you used ShellExecute for documents like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).
Using ShellExecuteEx for elevation/administrator permissions
If you need to execute external program with administrator/elevated privileges, you can use the runas method with the alternative ShellExecuteEx function:
uses ShellApi, ...;
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
var
sei: TShellExecuteInfoA;
begin
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := Handle;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := 'runas';
sei.lpFile := PAnsiChar(Path);
sei.lpParameters := PAnsiChar(Params);
sei.nShow := SW_SHOWNORMAL;
Result := ShellExecuteExA(@sei);
end;
procedure TFormMain.RunAddOrRemoveApplication;
begin
// Example that uses elevated rundll to open the Control Panel to Programs and features
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;
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, для работы и общения посредством обмена данными с другой программой.