Difference between revisions of "Executing External Programs/pl"
(→SysUtils.ExecuteProcess: tłumaczenie na j. polski) |
m (→Czytanie dużych ilości danych wyjściowych: Poprawki pisowni) |
||
(29 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
{{Executing External Programs}} | {{Executing External Programs}} | ||
− | + | <div style="font-size:1.88em;margin:0.75em 0;">Wykonywanie programów zewnętrznych</div> | |
== Przegląd: Porównanie == | == Przegląd: Porównanie == | ||
Istnieją różne sposoby dostępne w bibliotekach RTL, FCL i LCL dotyczące wykonywania zewnętrznego polecenia/procesu/programu. | Istnieją różne sposoby dostępne w bibliotekach RTL, FCL i LCL dotyczące wykonywania zewnętrznego polecenia/procesu/programu. | ||
Line 10: | Line 10: | ||
!Cechy | !Cechy | ||
|- | |- | ||
− | |[[Executing External Programs#SysUtils.ExecuteProcess|ExecuteProcess]] | + | |[[Executing External Programs/pl#SysUtils.ExecuteProcess|ExecuteProcess]] |
|RTL | |RTL | ||
|Wieloplatformowy | |Wieloplatformowy | ||
Line 16: | Line 16: | ||
|Bardzo ograniczone, synchroniczne. | |Bardzo ograniczone, synchroniczne. | ||
|- | |- | ||
− | |[[Executing External Programs# | + | |[[Executing External Programs/pl#MS_Windows:_CreateProcess,_ShellExecute_i_WinExec|ShellExecute]] |
|WinAPI | |WinAPI | ||
|Tylko MS Windows | |Tylko MS Windows | ||
Line 22: | Line 22: | ||
|Wiele. Może uruchamiać programy z podwyższonymi uprawnieniami administratora. | |Wiele. Może uruchamiać programy z podwyższonymi uprawnieniami administratora. | ||
|- | |- | ||
− | |[[Executing External Programs# | + | |[[Executing External Programs/pl#Unix_fpsystem,_fpexecve_i_shell |fpsystem, fpexecve]] |
|Unix | |Unix | ||
|Tylko Unix | |Tylko Unix | ||
Line 28: | Line 28: | ||
| | | | ||
|- | |- | ||
− | |[[Executing External Programs#TProcess|TProcess]] | + | |[[Executing External Programs/pl#TProcess|TProcess]] |
|FCL | |FCL | ||
|Wieloplatformowy | |Wieloplatformowy | ||
Line 34: | Line 34: | ||
|Pełny. | |Pełny. | ||
|- | |- | ||
− | |[[Executing External Programs#( | + | |[[Executing External Programs/pl#(Proces.)RunCommand|RunCommand]] |
|FCL | |FCL | ||
|Wieloplatformowy '''Wymaga FPC 2.6.2+''' | |Wieloplatformowy '''Wymaga FPC 2.6.2+''' | ||
Line 40: | Line 40: | ||
|Obejmuje typowe użycie TProcess. | |Obejmuje typowe użycie TProcess. | ||
|- | |- | ||
− | |[[Executing External Programs# | + | |[[Executing External Programs/pl#Alternatywy_LCLIntf|OpenDocument]] |
|LCL | |LCL | ||
|Wieloplatformowy | |Wieloplatformowy | ||
Line 75: | Line 75: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
Zauważ jednak, że nie wszystkie „wbudowane” polecenia powłoki (np. aliasy) działają, ponieważ aliasy domyślnie nie są rozwijane w powłokach nieinteraktywnych, a .bashrc nie jest odczytywany przez powłoki nieinteraktywne, chyba że ustawisz zmienną środowiskową BASH_ENV. tak więc poniższy przykąłd nie daje żadnych danych wyjściowych: | Zauważ jednak, że nie wszystkie „wbudowane” polecenia powłoki (np. aliasy) działają, ponieważ aliasy domyślnie nie są rozwijane w powłokach nieinteraktywnych, a .bashrc nie jest odczytywany przez powłoki nieinteraktywne, chyba że ustawisz zmienną środowiskową BASH_ENV. tak więc poniższy przykąłd nie daje żadnych danych wyjściowych: | ||
Line 159: | Line 158: | ||
p.started:=now; | p.started:=now; | ||
try | try | ||
− | // główna pętla wariantów runcommand(), pierwotnie oparta na scenariuszu | + | // główna pętla wariantów runcommand(), pierwotnie oparta na scenariuszu "dużego wyjścia" na wiki, ale stale rozwijana przez 5 lat. |
result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0; | result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0; | ||
if p.timedout then | if p.timedout then | ||
Line 178: | Line 177: | ||
if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then | if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then | ||
begin | begin | ||
− | // nie udało się uruchomić lub upłynął limit czasu. np. movefile() do | + | // nie udało się uruchomić lub upłynął limit czasu. np. movefile() do "wadliwego" katalogu. |
end | end | ||
else | else | ||
begin | begin | ||
− | // ok, plik przeniesiony do | + | // ok, plik przeniesiony do "dobrego" katalogu. |
end; | end; | ||
end; | end; | ||
Line 200: | Line 199: | ||
* Odniesienie do [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess] | * Odniesienie do [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess] | ||
− | == MS Windows: CreateProcess, ShellExecute | + | == MS Windows: CreateProcess, ShellExecute i WinExec == |
− | {{Note| | + | {{Note|Chociaż FPC/Lazarus obsługuje '''CreateProcess''', '''ShellExecute''' i/lub '''WinExec''', ta obsługa jest dostępna tylko w Win32/64. Jeśli Twój program jest wieloplatformowy, rozważ użycie '''RunCommand''' lub '''TProcess'''.}} |
− | {{Note|WinExec | + | {{Note|WinExec to 16-bitowe wywołanie, które od lat jest przestarzałe w interfejsie API systemu Windows. W ostatnich wersjach FPC generuje ostrzeżenie.}} |
− | '''ShellExecute''' | + | '''ShellExecute''' to standardowa funkcja systemu MS Windows (ShellApi.h) z dobrą [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx dokumentacją na MSDN] (zwróć uwagę na ich komentarze na temat inicjalizacji COM, jeśli odkryjesz, że ta jest niewiarygodna). |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
uses ..., ShellApi; | uses ..., ShellApi; | ||
− | // | + | // Proste jednowierszowe (ignorowanie zwracanych błędów): |
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; | ||
− | // | + | // Wykonaj plik wsadowy: |
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; | ||
− | // | + | // Otwórz okno poleceń w danym folderze: |
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; | ||
− | // | + | // Otwórz adres URL strony internetowej w domyślnej przeglądarce za pomocą polecenia „start” (poprzez ukryte okno cmd): |
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; | ||
− | // | + | // lub przydatna procedura: |
procedure RunShellExecute(const prog,params:string); | procedure RunShellExecute(const prog,params:string); | ||
begin | begin | ||
− | // | + | // parametry: ( Uchwyt, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' nie zawsze jest potrzebny |
− | // | + | // path+prog, parametry, folder roboczy, |
− | // 0= | + | // okno command 0=ukryte / 1=SW_SHOWNORMAL / 3=maks / 7=min) // dla stałych SW_ : 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; //sukces |
− | // | + | // zwracane wartości 0..32 są błędami |
end; | end; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Istnieje również ShellExecuteExW jako wersja WideChar, a ShellExecuteExA to wersja AnsiChar. | |
− | + | Opcja fMask może również używać SEE_MASK_DOENVSUBST lub SEE_MASK_FLAG_NO_UI lub SEE_MASK_NOCLOSEPROCESS itp. | |
− | + | Jeśli w Delphi używałeś ShellExecute do '''dokumentów''' takich jak dokumenty Word lub adresy URL, spójrz na funkcje open* (OpenURL itp.) w module lclintf (zobacz sekcję [https://wiki.lazarus.freepascal.org/Executing_External_Programs/pl#Zobacz_także Zobacz także] na dole tej strony). | |
− | === | + | === Używanie ShellExecuteEx do podnoszenia uprawnień administratora === |
− | + | Jeśli potrzebujesz uruchomić zewnętrzny program z podwyższonymi uprawnieniami administratora, możesz użyć metody '''runas''' z alternatywną funkcją ShellExecuteEx: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 262: | Line 261: | ||
procedure TFormMain.RunAddOrRemoveApplication; | procedure TFormMain.RunAddOrRemoveApplication; | ||
begin | begin | ||
− | // | + | // Przykład, który używa podwyższonego uprawnienia dla rundll, aby otworzyć Panel sterowania do programów i funkcji |
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', ''); | RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', ''); | ||
end; | end; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == Unix fpsystem, fpexecve | + | == Unix fpsystem, fpexecve i shell == |
− | + | Te funkcje są zależne od platformy. | |
− | * [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem | + | * Odniesienie do [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem] |
− | * [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve | + | * Odniesienie do [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve] |
− | Linux.Shell/Unix.Shell | + | Linux.Shell/Unix.Shell był odpowiednikiem fpsystem w wersji 1.0.x ze słabo zdefiniowaną obsługą błędów i po dekadzie deprecjacji został ostatecznie usunięty. Prawie we wszystkich przypadkach może być zastąpiony przez fpsystem, który obsługuje więcej standardów POSIX, takich jak obsługa błędów. |
== TProcess == | == TProcess == | ||
− | + | Możesz użyć TProcess do uruchamiania zewnętrznych programów. Niektóre z korzyści płynących z używania TProcess to: | |
− | * | + | * Jest niezależny od platformy. |
− | * | + | * Potrafi czytać ze standardowego wyjścia i zapisywać na standardowe wejście. |
− | * | + | * Możliwe jest oczekiwanie na zakończenie polecenia lub pozwolenie na jego działanie podczas działania programu. |
− | + | Ważne informacje: | |
− | * TProcess | + | * TProcess nie jest terminalem/powłoką! Nie możesz bezpośrednio wykonywać skryptów lub przekierowywać wyjścia za pomocą operatorów takich jak „|”, „>”, „<”, „&” itp. Możliwe jest uzyskanie tych samych wyników za pomocą TProcess i za pomocą pascala - kilka przykładów poniżej. |
− | * | + | * Przypuszczalnie w systemie Linux/Unix: '''musisz''' podać pełną ścieżkę do pliku wykonywalnego. Na przykład „/bin/cp” zamiast „cp”. Jeśli program znajduje się w standardowej PATH, możesz użyć funkcji [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] z modułu [[doc:lcl/fileutil/index.html|FileUtil]] biblioteki LCL. |
− | * | + | * W systemie Windows, jeśli polecenie znajduje się w ścieżce, nie musisz określać pełnej ścieżki. |
− | * [[doc:fcl/process/tprocess.html|TProcess | + | * Odnośnik do [[doc:fcl/process/tprocess.html|TProcess]] |
− | === | + | === Najprostszy przykład === |
− | + | Wiele typowych przypadków zostało przygotowanych w funkcjach [[Executing_External_Programs/pl#.28Proces..29RunCommand|RunCommand]]. Zanim zaczniesz kopiować i wklejać poniższe przykłady, najpierw je sprawdź. | |
− | === | + | === Prosty przykład === |
− | + | Ten przykład ('''który nie powinien być używany w środowisku produkcyjnym, patrz [[Executing_External_Programs/pl#Czytanie_dużych_ilości_danych_wyjściowych|Czytanie dużych ilości danych wyjściowych]] lub, lepiej, [[Executing_External_Programs/pl#.28Proces..29RunCommand|RunCommand]]''') pokazuje tylko, jak uruchomić zewnętrzny program, nic więcej: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
− | // | + | // Jest to program demonstracyjny, który |
− | // | + | // pokazuje, jak uruchomić zewnętrzny program. |
program launchprogram; | program launchprogram; | ||
− | // | + | // Tutaj dołączamy moduły zawierające przydatne |
− | // | + | // funkcje i procedury, których będziemy potrzebować. |
uses | uses | ||
Classes, SysUtils, Process; | Classes, SysUtils, Process; | ||
− | // | + | // Definiuje zmienną "AProcess" jako zmienną typu "TProcess" |
− | |||
var | var | ||
AProcess: TProcess; | AProcess: TProcess; | ||
− | // | + | // Tu zaczyna działać nasz program |
begin | begin | ||
− | // | + | // Teraz utworzymy obiekt TProcess i przypiszemy go |
− | // | + | // do zmiennej AProcess. |
AProcess := TProcess.Create(nil); | AProcess := TProcess.Create(nil); | ||
− | // | + | // Powiedz nowemu AProcess, jakie jest polecenie do wykonania. |
− | // | + | // Użyjmy kompilatora Free Pascal (dokładniej wersji x64) |
− | AProcess.Executable:= ' | + | // Lecz jeśli używasz kompilatora 32-bit zmień ppcx64 na ppc386 |
+ | AProcess.Executable:= 'ppcx64'; | ||
− | // | + | // Przekaż opcję -h razem z ppcx64, więc faktycznie wykonywane |
+ | // jest polecenie 'ppcx64 -h': | ||
AProcess.Parameters.Add('-h'); | AProcess.Parameters.Add('-h'); | ||
− | // | + | // Zdefiniujemy opcję działającą, w czasie wykonywania programu. |
− | // | + | // Ta opcja sprawi, że nasz program nie będzie kontynuowany, |
− | // | + | // dopóki program zewnętrzy, który uruchomimy, nie przestanie działać. |
− | // | + | // /------------\ |
AProcess.Options := AProcess.Options + [poWaitOnExit]; | AProcess.Options := AProcess.Options + [poWaitOnExit]; | ||
− | // | + | // Teraz pozwól AProcess uruchomić program zewnętrzny |
AProcess.Execute; | AProcess.Execute; | ||
− | // | + | // Poniższy kod nie wykona się, dopóki ppcx64 nie przestanie działać. |
AProcess.Free; | AProcess.Free; | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | Otóż to! Właśnie nauczyłeś się uruchamiać zewnętrzny program z wnętrza własnego programu. | |
− | === | + | === Ulepszony przykład (ale jeszcze niepoprawny) === |
− | + | Wszystko fajnie, ale jak odczytać dane wyjściowe uruchomionego programu? | |
− | + | Cóż, rozszerzmy nieco nasz przykład, jak poniżej: | |
− | ''' | + | '''Ten przykład jest prosty, więc możesz się z niego uczyć. Jednak nie używaj tego przykładu w kodzie produkcyjnym, ale użyj kodu z [[Executing_External_Programs/pl#Czytanie_dużych_ilości_danych_wyjściowych|Czytanie dużych ilości danych wyjściowych]].''' |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
− | // | + | // To jest |
− | // | + | // WADLIWY |
− | // | + | // program demonstracyjny, który pokazuje, |
− | // | + | // jak uruchomić zewnętrzny program |
− | // | + | // i odczytać z jego dane wyjściowe. |
program launchprogram; | program launchprogram; | ||
− | // | + | // Tutaj dołączamy moduły zawierające przydatne |
− | // | + | // funkcje i procedury, których będziemy potrzebować. |
uses | uses | ||
Classes, SysUtils, Process; | Classes, SysUtils, Process; | ||
− | // | + | // Definiuje zmienną "AProcess" jako zmienną typu "TProcess" |
− | + | // Teraz także dodajemy TStringList do przechowywania danych | |
− | // | + | // odczytanych z wyjścia programu. |
− | // | ||
var | var | ||
AProcess: TProcess; | AProcess: TProcess; | ||
AStringList: TStringList; | AStringList: TStringList; | ||
− | // | + | // Tu zaczyna działać nasz program |
begin | begin | ||
− | // | + | // Teraz utworzymy obiekt TProcess i przypiszemy go |
− | // | + | // do zmiennej AProcess. |
AProcess := TProcess.Create(nil); | AProcess := TProcess.Create(nil); | ||
− | // | + | // Powiedz nowemu AProcess, jakie jest polecenie do wykonania. |
− | AProcess.Executable := '/usr/bin/ | + | AProcess.Executable := '/usr/bin/ppcx64'; |
AProcess.Parameters.Add('-h'); | AProcess.Parameters.Add('-h'); | ||
− | // | + | // Zdefiniujemy opcję działającą, w czasie wykonywania programu. |
− | // | + | // Ta opcja sprawi, że nasz program nie będzie kontynuowany, |
− | // | + | // dopóki program zewnętrzy, który uruchomimy, nie przestanie działać. |
− | // | + | // Dodatkowo teraz powiemy mu, że chcemy odczytać dane wyjściowe. |
− | // | + | // /------------|-----------\ |
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes]; | AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes]; | ||
− | // | + | // Teraz, gdy AProcess wie, jaki jest wiersz poleceń, można go uruchomić. |
AProcess.Execute; | AProcess.Execute; | ||
− | // | + | // Po zakończeniu AProcess zostanie wykonana reszta programu. |
− | // | + | // Teraz odczytaj wynik działania programu, za pomocą TStringList. |
AStringList := TStringList.Create; | AStringList := TStringList.Create; | ||
AStringList.LoadFromStream(AProcess.Output); | AStringList.LoadFromStream(AProcess.Output); | ||
− | // | + | // Zapisz dane wyjściowe do pliku i zwolnij obiekt AStringList. |
AStringList.SaveToFile('output.txt'); | AStringList.SaveToFile('output.txt'); | ||
AStringList.Free; | AStringList.Free; | ||
− | // | + | // Teraz, gdy dane wyjściowe z procesu są przetworzone, także można je zwolnić. |
AProcess.Free; | AProcess.Free; | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | === | + | === Czytanie dużych ilości danych wyjściowych === |
− | + | W poprzednim przykładzie czekaliśmy, aż program się zakończy. Następnie czytaliśmy, co program zapisał na swoim wyjściu. | |
− | + | Załóżmy, że program zapisuje dużo danych na wyjściu. Następnie potok wyjściowy zapełnia się i wywoływany program czeka, aż potok zostanie odczytany. | |
− | + | Ale program wywołujący nie czyta z niego, dopóki wywoływany program się nie zakończy. Następuje impas. | |
− | + | Poniższy przykład nie używa zatem poWaitOnExit, ale odczytuje dane wyjściowe, gdy program jest nadal uruchomiony. Dane wyjściowe są przechowywane w strumieniu pamięci, który można później wykorzystać do odczytania danych wyjściowych do TStringList. | |
− | + | ||
− | + | Jeśli chcesz odczytać dane wyjściowe z procesu zewnętrznego i nie możesz użyć RunCommand, jest to kod, który powinien być podstawą do użytku produkcyjnego. Jeśli używasz '''FPC 3.2.0+''', parametryzowalna forma tej pętli jest dostępna jako metoda '''RunCommandLoop''' w TProcess. Zdarzenie '''OnRunCommandEvent''' można podłączyć, aby dalej modyfikować zachowanie. | |
<syntaxhighlight lang="pascal">program LargeOutputDemo; | <syntaxhighlight lang="pascal">program LargeOutputDemo; | ||
Line 416: | Line 415: | ||
uses | uses | ||
− | Classes, SysUtils, Process; // Process | + | Classes, SysUtils, Process; // Process to moduł, który zawiera TProcess |
const | const | ||
− | BUF_SIZE = 2048; // | + | BUF_SIZE = 2048; // Rozmiar bufora do odczytu danych wyjściowych w porcjach |
var | var | ||
Line 428: | Line 427: | ||
begin | begin | ||
− | // | + | // Skonfiguruj proces; jako przykład używane jest rekurencyjne przeszukiwanie |
− | // | + | // katalogów, ponieważ zwykle skutkuje to dużą ilością danych. |
AProcess := TProcess.Create(nil); | AProcess := TProcess.Create(nil); | ||
− | // | + | // Polecenia dla Windows i *nix są różne, stąd dyrektywy $IFDEF |
{$IFDEF Windows} | {$IFDEF Windows} | ||
− | // | + | // W systemie Windows polecenie dir nie może być użyte bezpośrednio, ponieważ jest to |
− | // | + | // wbudowane polecenie powłoki. Dlatego potrzebny jest cmd.exe i dodatkowe parametry. |
AProcess.Executable := 'c:\windows\system32\cmd.exe'; | AProcess.Executable := 'c:\windows\system32\cmd.exe'; | ||
AProcess.Parameters.Add('/c'); | AProcess.Parameters.Add('/c'); | ||
Line 462: | Line 461: | ||
{$ENDIF Unix} | {$ENDIF Unix} | ||
− | // | + | // Aby można było przechwycić dane wyjściowe, należy użyć opcji procesu poUsePipes. |
− | // | + | // Nie można użyć opcji procesu poWaitOnExit, ponieważ zablokuje to program główny, |
− | // | + | // uniemożliwiając mu odczytanie danych wyjściowych procesu zewnętrznego. |
AProcess.Options := [poUsePipes]; | AProcess.Options := [poUsePipes]; | ||
− | // | + | // Uruchom proces (uruchom polecenie dir dla Windows lub ls dla Linux/Unix) |
AProcess.Execute; | AProcess.Execute; | ||
− | // | + | // Utwórz obiekt strumienia, w którym będą przechowywane wygenerowane dane wyjściowe. |
− | // | + | // Może to być również strumień plikowy do bezpośredniego zapisywania danych wyjściowych na dysku. |
OutputStream := TMemoryStream.Create; | OutputStream := TMemoryStream.Create; | ||
− | // | + | // Wszystkie wygenerowane dane wyjściowe z AProcess są odczytywane w pętli, |
+ | // dopóki nie będzie dostępnych więcej danych | ||
repeat | repeat | ||
− | // | + | // Pobierz nowe dane z procesu do maksymalnego rozmiaru buforu, który został przydzielony. |
− | // | + | // Zauważ, że wszystkie wywołania Read(...) będą blokowane z wyjątkiem ostatniego, które zwraca 0 (zero). |
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE); | BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE); | ||
− | // | + | // Bajty, które zostały odczytane, dodaj do strumienia w celu późniejszego wykorzystania |
OutputStream.Write(Buffer, BytesRead) | OutputStream.Write(Buffer, BytesRead) | ||
− | until BytesRead = 0; // | + | until BytesRead = 0; // Zakończ pętlę, jeśli nie ma więcej danych |
− | // | + | // Proces się zakończył, więc można go posprzątać |
AProcess.Free; | AProcess.Free; | ||
− | // | + | // Teraz, gdy wszystkie dane zostały odczytane, można z nich korzystać; |
+ | // na przykład po to, by zapisać je do pliku na dysku | ||
with TFileStream.Create('output.txt', fmCreate) do | with TFileStream.Create('output.txt', fmCreate) do | ||
begin | begin | ||
− | OutputStream.Position := 0; // | + | OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku |
CopyFrom(OutputStream, OutputStream.Size); | CopyFrom(OutputStream, OutputStream.Size); | ||
Free | Free | ||
end; | end; | ||
− | // | + | // Lub dane mogą być wyświetlane na ekranie |
with TStringList.Create do | with TStringList.Create do | ||
begin | begin | ||
− | OutputStream.Position := 0; // | + | OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku |
LoadFromStream(OutputStream); | LoadFromStream(OutputStream); | ||
writeln(Text); | writeln(Text); | ||
− | writeln('--- | + | writeln('--- Liczba linii = ', Count, '----'); |
Free | Free | ||
end; | end; | ||
− | // | + | // Sprzątanie |
OutputStream.Free; | OutputStream.Free; | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | Zauważ, że powyższe można również osiągnąć za pomocą RunCommand: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 516: | Line 517: | ||
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight> | RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight> | ||
− | === | + | === Korzystanie z wejścia i wyjścia TProcess === |
− | + | Zobacz przykładowe demo procesu w [https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/examples/process Lazarus-CCR SVN]. | |
− | |||
− | |||
− | |||
− | + | === Wskazówki dotyczące korzystania z TProcess === | |
+ | Podczas tworzenia programu wieloplatformowego nazwę pliku wykonywalnego specyficzną dla systemu operacyjnego można ustawić za pomocą dyrektyw „{$IFDEF}” i „{$ENDIF}”. | ||
+ | Przykład: | ||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
{...} | {...} | ||
Line 538: | Line 538: | ||
{...}</syntaxhighlight> | {...}</syntaxhighlight> | ||
− | === macOS | + | === macOS pokazuje pakiet aplikacji na pierwszym planie === |
− | + | Możesz uruchomić '''pakiet aplikacji''' przez TProcess, uruchamiając plik wykonywalny w pakiecie. Na przykład: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 546: | Line 546: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Spowoduje to uruchomienie ''Kalendarza'', ale okno będzie znajdować się za bieżącą aplikacją. | |
− | + | Aby uzyskać aplikację na pierwszym planie, możesz użyć narzędzia '''open''' z parametrem '''-n''': | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
AProcess.Executable:='/usr/bin/open'; | AProcess.Executable:='/usr/bin/open'; | ||
AProcess.Parameters.Add('-n'); | AProcess.Parameters.Add('-n'); | ||
− | AProcess.Parameters.Add('-a'); // | + | AProcess.Parameters.Add('-a'); // opcjonalny: określa aplikację do użycia; przeszukuje tylko katalogi aplikacji |
− | AProcess.Parameters.Add('-W'); // | + | AProcess.Parameters.Add('-W'); // opcjonalnie: open czeka, aż aplikacje, które otwiera (lub były już otwarte) zostaną zamknięte |
− | AProcess.Parameters.Add('Pages.app'); // | + | AProcess.Parameters.Add('Pages.app'); // dołączenie .app jest opcjonalne |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Jeśli Twoja aplikacja potrzebuje parametrów, możesz przekazać wraz z '''open''' parametr '''--args''', po którym wszystkie dalsze parametry są przekazywane do aplikacji: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 565: | Line 565: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Zobacz także: [[macOS_Open_Sesame|macOS polecenie open]]. | |
− | === | + | === Uruchom oddzielny program === |
− | + | Normalnie program uruchomiony przez twoją aplikację jest procesem potomnym i zostaje zabity, gdy twoja aplikacja zostanie zabita. Jeśli chcesz uruchomić samodzielny program, który nadal będzie działał, możesz użyć: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 582: | Line 582: | ||
Process.ShowWindow := swoShow; | Process.ShowWindow := swoShow; | ||
− | // | + | // Skopiuj domyślne zmienne środowiskowe, w tym zmienną DISPLAY, aby aplikacja GUI działała |
for I := 1 to GetEnvironmentVariableCount do | for I := 1 to GetEnvironmentVariableCount do | ||
Process.Environment.Add(GetEnvironmentString(I)); | Process.Environment.Add(GetEnvironmentString(I)); | ||
Line 594: | Line 594: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | === Przykład „rozmowy” z procesem aspell === |
− | + | Wewnątrz kodu źródłowego [https://github.com/pasdoc/pasdoc/wiki pasdoc] można znaleźć dwa moduły, które sprawdzają pisownię poprzez „rozmawianie” przez potoki z uruchomionym procesem aspell: | |
− | * [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas | + | * [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas Moduł PasDoc_ProcessLineTalk.pas] implementuje klasę TProcessLineTalk, potomka TProcess, która może być z łatwością używana do komunikacji z dowolnym procesem linia po linii. |
− | * [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas | + | * [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas Moduł PasDoc_Aspell.pas] implementuje klasę TAspellProcess, która sprawdza pisownię za pomocą bazowej instancji TProcessLineTalk do wykonania aspell i komunikacji z uruchomionym procesem aspell. |
− | + | Obydwa moduły są raczej niezależne od pozostałych źródeł pasdoc, więc mogą służyć jako rzeczywiste przykłady użycia TProcess do uruchamiania i komunikacji przez potoki z innym programem. | |
− | === | + | === Zastępowanie operatorów powłoki, takich jak „| < >” === |
− | + | Czasami chcesz uruchomić bardziej skomplikowane polecenie, które przesyła dane do innego polecenia lub do pliku. Coś podobnego do | |
− | |||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> | ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> | ||
− | + | lub | |
<syntaxhighlight lang="pascal">ShellExecute('dir > output.txt');</syntaxhighlight> | <syntaxhighlight lang="pascal">ShellExecute('dir > output.txt');</syntaxhighlight> | ||
− | + | Wykonanie tego za pomocą TProcess nie zadziała. tj: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
− | // | + | // to nie zadziała! |
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | ||
Process.Execute;</syntaxhighlight> | Process.Execute;</syntaxhighlight> | ||
− | ==== | + | ==== Dlaczego używanie specjalnych operatorów do przekierowywania wyjścia nie działa? ==== |
− | TProcess | + | TProcess nie jest środowiskiem powłoki, tylko procesem. To nie dwa procesy, to tylko jeden. Możliwe jest jednak przekierowanie danych wyjściowych dokładnie tak, jak chcesz. Zobacz [[Executing_External_Programs/pl#Jak_przekierować_wyjście_za_pomocą_TProcess|następną sekcję]]. |
− | |||
− | === | + | === Jak przekierować wyjście za pomocą TProcess === |
− | + | Możesz przekierować dane wyjściowe polecenia do innego polecenia, używając osobnej instancji TProcess dla '''każdego''' polecenia. | |
− | + | Oto przykład, który wyjaśnia, jak przekierować dane wyjściowe jednego procesu do drugiego. Aby przekierować wyjście procesu do pliku/strumienia, zobacz przykład [[Executing_External_Programs/pl#Czytanie_dużych_ilości_danych_wyjściowych|Czytanie dużych ilości danych wyjściowych]]. | |
− | + | Możesz nie tylko przekierować „normalne” wyjście (znane również jako stdout), ale możesz także przekierować wyjście błędu (stderr), jeśli określisz opcję poStderrToOutPut, jak to widać w opcjach drugiego procesu. | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 654: | Line 652: | ||
SecondProcess.Executable := 'grep'; | SecondProcess.Executable := 'grep'; | ||
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); | SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); | ||
− | // | + | // byłoby to to samo co "pwd | grep / -" |
FirstProcess.Execute; | FirstProcess.Execute; | ||
Line 663: | Line 661: | ||
if FirstProcess.Output.NumBytesAvailable > 0 then | if FirstProcess.Output.NumBytesAvailable > 0 then | ||
begin | begin | ||
− | // | + | // upewnij się, że nie czytamy więcej danych niż przydzieliliśmy w buforze |
− | |||
ReadSize := FirstProcess.Output.NumBytesAvailable; | ReadSize := FirstProcess.Output.NumBytesAvailable; | ||
if ReadSize > SizeOf(Buffer) then | if ReadSize > SizeOf(Buffer) then | ||
ReadSize := SizeOf(Buffer); | ReadSize := SizeOf(Buffer); | ||
− | // | + | // teraz wczytaj dane wyjściowe do bufora |
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | ||
− | // | + | // i zapisz bufor do drugiego procesu |
SecondProcess.Input.Write(Buffer[0], ReadCount); | SecondProcess.Input.Write(Buffer[0], ReadCount); | ||
− | // | + | // jeśli SecondProcess zapisuje dużo danych do swojego wyjścia, |
− | // | + | // powinniśmy odczytać te dane tutaj, aby zapobiec zakleszczeniu, |
− | // | + | // patrz poprzedni przykład "Czytanie dużych ilości danych wyjściowych" |
end; | end; | ||
end; | end; | ||
− | // | + | // Zamknij dane wejściowe w SecondProcess, |
− | // | + | // aby zakończyć przetwarzanie swoich danych |
SecondProcess.CloseInput; | SecondProcess.CloseInput; | ||
− | // | + | // i poczekaj, aż się zakończy, lecz uważaj, jakie polecenie uruchamiasz, |
− | // | + | // ponieważ może się ono nie zakończyć, gdy jego dane wejściowe są zamknięte, |
− | // | + | // to następująca linia może zapętlić się w nieskończoność |
while SecondProcess.Running do | while SecondProcess.Running do | ||
Sleep(1); | Sleep(1); | ||
− | // | + | // Gotowe! Reszta programu jest tylko po to, aby przykład był bardziej 'użyteczny' |
− | |||
− | // | + | // użyjemy ponownie Buffer, aby wyprowadzić wyjście SecondProcess do *tego* programu stdout |
− | + | WriteLn('Rozpoczęcie wyjścia grep:'); | |
− | WriteLn(' | ||
ReadSize := SecondProcess.Output.NumBytesAvailable; | ReadSize := SecondProcess.Output.NumBytesAvailable; | ||
if ReadSize > SizeOf(Buffer) then | if ReadSize > SizeOf(Buffer) then | ||
Line 702: | Line 697: | ||
end | end | ||
else | else | ||
− | WriteLn('grep | + | WriteLn('grep nie znalazł tego, czego szukaliśmy. ', SecondProcess.ExitStatus); |
− | WriteLn(' | + | WriteLn('Zakończenie wyjścia grep:'); |
− | // | + | // zwolnij obiekty naszych procesów |
FirstProcess.Free; | FirstProcess.Free; | ||
SecondProcess.Free; | SecondProcess.Free; | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | W ten sposób możesz przekierować dane wyjściowe z jednego programu do drugiego. | |
− | ==== | + | ==== Uwagi ==== |
− | + | Ten przykład może wydawać się przesadny, ponieważ możliwe jest uruchamianie „skomplikowanych” poleceń przy użyciu powłoki przy pomocy TProcess, takich jak: | |
<syntaxhighlight lang="pascal">Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> | <syntaxhighlight lang="pascal">Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> | ||
− | + | Ale nasz przykład jest bardziej wieloplatformowy, ponieważ nie wymaga modyfikacji, aby działać w systemie Windows lub Linux itp. Polecenie „sh” może, ale nie musi istnieć na twojej platformie i jest ogólnie dostępny tylko na platformach *nix. W naszym przykładzie mamy również większą elastyczność, ponieważ możesz czytać i zapisywać z/do wejścia, wyjścia i stderr każdego procesu z osobna, co może być bardzo korzystne dla twojego projektu. | |
− | === | + | ===Przekierowywanie wejścia i wyjścia oraz działanie z rootem=== |
− | + | Częstym problemem w systemach Unix (FreeBSD, macOS) i Linux jest to, że chcesz uruchomić jakiś program na koncie root (lub, bardziej ogólnie, na innym koncie użytkownika). Przykładem może być uruchomienie polecenia ''ping''. | |
− | + | Jeśli możesz użyć do tego sudo, możesz dostosować następujący przykład zaadaptowany z postu opublikowanego przez andymana na forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). Ten przykład uruchamia polecenie <code>ls</code> w katalogu <code>/root</code>, ale oczywiście można go dostosować. | |
− | + | '''Lepszym sposobem''' na to jest użycie pakietu policykit, który powinien być dostępny we wszystkich najnowszych Linuksach. [http://lazarus.freepascal.org/index.php/topic,14479.0.html Zobacz wątek na forum, aby uzyskać szczegółowe informacje.] | |
− | + | Duże części tego kodu są podobne do wcześniejszego przykładu, ale pokazuje również, jak przekierować stdout i stderr procesu wywoływanego oddzielnie na stdout i stderr naszego własnego kodu. | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
program rootls; | program rootls; | ||
− | { | + | { Demonstracja użycia TProcess, przekierowania stdout/stderr do naszego stdout/stderr, |
− | + | wywoływania sudo w systemach FreeBSD/Linux/macOS i dostarczania danych wejściowych na stdin} | |
{$mode objfpc}{$H+} | {$mode objfpc}{$H+} | ||
Line 749: | Line 744: | ||
WriteLn('Please enter the sudo password:'); | WriteLn('Please enter the sudo password:'); | ||
Readln(SudoPassword); | Readln(SudoPassword); | ||
− | ExitCode := -1; // | + | ExitCode := -1; //Zacznij od porażki, zobaczmy później, czy to zadziała |
− | Proc := TProcess.Create(nil); // | + | Proc := TProcess.Create(nil); //Utwórz nowy proces |
try | try | ||
− | Proc.Options := [poUsePipes, poStderrToOutPut]; // | + | Proc.Options := [poUsePipes, poStderrToOutPut]; //Użyj potoków, aby przekierować stdin,stdout,stderr |
− | Proc.CommandLine := 'sudo -S ls /root'; // | + | Proc.CommandLine := 'sudo -S ls /root'; //Uruchom ls /root jako root za pomocą sudo |
− | // -S | + | // -S powoduje, że sudo odczytuje hasło z stdin. |
− | Proc.Execute; // | + | Proc.Execute; //Uruchom to. sudo prawdopodobnie poprosi teraz o hasło |
− | // | + | // wpisz hasło do stdin programu sudo: |
SudoPassword := SudoPassword + LineEnding; | SudoPassword := SudoPassword + LineEnding; | ||
Proc.Input.Write(SudoPassword[1], Length(SudoPassword)); | Proc.Input.Write(SudoPassword[1], Length(SudoPassword)); | ||
− | SudoPassword := '%*'; // | + | SudoPassword := '%*'; // krótki string, mam nadzieję, że trochę pomiesza pamięć; uwaga: używanie PChars jest bardziej niezawodne |
− | SudoPassword := ''; // | + | SudoPassword := ''; // i sprawić, by program był nieco bezpieczniejszy przed węszeniem?!? |
− | // | + | // główna pętla do odczytu wyjścia z stdout i stderr dla sudo |
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or | while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or | ||
(Proc.Stderr.NumBytesAvailable > 0) do | (Proc.Stderr.NumBytesAvailable > 0) do | ||
begin | begin | ||
− | // | + | // odczytaj stdout i zapisz do naszego stdout |
while Proc.Output.NumBytesAvailable > 0 do | while Proc.Output.NumBytesAvailable > 0 do | ||
begin | begin | ||
− | ReadCount := Min(512, Proc.Output.NumBytesAvailable); // | + | ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Czytaj do bufora, nie więcej |
Proc.Output.Read(CharBuffer, ReadCount); | Proc.Output.Read(CharBuffer, ReadCount); | ||
Write(StdOut, Copy(CharBuffer, 0, ReadCount)); | Write(StdOut, Copy(CharBuffer, 0, ReadCount)); | ||
end; | end; | ||
− | // | + | // odczytaj stderr i zapisz do naszego stderr |
while Proc.Stderr.NumBytesAvailable > 0 do | while Proc.Stderr.NumBytesAvailable > 0 do | ||
begin | begin | ||
− | ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); // | + | ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Czytaj do bufora, nie więcej |
Proc.Stderr.Read(CharBuffer, ReadCount); | Proc.Stderr.Read(CharBuffer, ReadCount); | ||
Write(StdErr, Copy(CharBuffer, 0, ReadCount)); | Write(StdErr, Copy(CharBuffer, 0, ReadCount)); | ||
Line 794: | Line 789: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Inne przemyślenia: | |
− | + | Bez wątpienia wskazane byłoby sprawdzenie, czy sudo rzeczywiście pyta o hasło. Można to konsekwentnie sprawdzać, ustawiając zmienną środowiskową SUDO_PROMPT na coś, na co zwracamy uwagę podczas czytania stdout TProcess, unikając problemu związanego z różnymi znakami zachęty dla różnych lokalizacji. Ustawienie zmiennej środowiskowej powoduje wyczyszczenie wartości domyślnych (odziedziczonych z naszego procesu), więc w razie potrzeby musimy skopiować środowisko z naszego programu. | |
− | === | + | === Używanie fdisk z sudo w systemie Linux === |
− | + | Poniższy przykład pokazuje, jak uruchomić fdisk na komputerze z systemem Linux za pomocą polecenia sudo, aby uzyskać uprawnienia roota. '''Uwaga: jest to tylko przykład i nie obejmuje dużej ilości danych na wyjściu.''' | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
program getpartitioninfo; | program getpartitioninfo; | ||
− | { | + | {Pierwotnie napisany przez użytkownika forum Lazarus wjackon153. Prosimy o kontakt w przypadku pytań, uwag itp. |
− | + | Zmodyfikowan z fragmentu kodu Lazarusa na program FPC dla ułatwienia zrozumienia/zwięzłości przez BigChimp} | |
Uses | Uses | ||
Line 814: | Line 809: | ||
begin | begin | ||
− | sPass := 'yoursudopasswordhere'; // | + | sPass := 'yoursudopasswordhere'; // Musisz to zmienić na własne hasło sudo |
− | OutputLines:=TStringList.Create; //... | + | OutputLines:=TStringList.Create; //dla pewności warto zamknąć to w bloku try...finally...end |
− | // OutputLines | + | // OutputLines został utworzony ... To samo dla hProcess. |
− | // | + | // Poniższy przykład otworzy fdisk w tle i da nam informacje o partycjach |
− | // | + | // Ponieważ fdisk wymaga podwyższonych uprawnień, musimy przekazać |
− | // | + | // nasze hasło jako parametr do sudo za pomocą opcji -S, więc proces |
− | // | + | // poczeka, aż nasz program wyśle nasze hasło do aplikacji sudo |
+ | |||
hProcess := TProcess.Create(nil); | hProcess := TProcess.Create(nil); | ||
− | // | + | // W systemach Linux/Unix/FreeBSD/macOS musimy podać pełną ścieżkę do naszego pliku wykonywalnego: |
hProcess.Executable := '/bin/sh'; | hProcess.Executable := '/bin/sh'; | ||
− | // | + | // Teraz dodajemy wszystkie parametry w wierszu poleceń: |
hprocess.Parameters.Add('-c'); | hprocess.Parameters.Add('-c'); | ||
− | // | + | // Tutaj przesyłamy potokiem hasło do polecenia sudo, które następnie wykonuje fdisk -l: |
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); | hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); | ||
− | // | + | // Uruchom asynchronicznie (poczekaj na zakończenie procesu) i użyj potoków, abyśmy mogli odczytać potok wyjściowy |
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; | hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; | ||
− | // | + | // Teraz wykonaj: |
hProcess.Execute; | hProcess.Execute; | ||
− | // hProcess | + | // hProcess powinien teraz uruchomić zewnętrzny plik wykonywalny (ponieważ używamy poWaitOnExit). |
− | // | + | // Teraz możesz przetworzyć wyjście procesu (standardowe wyjście i standardowy błąd), np.: |
OutputLines.Add('stdout:'); | OutputLines.Add('stdout:'); | ||
OutputLines.LoadFromStream(hprocess.Output); | OutputLines.LoadFromStream(hprocess.Output); | ||
OutputLines.Add('stderr:'); | OutputLines.Add('stderr:'); | ||
OutputLines.LoadFromStream(hProcess.Stderr); | OutputLines.LoadFromStream(hProcess.Stderr); | ||
− | // | + | // Pokaż dane wyjściowe na ekranie: |
writeln(OutputLines.Text); | writeln(OutputLines.Text); | ||
− | // | + | // Wyczyść, aby uniknąć wycieków pamięci: |
hProcess.Free; | hProcess.Free; | ||
OutputLines.Free; | OutputLines.Free; | ||
− | // | + | //Poniżej znajduje się kilka przykładów, jak widać, możemy przekazywać niedozwolone znaki, tak jak robimy to z terminala |
− | // | + | //Nawet jeśli przeczytałeś gdzie indziej, że nie możesz upewnić się tą metodą, to możesz :) |
//hprocess.Parameters.Add('ping -c 1 www.google.com'); | //hprocess.Parameters.Add('ping -c 1 www.google.com'); | ||
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2'); | //hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2'); | ||
− | // | + | //Użycie QuotedStr() nie jest wymagane, chociaż zapewnia czystszy kod; |
− | // | + | //możesz użyć podwójnego cudzysłowu i mieć ten sam efekt. |
//hprocess.Parameters.Add('glxinfo | grep direct'); | //hprocess.Parameters.Add('glxinfo | grep direct'); | ||
− | // | + | // Ta metoda może być również używana do instalowania aplikacji z repozytorium: |
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); | //hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); | ||
Line 865: | Line 861: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | ===Parametry zawierające spacje (Zastępowanie cudzysłowów powłoki)=== |
− | + | W powłoce Linuksa możliwe jest pisanie cytowanych argumentów w następujący sposób: | |
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram | gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram | ||
− | + | Polecenie GDB otrzyma 3 argumenty (oprócz pierwszego argumentu, który jest pełną ścieżką do pliku wykonywalnego): | |
#--batch | #--batch | ||
#--eval-command=info symbol 0x0000DDDD | #--eval-command=info symbol 0x0000DDDD | ||
− | # | + | #pełna ścieżka do myprogram |
− | + | Najlepszym rozwiązaniem, aby uniknąć skomplikowanego cytowania, jest przejście na TProcess.Parameters.Add zamiast ustawiania wiersza poleceń dla nietrywialnych przypadków, np. | |
AProcess.Executable:='/usr/bin/gdb'; | AProcess.Executable:='/usr/bin/gdb'; | ||
AProcess.Parameters.Add('--batch'); | AProcess.Parameters.Add('--batch'); | ||
− | AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // | + | AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // zwróć tutaj uwagę na brak cytowania |
AProcess.Parameters.Add('/home/me/myprogram'); | AProcess.Parameters.Add('/home/me/myprogram'); | ||
− | + | Pamiętaj też, aby używać tylko pełne ścieżki. | |
− | TProcess.Commandline | + | TProcess.Commandline obsługuje jednak tylko podstawowe cytowanie parametrów z cudzysłowami. Ujmij w apostrofy ''cały'' parametr zawierający spacje z podwójnymi cudzysłowami. Jak poniżej: |
AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram'; | AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram'; | ||
+ | Właściwość .CommandLine jest [http://bugs.freepascal.org/view.php?id=14446 przestarzała], a raporty o błędach do obsługi bardziej skomplikowanych przypadków cytowania nie będą akceptowane. | ||
− | + | ==Alternatywy LCLIntf== | |
− | + | Czasami nie trzeba jawnie wywoływać zewnętrznego programu, aby uzyskać potrzebną funkcjonalność. Zamiast otwierać aplikację i określać dokument, który ma być z nią powiązany, po prostu poproś system operacyjny o otwarcie dokumentu i pozwól mu użyć domyślnej aplikacji powiązanej z tym typem pliku. Poniżej kilka przykładów. | |
− | ==LCLIntf | ||
− | |||
− | === | + | ===Otwieranie dokumentu w domyślnej aplikacji=== |
− | + | W niektórych sytuacjach musisz otworzyć jakiś dokument/plik przy użyciu domyślnej aplikacji z nim powiązanej, zamiast uruchamiać konkretny program. To zależy od działającego systemu operacyjnego. Lazarus zapewnia niezależną od platformy procedurę OpenDocument, która zajmie się tym za Ciebie. Twoja aplikacja będzie nadal działać, nie czekając na zamknięcie procesu tworzenia dokumentu. | |
− | |||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 907: | Line 901: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * [[opendocument|OpenDocument | + | * [[opendocument|Dokumentacja OpenDocument]] |
− | === | + | ===Otwieranie strony web w domyślnej przeglądarce internetowej=== |
− | + | Aby to zrobić, po prostu przekaż wymagany adres URL. Przedrostek protokołu http:// wydaje się w pewnych okolicznościach opcjonalny. Ponadto przekazanie nazwy pliku wydaje się dawać takie same wyniki jak OpenDocument() | |
− | |||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
uses LCLIntf; | uses LCLIntf; | ||
Line 919: | Line 912: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Zobacz także: | |
− | * [[OpenURL|OpenURL | + | * [[OpenURL|Odnośnik do OpenURL]] |
− | + | Lub możesz użyć '''TProcess''' w taki sposób: | |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 928: | Line 921: | ||
procedure OpenWebPage(URL: string); | procedure OpenWebPage(URL: string); | ||
− | // | + | // Możliwe, że musisz podać swój adres URL w cudzysłowiu np. "www.lazarus.freepascal.org" |
var | var | ||
Browser, Params: string; | Browser, Params: string; | ||
Line 937: | Line 930: | ||
Executable := Browser; | Executable := Browser; | ||
Params:=Format(Params, [URL]); | Params:=Format(Params, [URL]); | ||
− | Params:=copy(Params,2,length(Params)-2); // | + | Params:=copy(Params,2,length(Params)-2); // usuń znaki "", nowa wersja TProcess.Parameters robi to sama |
Parameters.Add(Params); | Parameters.Add(Params); | ||
Options := [poNoConsole]; | Options := [poNoConsole]; | ||
Line 947: | Line 940: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | ==Zobacz także== |
− | * [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess | + | * [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html dokumentacja TProcess] |
* [[opendocument|OpenDocument]] | * [[opendocument|OpenDocument]] | ||
* [[OpenURL]] | * [[OpenURL]] |
Latest revision as of 12:28, 3 November 2023
│
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) │
Przegląd: Porównanie
Istnieją różne sposoby dostępne w bibliotekach RTL, FCL i LCL dotyczące wykonywania zewnętrznego polecenia/procesu/programu.
Metoda | Biblioteka | Platformy | Pojedyncza linia | Cechy |
---|---|---|---|---|
ExecuteProcess | RTL | Wieloplatformowy | Tak | Bardzo ograniczone, synchroniczne. |
ShellExecute | WinAPI | Tylko MS Windows | Tak | Wiele. Może uruchamiać programy z podwyższonymi uprawnieniami administratora. |
fpsystem, fpexecve | Unix | Tylko Unix | ||
TProcess | FCL | Wieloplatformowy | Nie | Pełny. |
RunCommand | FCL | Wieloplatformowy Wymaga FPC 2.6.2+ | Tak | Obejmuje typowe użycie TProcess. |
OpenDocument | LCL | Wieloplatformowy | Tak | Tylko otwarty dokument. Dokument otworzy się z aplikacją powiązaną z podanym typem dokumentu. |
(Proces.)RunCommand
- Odnośnik do RunCommand
- Odnośnik do RunCommandInDir
W FPC 2.6.2 niektóre funkcje pomocnicze dla TProcess zostały dodane do modułu process w oparciu o wrappery (funkcje lub klasy opakowujące) używane w projekcie fpcup. Te funkcje są przeznaczone do użytku podstawowego i pośredniego, i mogą przechwytywać dane wyjściowe do pojedynczego ciągu string i w pełni obsługiwać duże dane wyjściowe.
Prosty przykład to:
program project1;
{$mode objfpc}{$H+}
uses
Process;
var
s : ansistring;
begin
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
writeln(s);
end.
Zauważ jednak, że nie wszystkie „wbudowane” polecenia powłoki (np. aliasy) działają, ponieważ aliasy domyślnie nie są rozwijane w powłokach nieinteraktywnych, a .bashrc nie jest odczytywany przez powłoki nieinteraktywne, chyba że ustawisz zmienną środowiskową BASH_ENV. tak więc poniższy przykąłd nie daje żadnych danych wyjściowych:
program project2;
{$mode objfpc}{$H+}
uses
Process;
var
s : ansistring;
begin
if RunCommand('/bin/bash',['-c','alias'],s) then
writeln(s);
end.
Przeciążony wariant RunCommand zwraca kod wyjścia programu. RunCommandInDir uruchamia polecenie w innym katalogu (ustawia p.CurrentDirectory):
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;
W FPC 3.2.0+ RunCommand otrzymał dodatkowe warianty, które pozwalają nadpisać TProcessOptions i WindowOptions.
Rozszerzenia RunCommand
W FPC 3.2.0+ implementacja RunCommand została uogólniona i ponownie zintegrowana z TProcess, aby umożliwić szybszą budowę własnych wariantów. Jako przykład wariant RunCommand z limitem czasu:
{$mode delphi}
uses classes, sysutils, process, dateutils;
type
{ TProcessTimeout }
TProcessTimeout = class(TProcess)
public
timeoutperiod: TTime;
timedout : boolean;
started : TDateTime;
procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
end;
procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
begin
if status=RunCommandIdle then
begin
if (now-started)>timeoutperiod then
begin
timedout:=true;
Terminate(255);
exit;
end;
sleep(RunCommandSleepTime);
end;
end;
function RunCommandTimeout(const exename:TProcessString;const commands:array of TProcessString;out outputstring:string; Options : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=60):boolean;
Var
p : TProcessTimeout;
i,
exitstatus : integer;
ErrorString : String;
begin
p:=TProcessTimeout.create(nil);
p.OnRunCommandEvent:=p.LocalnIdleSleep;
p.timeoutperiod:=timeout/SecsPerDay;
if Options<>[] then
P.Options:=Options - [poRunSuspended,poWaitOnExit];
p.options:=p.options+[poRunIdle]; // potrzebne do uruchomienia zdarzenia RUNIDLE. Zobacz Zmiany Użytkownika dla wersji 3.2.0
P.ShowWindow:=SwOptions;
p.Executable:=exename;
if high(commands)>=0 then
for i:=low(commands) to high(commands) do
p.Parameters.add(commands[i]);
p.timedout:=false;
p.started:=now;
try
// główna pętla wariantów runcommand(), pierwotnie oparta na scenariuszu "dużego wyjścia" na wiki, ale stale rozwijana przez 5 lat.
result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0;
if p.timedout then
result:=false;
finally
p.free;
end;
if exitstatus<>0 then result:=false;
end;
// przykładowe zastosowanie:
var
s : string;
begin
for s in FileList do
begin
if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then
begin
// nie udało się uruchomić lub upłynął limit czasu. np. movefile() do "wadliwego" katalogu.
end
else
begin
// ok, plik przeniesiony do "dobrego" katalogu.
end;
end;
end;
SysUtils.ExecuteProcess
(Wieloplatformowy)
Pomimo wielu ograniczeń, najprostszym sposobem uruchomienia programu (modalnego, bez potoków lub jakiejkolwiek formy sterowania) jest po prostu użycie:
SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);
Proces wywołujący działa synchronicznie: „zawiesza się” do momentu zakończenia działania zewnętrznego programu — ale może to być przydatne, jeśli wymagasz od użytkownika wykonania czegoś przed kontynuowaniem pracy w aplikacji. Aby uzyskać bardziej wszechstronne podejście, zapoznaj się z sekcją dotyczącą preferowanego międzyplatformowego RunCommand lub innej funkcjonalności TProcess, lub jeśli chcesz obsługiwać tylko system Windows, możesz użyć ShellExecute.
- Odniesienie do ExecuteProcess
MS Windows: CreateProcess, ShellExecute i WinExec
ShellExecute to standardowa funkcja systemu MS Windows (ShellApi.h) z dobrą dokumentacją na MSDN (zwróć uwagę na ich komentarze na temat inicjalizacji COM, jeśli odkryjesz, że ta jest niewiarygodna).
uses ..., ShellApi;
// Proste jednowierszowe (ignorowanie zwracanych błędów):
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;
// Wykonaj plik wsadowy:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;
// Otwórz okno poleceń w danym folderze:
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;
// Otwórz adres URL strony internetowej w domyślnej przeglądarce za pomocą polecenia „start” (poprzez ukryte okno cmd):
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
// lub przydatna procedura:
procedure RunShellExecute(const prog,params:string);
begin
// parametry: ( Uchwyt, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' nie zawsze jest potrzebny
// path+prog, parametry, folder roboczy,
// okno command 0=ukryte / 1=SW_SHOWNORMAL / 3=maks / 7=min) // dla stałych SW_ : uses ... Windows ...
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //sukces
// zwracane wartości 0..32 są błędami
end;
Istnieje również ShellExecuteExW jako wersja WideChar, a ShellExecuteExA to wersja AnsiChar.
Opcja fMask może również używać SEE_MASK_DOENVSUBST lub SEE_MASK_FLAG_NO_UI lub SEE_MASK_NOCLOSEPROCESS itp.
Jeśli w Delphi używałeś ShellExecute do dokumentów takich jak dokumenty Word lub adresy URL, spójrz na funkcje open* (OpenURL itp.) w module lclintf (zobacz sekcję Zobacz także na dole tej strony).
Używanie ShellExecuteEx do podnoszenia uprawnień administratora
Jeśli potrzebujesz uruchomić zewnętrzny program z podwyższonymi uprawnieniami administratora, możesz użyć metody runas z alternatywną funkcją ShellExecuteEx:
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
// Przykład, który używa podwyższonego uprawnienia dla rundll, aby otworzyć Panel sterowania do programów i funkcji
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;
Unix fpsystem, fpexecve i shell
Te funkcje są zależne od platformy.
Linux.Shell/Unix.Shell był odpowiednikiem fpsystem w wersji 1.0.x ze słabo zdefiniowaną obsługą błędów i po dekadzie deprecjacji został ostatecznie usunięty. Prawie we wszystkich przypadkach może być zastąpiony przez fpsystem, który obsługuje więcej standardów POSIX, takich jak obsługa błędów.
TProcess
Możesz użyć TProcess do uruchamiania zewnętrznych programów. Niektóre z korzyści płynących z używania TProcess to:
- Jest niezależny od platformy.
- Potrafi czytać ze standardowego wyjścia i zapisywać na standardowe wejście.
- Możliwe jest oczekiwanie na zakończenie polecenia lub pozwolenie na jego działanie podczas działania programu.
Ważne informacje:
- TProcess nie jest terminalem/powłoką! Nie możesz bezpośrednio wykonywać skryptów lub przekierowywać wyjścia za pomocą operatorów takich jak „|”, „>”, „<”, „&” itp. Możliwe jest uzyskanie tych samych wyników za pomocą TProcess i za pomocą pascala - kilka przykładów poniżej.
- Przypuszczalnie w systemie Linux/Unix: musisz podać pełną ścieżkę do pliku wykonywalnego. Na przykład „/bin/cp” zamiast „cp”. Jeśli program znajduje się w standardowej PATH, możesz użyć funkcji FindDefaultExecutablePath z modułu FileUtil biblioteki LCL.
- W systemie Windows, jeśli polecenie znajduje się w ścieżce, nie musisz określać pełnej ścieżki.
- Odnośnik do TProcess
Najprostszy przykład
Wiele typowych przypadków zostało przygotowanych w funkcjach RunCommand. Zanim zaczniesz kopiować i wklejać poniższe przykłady, najpierw je sprawdź.
Prosty przykład
Ten przykład (który nie powinien być używany w środowisku produkcyjnym, patrz Czytanie dużych ilości danych wyjściowych lub, lepiej, RunCommand) pokazuje tylko, jak uruchomić zewnętrzny program, nic więcej:
// Jest to program demonstracyjny, który
// pokazuje, jak uruchomić zewnętrzny program.
program launchprogram;
// Tutaj dołączamy moduły zawierające przydatne
// funkcje i procedury, których będziemy potrzebować.
uses
Classes, SysUtils, Process;
// Definiuje zmienną "AProcess" jako zmienną typu "TProcess"
var
AProcess: TProcess;
// Tu zaczyna działać nasz program
begin
// Teraz utworzymy obiekt TProcess i przypiszemy go
// do zmiennej AProcess.
AProcess := TProcess.Create(nil);
// Powiedz nowemu AProcess, jakie jest polecenie do wykonania.
// Użyjmy kompilatora Free Pascal (dokładniej wersji x64)
// Lecz jeśli używasz kompilatora 32-bit zmień ppcx64 na ppc386
AProcess.Executable:= 'ppcx64';
// Przekaż opcję -h razem z ppcx64, więc faktycznie wykonywane
// jest polecenie 'ppcx64 -h':
AProcess.Parameters.Add('-h');
// Zdefiniujemy opcję działającą, w czasie wykonywania programu.
// Ta opcja sprawi, że nasz program nie będzie kontynuowany,
// dopóki program zewnętrzy, który uruchomimy, nie przestanie działać.
// /------------\
AProcess.Options := AProcess.Options + [poWaitOnExit];
// Teraz pozwól AProcess uruchomić program zewnętrzny
AProcess.Execute;
// Poniższy kod nie wykona się, dopóki ppcx64 nie przestanie działać.
AProcess.Free;
end.
Otóż to! Właśnie nauczyłeś się uruchamiać zewnętrzny program z wnętrza własnego programu.
Ulepszony przykład (ale jeszcze niepoprawny)
Wszystko fajnie, ale jak odczytać dane wyjściowe uruchomionego programu?
Cóż, rozszerzmy nieco nasz przykład, jak poniżej: Ten przykład jest prosty, więc możesz się z niego uczyć. Jednak nie używaj tego przykładu w kodzie produkcyjnym, ale użyj kodu z Czytanie dużych ilości danych wyjściowych.
// To jest
// WADLIWY
// program demonstracyjny, który pokazuje,
// jak uruchomić zewnętrzny program
// i odczytać z jego dane wyjściowe.
program launchprogram;
// Tutaj dołączamy moduły zawierające przydatne
// funkcje i procedury, których będziemy potrzebować.
uses
Classes, SysUtils, Process;
// Definiuje zmienną "AProcess" jako zmienną typu "TProcess"
// Teraz także dodajemy TStringList do przechowywania danych
// odczytanych z wyjścia programu.
var
AProcess: TProcess;
AStringList: TStringList;
// Tu zaczyna działać nasz program
begin
// Teraz utworzymy obiekt TProcess i przypiszemy go
// do zmiennej AProcess.
AProcess := TProcess.Create(nil);
// Powiedz nowemu AProcess, jakie jest polecenie do wykonania.
AProcess.Executable := '/usr/bin/ppcx64';
AProcess.Parameters.Add('-h');
// Zdefiniujemy opcję działającą, w czasie wykonywania programu.
// Ta opcja sprawi, że nasz program nie będzie kontynuowany,
// dopóki program zewnętrzy, który uruchomimy, nie przestanie działać.
// Dodatkowo teraz powiemy mu, że chcemy odczytać dane wyjściowe.
// /------------|-----------\
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
// Teraz, gdy AProcess wie, jaki jest wiersz poleceń, można go uruchomić.
AProcess.Execute;
// Po zakończeniu AProcess zostanie wykonana reszta programu.
// Teraz odczytaj wynik działania programu, za pomocą TStringList.
AStringList := TStringList.Create;
AStringList.LoadFromStream(AProcess.Output);
// Zapisz dane wyjściowe do pliku i zwolnij obiekt AStringList.
AStringList.SaveToFile('output.txt');
AStringList.Free;
// Teraz, gdy dane wyjściowe z procesu są przetworzone, także można je zwolnić.
AProcess.Free;
end.
Czytanie dużych ilości danych wyjściowych
W poprzednim przykładzie czekaliśmy, aż program się zakończy. Następnie czytaliśmy, co program zapisał na swoim wyjściu.
Załóżmy, że program zapisuje dużo danych na wyjściu. Następnie potok wyjściowy zapełnia się i wywoływany program czeka, aż potok zostanie odczytany.
Ale program wywołujący nie czyta z niego, dopóki wywoływany program się nie zakończy. Następuje impas.
Poniższy przykład nie używa zatem poWaitOnExit, ale odczytuje dane wyjściowe, gdy program jest nadal uruchomiony. Dane wyjściowe są przechowywane w strumieniu pamięci, który można później wykorzystać do odczytania danych wyjściowych do TStringList.
Jeśli chcesz odczytać dane wyjściowe z procesu zewnętrznego i nie możesz użyć RunCommand, jest to kod, który powinien być podstawą do użytku produkcyjnego. Jeśli używasz FPC 3.2.0+, parametryzowalna forma tej pętli jest dostępna jako metoda RunCommandLoop w TProcess. Zdarzenie OnRunCommandEvent można podłączyć, aby dalej modyfikować zachowanie.
program LargeOutputDemo;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, Process; // Process to moduł, który zawiera TProcess
const
BUF_SIZE = 2048; // Rozmiar bufora do odczytu danych wyjściowych w porcjach
var
AProcess : TProcess;
OutputStream : TStream;
BytesRead : longint;
Buffer : array[1..BUF_SIZE] of byte;
begin
// Skonfiguruj proces; jako przykład używane jest rekurencyjne przeszukiwanie
// katalogów, ponieważ zwykle skutkuje to dużą ilością danych.
AProcess := TProcess.Create(nil);
// Polecenia dla Windows i *nix są różne, stąd dyrektywy $IFDEF
{$IFDEF Windows}
// W systemie Windows polecenie dir nie może być użyte bezpośrednio, ponieważ jest to
// wbudowane polecenie powłoki. Dlatego potrzebny jest cmd.exe i dodatkowe parametry.
AProcess.Executable := 'c:\windows\system32\cmd.exe';
AProcess.Parameters.Add('/c');
AProcess.Parameters.Add('dir /s c:\windows');
{$ENDIF Windows}
{$IFDEF Unix}
AProcess.Executable := '/bin/ls';
{$IFDEF Darwin}
AProcess.Parameters.Add('-recursive');
AProcess.Parameters.Add('-all');
{$ENDIF Darwin}
{$IFDEF Linux}
AProcess.Parameters.Add('--recursive');
AProcess.Parameters.Add('--all');
{$ENDIF Linux}
{$IFDEF FreeBSD}
AProcess.Parameters.Add('-R');
AProcess.Parameters.Add('-a');
{$ENDIF FreeBSD}
AProcess.Parameters.Add('-l');
{$ENDIF Unix}
// Aby można było przechwycić dane wyjściowe, należy użyć opcji procesu poUsePipes.
// Nie można użyć opcji procesu poWaitOnExit, ponieważ zablokuje to program główny,
// uniemożliwiając mu odczytanie danych wyjściowych procesu zewnętrznego.
AProcess.Options := [poUsePipes];
// Uruchom proces (uruchom polecenie dir dla Windows lub ls dla Linux/Unix)
AProcess.Execute;
// Utwórz obiekt strumienia, w którym będą przechowywane wygenerowane dane wyjściowe.
// Może to być również strumień plikowy do bezpośredniego zapisywania danych wyjściowych na dysku.
OutputStream := TMemoryStream.Create;
// Wszystkie wygenerowane dane wyjściowe z AProcess są odczytywane w pętli,
// dopóki nie będzie dostępnych więcej danych
repeat
// Pobierz nowe dane z procesu do maksymalnego rozmiaru buforu, który został przydzielony.
// Zauważ, że wszystkie wywołania Read(...) będą blokowane z wyjątkiem ostatniego, które zwraca 0 (zero).
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
// Bajty, które zostały odczytane, dodaj do strumienia w celu późniejszego wykorzystania
OutputStream.Write(Buffer, BytesRead)
until BytesRead = 0; // Zakończ pętlę, jeśli nie ma więcej danych
// Proces się zakończył, więc można go posprzątać
AProcess.Free;
// Teraz, gdy wszystkie dane zostały odczytane, można z nich korzystać;
// na przykład po to, by zapisać je do pliku na dysku
with TFileStream.Create('output.txt', fmCreate) do
begin
OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku
CopyFrom(OutputStream, OutputStream.Size);
Free
end;
// Lub dane mogą być wyświetlane na ekranie
with TStringList.Create do
begin
OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku
LoadFromStream(OutputStream);
writeln(Text);
writeln('--- Liczba linii = ', Count, '----');
Free
end;
// Sprzątanie
OutputStream.Free;
end.
Zauważ, że powyższe można również osiągnąć za pomocą RunCommand:
var s: string;
...
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);
Korzystanie z wejścia i wyjścia TProcess
Zobacz przykładowe demo procesu w Lazarus-CCR SVN.
Wskazówki dotyczące korzystania z TProcess
Podczas tworzenia programu wieloplatformowego nazwę pliku wykonywalnego specyficzną dla systemu operacyjnego można ustawić za pomocą dyrektyw „{$IFDEF}” i „{$ENDIF}”. Przykład:
{...}
AProcess := TProcess.Create(nil)
{$IFDEF WIN32}
AProcess.Executable := 'calc.exe';
{$ENDIF}
{$IFDEF LINUX}
AProcess.Executable := FindDefaultExecutablePath('kcalc');
{$ENDIF}
AProcess.Execute;
{...}
macOS pokazuje pakiet aplikacji na pierwszym planie
Możesz uruchomić pakiet aplikacji przez TProcess, uruchamiając plik wykonywalny w pakiecie. Na przykład:
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
Spowoduje to uruchomienie Kalendarza, ale okno będzie znajdować się za bieżącą aplikacją. Aby uzyskać aplikację na pierwszym planie, możesz użyć narzędzia open z parametrem -n:
AProcess.Executable:='/usr/bin/open';
AProcess.Parameters.Add('-n');
AProcess.Parameters.Add('-a'); // opcjonalny: określa aplikację do użycia; przeszukuje tylko katalogi aplikacji
AProcess.Parameters.Add('-W'); // opcjonalnie: open czeka, aż aplikacje, które otwiera (lub były już otwarte) zostaną zamknięte
AProcess.Parameters.Add('Pages.app'); // dołączenie .app jest opcjonalne
Jeśli Twoja aplikacja potrzebuje parametrów, możesz przekazać wraz z open parametr --args, po którym wszystkie dalsze parametry są przekazywane do aplikacji:
AProcess.Parameters.Add('--args');
AProcess.Parameters.Add('argument1');
AProcess.Parameters.Add('argument2');
Zobacz także: macOS polecenie open.
Uruchom oddzielny program
Normalnie program uruchomiony przez twoją aplikację jest procesem potomnym i zostaje zabity, gdy twoja aplikacja zostanie zabita. Jeśli chcesz uruchomić samodzielny program, który nadal będzie działał, możesz użyć:
var
Process: TProcess;
I: Integer;
begin
Process := TProcess.Create(nil);
try
Process.InheritHandles := False;
Process.Options := [];
Process.ShowWindow := swoShow;
// Skopiuj domyślne zmienne środowiskowe, w tym zmienną DISPLAY, aby aplikacja GUI działała
for I := 1 to GetEnvironmentVariableCount do
Process.Environment.Add(GetEnvironmentString(I));
Process.Executable := '/usr/bin/gedit';
Process.Execute;
finally
Process.Free;
end;
end;
Przykład „rozmowy” z procesem aspell
Wewnątrz kodu źródłowego pasdoc można znaleźć dwa moduły, które sprawdzają pisownię poprzez „rozmawianie” przez potoki z uruchomionym procesem aspell:
- Moduł PasDoc_ProcessLineTalk.pas implementuje klasę TProcessLineTalk, potomka TProcess, która może być z łatwością używana do komunikacji z dowolnym procesem linia po linii.
- Moduł PasDoc_Aspell.pas implementuje klasę TAspellProcess, która sprawdza pisownię za pomocą bazowej instancji TProcessLineTalk do wykonania aspell i komunikacji z uruchomionym procesem aspell.
Obydwa moduły są raczej niezależne od pozostałych źródeł pasdoc, więc mogą służyć jako rzeczywiste przykłady użycia TProcess do uruchamiania i komunikacji przez potoki z innym programem.
Zastępowanie operatorów powłoki, takich jak „| < >”
Czasami chcesz uruchomić bardziej skomplikowane polecenie, które przesyła dane do innego polecenia lub do pliku. Coś podobnego do
ShellExecute('firstcommand.exe | secondcommand.exe');
lub
ShellExecute('dir > output.txt');
Wykonanie tego za pomocą TProcess nie zadziała. tj:
// to nie zadziała!
Process.CommandLine := 'firstcommand.exe | secondcommand.exe';
Process.Execute;
Dlaczego używanie specjalnych operatorów do przekierowywania wyjścia nie działa?
TProcess nie jest środowiskiem powłoki, tylko procesem. To nie dwa procesy, to tylko jeden. Możliwe jest jednak przekierowanie danych wyjściowych dokładnie tak, jak chcesz. Zobacz następną sekcję.
Jak przekierować wyjście za pomocą TProcess
Możesz przekierować dane wyjściowe polecenia do innego polecenia, używając osobnej instancji TProcess dla każdego polecenia.
Oto przykład, który wyjaśnia, jak przekierować dane wyjściowe jednego procesu do drugiego. Aby przekierować wyjście procesu do pliku/strumienia, zobacz przykład Czytanie dużych ilości danych wyjściowych.
Możesz nie tylko przekierować „normalne” wyjście (znane również jako stdout), ale możesz także przekierować wyjście błędu (stderr), jeśli określisz opcję poStderrToOutPut, jak to widać w opcjach drugiego procesu.
program Project1;
uses
Classes, sysutils, process;
var
FirstProcess,
SecondProcess: TProcess;
Buffer: array[0..127] of char;
ReadCount: Integer;
ReadSize: Integer;
begin
FirstProcess := TProcess.Create(nil);
SecondProcess := TProcess.Create(nil);
FirstProcess.Options := [poUsePipes];
FirstProcess.Executable := 'pwd';
SecondProcess.Options := [poUsePipes,poStderrToOutPut];
SecondProcess.Executable := 'grep';
SecondProcess.Parameters.Add(DirectorySeparator+ ' -');
// byłoby to to samo co "pwd | grep / -"
FirstProcess.Execute;
SecondProcess.Execute;
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
begin
if FirstProcess.Output.NumBytesAvailable > 0 then
begin
// upewnij się, że nie czytamy więcej danych niż przydzieliliśmy w buforze
ReadSize := FirstProcess.Output.NumBytesAvailable;
if ReadSize > SizeOf(Buffer) then
ReadSize := SizeOf(Buffer);
// teraz wczytaj dane wyjściowe do bufora
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
// i zapisz bufor do drugiego procesu
SecondProcess.Input.Write(Buffer[0], ReadCount);
// jeśli SecondProcess zapisuje dużo danych do swojego wyjścia,
// powinniśmy odczytać te dane tutaj, aby zapobiec zakleszczeniu,
// patrz poprzedni przykład "Czytanie dużych ilości danych wyjściowych"
end;
end;
// Zamknij dane wejściowe w SecondProcess,
// aby zakończyć przetwarzanie swoich danych
SecondProcess.CloseInput;
// i poczekaj, aż się zakończy, lecz uważaj, jakie polecenie uruchamiasz,
// ponieważ może się ono nie zakończyć, gdy jego dane wejściowe są zamknięte,
// to następująca linia może zapętlić się w nieskończoność
while SecondProcess.Running do
Sleep(1);
// Gotowe! Reszta programu jest tylko po to, aby przykład był bardziej 'użyteczny'
// użyjemy ponownie Buffer, aby wyprowadzić wyjście SecondProcess do *tego* programu stdout
WriteLn('Rozpoczęcie wyjścia grep:');
ReadSize := SecondProcess.Output.NumBytesAvailable;
if ReadSize > SizeOf(Buffer) then
ReadSize := SizeOf(Buffer);
if ReadSize > 0 then
begin
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);
WriteLn(Copy(Buffer,0, ReadCount));
end
else
WriteLn('grep nie znalazł tego, czego szukaliśmy. ', SecondProcess.ExitStatus);
WriteLn('Zakończenie wyjścia grep:');
// zwolnij obiekty naszych procesów
FirstProcess.Free;
SecondProcess.Free;
end.
W ten sposób możesz przekierować dane wyjściowe z jednego programu do drugiego.
Uwagi
Ten przykład może wydawać się przesadny, ponieważ możliwe jest uruchamianie „skomplikowanych” poleceń przy użyciu powłoki przy pomocy TProcess, takich jak:
Process.Commandline := 'sh -c "pwd | grep / -"';
Ale nasz przykład jest bardziej wieloplatformowy, ponieważ nie wymaga modyfikacji, aby działać w systemie Windows lub Linux itp. Polecenie „sh” może, ale nie musi istnieć na twojej platformie i jest ogólnie dostępny tylko na platformach *nix. W naszym przykładzie mamy również większą elastyczność, ponieważ możesz czytać i zapisywać z/do wejścia, wyjścia i stderr każdego procesu z osobna, co może być bardzo korzystne dla twojego projektu.
Przekierowywanie wejścia i wyjścia oraz działanie z rootem
Częstym problemem w systemach Unix (FreeBSD, macOS) i Linux jest to, że chcesz uruchomić jakiś program na koncie root (lub, bardziej ogólnie, na innym koncie użytkownika). Przykładem może być uruchomienie polecenia ping.
Jeśli możesz użyć do tego sudo, możesz dostosować następujący przykład zaadaptowany z postu opublikowanego przez andymana na forum ([1]). Ten przykład uruchamia polecenie ls
w katalogu /root
, ale oczywiście można go dostosować.
Lepszym sposobem na to jest użycie pakietu policykit, który powinien być dostępny we wszystkich najnowszych Linuksach. Zobacz wątek na forum, aby uzyskać szczegółowe informacje.
Duże części tego kodu są podobne do wcześniejszego przykładu, ale pokazuje również, jak przekierować stdout i stderr procesu wywoływanego oddzielnie na stdout i stderr naszego własnego kodu.
program rootls;
{ Demonstracja użycia TProcess, przekierowania stdout/stderr do naszego stdout/stderr,
wywoływania sudo w systemach FreeBSD/Linux/macOS i dostarczania danych wejściowych na stdin}
{$mode objfpc}{$H+}
uses
Classes,
Math, {for min}
Process;
procedure RunsLsRoot;
var
Proc: TProcess;
CharBuffer: array [0..511] of char;
ReadCount: integer;
ExitCode: integer;
SudoPassword: string;
begin
WriteLn('Please enter the sudo password:');
Readln(SudoPassword);
ExitCode := -1; //Zacznij od porażki, zobaczmy później, czy to zadziała
Proc := TProcess.Create(nil); //Utwórz nowy proces
try
Proc.Options := [poUsePipes, poStderrToOutPut]; //Użyj potoków, aby przekierować stdin,stdout,stderr
Proc.CommandLine := 'sudo -S ls /root'; //Uruchom ls /root jako root za pomocą sudo
// -S powoduje, że sudo odczytuje hasło z stdin.
Proc.Execute; //Uruchom to. sudo prawdopodobnie poprosi teraz o hasło
// wpisz hasło do stdin programu sudo:
SudoPassword := SudoPassword + LineEnding;
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
SudoPassword := '%*'; // krótki string, mam nadzieję, że trochę pomiesza pamięć; uwaga: używanie PChars jest bardziej niezawodne
SudoPassword := ''; // i sprawić, by program był nieco bezpieczniejszy przed węszeniem?!?
// główna pętla do odczytu wyjścia z stdout i stderr dla sudo
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
(Proc.Stderr.NumBytesAvailable > 0) do
begin
// odczytaj stdout i zapisz do naszego stdout
while Proc.Output.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Czytaj do bufora, nie więcej
Proc.Output.Read(CharBuffer, ReadCount);
Write(StdOut, Copy(CharBuffer, 0, ReadCount));
end;
// odczytaj stderr i zapisz do naszego stderr
while Proc.Stderr.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Czytaj do bufora, nie więcej
Proc.Stderr.Read(CharBuffer, ReadCount);
Write(StdErr, Copy(CharBuffer, 0, ReadCount));
end;
end;
ExitCode := Proc.ExitStatus;
finally
Proc.Free;
Halt(ExitCode);
end;
end;
begin
RunsLsRoot;
end.
Inne przemyślenia: Bez wątpienia wskazane byłoby sprawdzenie, czy sudo rzeczywiście pyta o hasło. Można to konsekwentnie sprawdzać, ustawiając zmienną środowiskową SUDO_PROMPT na coś, na co zwracamy uwagę podczas czytania stdout TProcess, unikając problemu związanego z różnymi znakami zachęty dla różnych lokalizacji. Ustawienie zmiennej środowiskowej powoduje wyczyszczenie wartości domyślnych (odziedziczonych z naszego procesu), więc w razie potrzeby musimy skopiować środowisko z naszego programu.
Używanie fdisk z sudo w systemie Linux
Poniższy przykład pokazuje, jak uruchomić fdisk na komputerze z systemem Linux za pomocą polecenia sudo, aby uzyskać uprawnienia roota. Uwaga: jest to tylko przykład i nie obejmuje dużej ilości danych na wyjściu.
program getpartitioninfo;
{Pierwotnie napisany przez użytkownika forum Lazarus wjackon153. Prosimy o kontakt w przypadku pytań, uwag itp.
Zmodyfikowan z fragmentu kodu Lazarusa na program FPC dla ułatwienia zrozumienia/zwięzłości przez BigChimp}
Uses
Classes, SysUtils, FileUtil, Process;
var
hprocess: TProcess;
sPass: String;
OutputLines: TStringList;
begin
sPass := 'yoursudopasswordhere'; // Musisz to zmienić na własne hasło sudo
OutputLines:=TStringList.Create; //dla pewności warto zamknąć to w bloku try...finally...end
// OutputLines został utworzony ... To samo dla hProcess.
// Poniższy przykład otworzy fdisk w tle i da nam informacje o partycjach
// Ponieważ fdisk wymaga podwyższonych uprawnień, musimy przekazać
// nasze hasło jako parametr do sudo za pomocą opcji -S, więc proces
// poczeka, aż nasz program wyśle nasze hasło do aplikacji sudo
hProcess := TProcess.Create(nil);
// W systemach Linux/Unix/FreeBSD/macOS musimy podać pełną ścieżkę do naszego pliku wykonywalnego:
hProcess.Executable := '/bin/sh';
// Teraz dodajemy wszystkie parametry w wierszu poleceń:
hprocess.Parameters.Add('-c');
// Tutaj przesyłamy potokiem hasło do polecenia sudo, które następnie wykonuje fdisk -l:
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');
// Uruchom asynchronicznie (poczekaj na zakończenie procesu) i użyj potoków, abyśmy mogli odczytać potok wyjściowy
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
// Teraz wykonaj:
hProcess.Execute;
// hProcess powinien teraz uruchomić zewnętrzny plik wykonywalny (ponieważ używamy poWaitOnExit).
// Teraz możesz przetworzyć wyjście procesu (standardowe wyjście i standardowy błąd), np.:
OutputLines.Add('stdout:');
OutputLines.LoadFromStream(hprocess.Output);
OutputLines.Add('stderr:');
OutputLines.LoadFromStream(hProcess.Stderr);
// Pokaż dane wyjściowe na ekranie:
writeln(OutputLines.Text);
// Wyczyść, aby uniknąć wycieków pamięci:
hProcess.Free;
OutputLines.Free;
//Poniżej znajduje się kilka przykładów, jak widać, możemy przekazywać niedozwolone znaki, tak jak robimy to z terminala
//Nawet jeśli przeczytałeś gdzie indziej, że nie możesz upewnić się tą metodą, to możesz :)
//hprocess.Parameters.Add('ping -c 1 www.google.com');
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');
//Użycie QuotedStr() nie jest wymagane, chociaż zapewnia czystszy kod;
//możesz użyć podwójnego cudzysłowu i mieć ten sam efekt.
//hprocess.Parameters.Add('glxinfo | grep direct');
// Ta metoda może być również używana do instalowania aplikacji z repozytorium:
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name');
end.
Parametry zawierające spacje (Zastępowanie cudzysłowów powłoki)
W powłoce Linuksa możliwe jest pisanie cytowanych argumentów w następujący sposób:
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
Polecenie GDB otrzyma 3 argumenty (oprócz pierwszego argumentu, który jest pełną ścieżką do pliku wykonywalnego):
- --batch
- --eval-command=info symbol 0x0000DDDD
- pełna ścieżka do myprogram
Najlepszym rozwiązaniem, aby uniknąć skomplikowanego cytowania, jest przejście na TProcess.Parameters.Add zamiast ustawiania wiersza poleceń dla nietrywialnych przypadków, np.
AProcess.Executable:='/usr/bin/gdb'; AProcess.Parameters.Add('--batch'); AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // zwróć tutaj uwagę na brak cytowania AProcess.Parameters.Add('/home/me/myprogram');
Pamiętaj też, aby używać tylko pełne ścieżki.
TProcess.Commandline obsługuje jednak tylko podstawowe cytowanie parametrów z cudzysłowami. Ujmij w apostrofy cały parametr zawierający spacje z podwójnymi cudzysłowami. Jak poniżej:
AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
Właściwość .CommandLine jest przestarzała, a raporty o błędach do obsługi bardziej skomplikowanych przypadków cytowania nie będą akceptowane.
Alternatywy LCLIntf
Czasami nie trzeba jawnie wywoływać zewnętrznego programu, aby uzyskać potrzebną funkcjonalność. Zamiast otwierać aplikację i określać dokument, który ma być z nią powiązany, po prostu poproś system operacyjny o otwarcie dokumentu i pozwól mu użyć domyślnej aplikacji powiązanej z tym typem pliku. Poniżej kilka przykładów.
Otwieranie dokumentu w domyślnej aplikacji
W niektórych sytuacjach musisz otworzyć jakiś dokument/plik przy użyciu domyślnej aplikacji z nim powiązanej, zamiast uruchamiać konkretny program. To zależy od działającego systemu operacyjnego. Lazarus zapewnia niezależną od platformy procedurę OpenDocument, która zajmie się tym za Ciebie. Twoja aplikacja będzie nadal działać, nie czekając na zamknięcie procesu tworzenia dokumentu.
uses LCLIntf;
...
OpenDocument('manual.pdf');
...
Otwieranie strony web w domyślnej przeglądarce internetowej
Aby to zrobić, po prostu przekaż wymagany adres URL. Przedrostek protokołu http:// wydaje się w pewnych okolicznościach opcjonalny. Ponadto przekazanie nazwy pliku wydaje się dawać takie same wyniki jak OpenDocument()
uses LCLIntf;
...
OpenURL('www.lazarus.freepascal.org/');
Zobacz także:
Lub możesz użyć TProcess w taki sposób:
uses Process;
procedure OpenWebPage(URL: string);
// Możliwe, że musisz podać swój adres URL w cudzysłowiu np. "www.lazarus.freepascal.org"
var
Browser, Params: string;
begin
FindDefaultBrowser(Browser, Params);
with TProcess.Create(nil) do
try
Executable := Browser;
Params:=Format(Params, [URL]);
Params:=copy(Params,2,length(Params)-2); // usuń znaki "", nowa wersja TProcess.Parameters robi to sama
Parameters.Add(Params);
Options := [poNoConsole];
Execute;
finally
Free;
end;
end;