Executing External Programs/pl: what changed
Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | Wykonywanie programów zewnętrznych | |
| 2 | 2 | ||
| 3 | 3 | ## Przegląd: Porównanie | |
| 4 | 4 | ||
| 5 | 5 | Istnieją różne sposoby dostępne w bibliotekach RTL, FCL i LCL dotyczące wykonywania zewnętrznego polecenia/procesu/programu. | |
| 6 | 6 | ||
| 7 | 7 | | Metoda | Biblioteka | Platformy | Pojedyncza linia | Cechy | | |
| 8 | 8 | |----|----|----|----|----| | |
| 9 | 9 | | [[Executing External Programs/pl|ExecuteProcess]] | RTL | Wieloplatformowy | Tak | Bardzo ograniczone, synchroniczne. | | |
| 10 | 10 | | [[Executing External Programs/pl|ShellExecute]] | WinAPI | Tylko MS Windows | Tak | Wiele. Może uruchamiać programy z podwyższonymi uprawnieniami administratora. | | |
| 11 | 11 | | [[Executing External Programs/pl|fpsystem, fpexecve]] | Unix | Tylko Unix | | | | |
| 12 | 12 | | [[Executing External Programs/pl|TProcess]] | FCL | Wieloplatformowy | Nie | Pełny. | | |
| 13 | 13 | | [[Executing External Programs/pl|RunCommand]] | FCL | Wieloplatformowy **Wymaga FPC 2.6.2+** | Tak | Obejmuje typowe użycie TProcess. | | |
| 14 | 14 | | [[Executing External Programs/pl|OpenDocument]] | LCL | Wieloplatformowy | Tak | Tylko otwarty dokument. Dokument otworzy się z aplikacją powiązaną z podanym typem dokumentu. | | |
| 15 | 15 | ||
| 16 | 16 | ## (Proces.)RunCommand | |
| 17 | 17 | ||
| 18 | 18 | - Odnośnik do [RunCommand](http://www.freepascal.org/docs-html/fcl/process/runcommand.html) | |
| 19 | 19 | - Odnośnik do [RunCommandInDir](http://www.freepascal.org/docs-html/fcl/process/runcommandindir.html) | |
| 20 | 20 | ||
| 21 | 21 | 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 [[Projects using Lazarus|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. | |
| 22 | 22 | ||
| 23 | 23 | Prosty przykład to: | |
| 24 | 24 | ||
| 25 | 25 | ``` pascal | |
| 26 | 26 | program project1; | |
| 27 | 27 | ||
| 28 | 28 | {$mode objfpc}{$H+} | |
| 29 | 29 | ||
| 30 | 30 | uses | |
| 31 | 31 | Process; | |
| 32 | 32 | ||
| 33 | 33 | var | |
| 34 | 34 | s : ansistring; | |
| 35 | 35 | ||
| 36 | 36 | begin | |
| 37 | 37 | ||
| 38 | 38 | if RunCommand('/bin/bash',['-c','echo $PATH'],s) then | |
| 39 | 39 | writeln(s); | |
| 40 | 40 | ||
| 41 | 41 | end. | |
| 42 | 42 | ``` | |
| 43 | 43 | ||
| 44 | 44 | 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: | |
| 45 | 45 | ||
| 46 | 46 | ``` pascal | |
| 47 | 47 | program project2; | |
| 48 | 48 | ||
| 49 | 49 | {$mode objfpc}{$H+} | |
| 50 | 50 | ||
| 51 | 51 | uses | |
| 52 | 52 | Process; | |
| 53 | 53 | ||
| 54 | 54 | var | |
| 55 | 55 | s : ansistring; | |
| 56 | 56 | ||
| 57 | 57 | begin | |
| 58 | 58 | ||
| 59 | 59 | if RunCommand('/bin/bash',['-c','alias'],s) then | |
| 60 | 60 | writeln(s); | |
| 61 | 61 | ||
| 62 | 62 | end. | |
| 63 | 63 | ``` | |
| 64 | 64 | ||
| 65 | 65 | Przeciążony wariant RunCommand zwraca kod wyjścia programu. RunCommandInDir uruchamia polecenie w innym katalogu (ustawia p.CurrentDirectory): | |
| 66 | 66 | ||
| 67 | 67 | ``` pascal | |
| 68 | 68 | function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer; | |
| 69 | 69 | function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean; | |
| 70 | 70 | function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean; | |
| 71 | 71 | ``` | |
| 72 | 72 | ||
| 73 | 73 | W **FPC 3.2.0+** RunCommand otrzymał dodatkowe warianty, które pozwalają nadpisać TProcessOptions i WindowOptions. | |
| 74 | 74 | ||
| 75 | 75 | ### Rozszerzenia RunCommand | |
| 76 | 76 | ||
| 77 | 77 | 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: | |
| 78 | 78 | ||
| 79 | 79 | ``` pascal | |
| 80 | 80 | ||
| 81 | 81 | {$mode delphi} | |
| 82 | 82 | uses classes, sysutils, process, dateutils; | |
| 83 | 83 | type | |
| 84 | 84 | { TProcessTimeout } | |
| 85 | 85 | TProcessTimeout = class(TProcess) | |
| 86 | 86 | public | |
| 87 | 87 | timeoutperiod: TTime; | |
| 88 | 88 | timedout : boolean; | |
| 89 | 89 | started : TDateTime; | |
| 90 | 90 | procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string); | |
| 91 | 91 | end; | |
| 92 | 92 | ||
| 93 | 93 | procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string); | |
| 94 | 94 | begin | |
| 95 | 95 | if status=RunCommandIdle then | |
| 96 | 96 | begin | |
| 97 | 97 | if (now-started)>timeoutperiod then | |
| 98 | 98 | begin | |
| 99 | 99 | timedout:=true; | |
| 100 | 100 | Terminate(255); | |
| 101 | 101 | exit; | |
| 102 | 102 | end; | |
| 103 | 103 | sleep(RunCommandSleepTime); | |
| 104 | 104 | end; | |
| 105 | 105 | end; | |
| 106 | 106 | ||
| 107 | 107 | function RunCommandTimeout(const exename:TProcessString;const commands:array of TProcessString;out outputstring:string; Options : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=60):boolean; | |
| 108 | 108 | Var | |
| 109 | 109 | p : TProcessTimeout; | |
| 110 | 110 | i, | |
| 111 | 111 | exitstatus : integer; | |
| 112 | 112 | ErrorString : String; | |
| 113 | 113 | begin | |
| 114 | 114 | p:=TProcessTimeout.create(nil); | |
| 115 | 115 | p.OnRunCommandEvent:=p.LocalnIdleSleep; | |
| 116 | 116 | p.timeoutperiod:=timeout/SecsPerDay; | |
| 117 | 117 | if Options<>[] then | |
| 118 | 118 | P.Options:=Options - [poRunSuspended,poWaitOnExit]; | |
| 119 | 119 | p.options:=p.options+[poRunIdle]; // potrzebne do uruchomienia zdarzenia RUNIDLE. Zobacz Zmiany Użytkownika dla wersji 3.2.0 | |
| 120 | 120 | ||
| 121 | 121 | P.ShowWindow:=SwOptions; | |
| 122 | 122 | p.Executable:=exename; | |
| 123 | 123 | if high(commands)>=0 then | |
| 124 | 124 | for i:=low(commands) to high(commands) do | |
| 125 | 125 | p.Parameters.add(commands[i]); | |
| 126 | 126 | p.timedout:=false; | |
| 127 | 127 | p.started:=now; | |
| 128 | 128 | try | |
| 129 | 129 | // główna pętla wariantów runcommand(), pierwotnie oparta na scenariuszu "dużego wyjścia" na wiki, ale stale rozwijana przez 5 lat. | |
| 130 | 130 | result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0; | |
| 131 | 131 | if p.timedout then | |
| 132 | 132 | result:=false; | |
| 133 | 133 | finally | |
| 134 | 134 | p.free; | |
| 135 | 135 | end; | |
| 136 | 136 | if exitstatus<>0 then result:=false; | |
| 137 | 137 | end; | |
| 138 | 138 | ||
| 139 | 139 | // przykładowe zastosowanie: | |
| 140 | 140 | ||
| 141 | 141 | var | |
| 142 | 142 | s : string; | |
| 143 | 143 | begin | |
| 144 | 144 | for s in FileList do | |
| 145 | 145 | begin | |
| 146 | 146 | if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then | |
| 147 | 147 | begin | |
| 148 | 148 | // nie udało się uruchomić lub upłynął limit czasu. np. movefile() do "wadliwego" katalogu. | |
| 149 | 149 | end | |
| 150 | 150 | else | |
| 151 | 151 | begin | |
| 152 | 152 | // ok, plik przeniesiony do "dobrego" katalogu. | |
| 153 | 153 | end; | |
| 154 | 154 | end; | |
| 155 | 155 | end; | |
| 156 | 156 | ``` | |
| 157 | 157 | ||
| 158 | 158 | ## SysUtils.ExecuteProcess | |
| 159 | 159 | ||
| 160 | 160 | (Wieloplatformowy)\ | |
| 161 | 161 | Pomimo wielu ograniczeń, najprostszym sposobem uruchomienia programu (modalnego, bez potoków lub jakiejkolwiek formy sterowania) jest po prostu użycie: | |
| 162 | 162 | ||
| 163 | 163 | ``` pascal | |
| 164 | 164 | SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []); | |
| 165 | 165 | ``` | |
| 166 | 166 | ||
| 167 | 167 | 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**. | |
| 168 | 168 | ||
| 169 | 169 | - Odniesienie do [ExecuteProcess](http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html) | |
| 170 | 170 | ||
| 171 | 171 | ## MS Windows: CreateProcess, ShellExecute i WinExec | |
| 172 | 172 | ||
| 173 | 173 | > **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**. | |
| 174 | 174 | ||
| 175 | 175 | > **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. | |
| 176 | 176 | ||
| 177 | 177 | **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). | |
| 178 | 178 | ||
| 179 | 179 | ``` pascal | |
| 180 | 180 | uses ..., ShellApi; | |
| 181 | 181 | ||
| 182 | 182 | // Proste jednowierszowe (ignorowanie zwracanych błędów): | |
| 183 | 183 | if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then; | |
| 184 | 184 | ||
| 185 | 185 | // Wykonaj plik wsadowy: | |
| 186 | 186 | if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then; | |
| 187 | 187 | ||
| 188 | 188 | // Otwórz okno poleceń w danym folderze: | |
| 189 | 189 | if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then; | |
| 190 | 190 | ||
| 191 | 191 | // Otwórz adres URL strony internetowej w domyślnej przeglądarce za pomocą polecenia „start” (poprzez ukryte okno cmd): | |
| 192 | 192 | if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then; | |
| 193 | 193 | ||
| 194 | 194 | // lub przydatna procedura: | |
| 195 | 195 | procedure RunShellExecute(const prog,params:string); | |
| 196 | 196 | begin | |
| 197 | 197 | // parametry: ( Uchwyt, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' nie zawsze jest potrzebny | |
| 198 | 198 | // path+prog, parametry, folder roboczy, | |
| 199 | 199 | // okno command 0=ukryte / 1=SW_SHOWNORMAL / 3=maks / 7=min) // dla stałych SW_ : uses ... Windows ... | |
| 200 | 200 | if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //sukces | |
| 201 | 201 | // zwracane wartości 0..32 są błędami | |
| 202 | 202 | end; | |
| 203 | 203 | ``` | |
| 204 | 204 | ||
| 205 | 205 | Istnieje również ShellExecuteExW jako wersja WideChar, a ShellExecuteExA to wersja AnsiChar. | |
| 206 | 206 | ||
| 207 | 207 | Opcja fMask może również używać SEE_MASK_DOENVSUBST lub SEE_MASK_FLAG_NO_UI lub SEE_MASK_NOCLOSEPROCESS itp. | |
| 208 | 208 | ||
| 209 | 209 | 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](https://wiki.lazarus.freepascal.org/Executing_External_Programs/pl#Zobacz_także) na dole tej strony). | |
| 210 | 210 | ||
| 211 | 211 | ### Używanie ShellExecuteEx do podnoszenia uprawnień administratora | |
| 212 | 212 | ||
| 213 | 213 | Jeśli potrzebujesz uruchomić zewnętrzny program z podwyższonymi uprawnieniami administratora, możesz użyć metody **runas** z alternatywną funkcją ShellExecuteEx: | |
| 214 | 214 | ||
| 215 | 215 | ``` pascal | |
| 216 | 216 | uses ShellApi, ...; | |
| 217 | 217 | ||
| 218 | 218 | function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean; | |
| 219 | 219 | var | |
| 220 | 220 | sei: TShellExecuteInfoA; | |
| 221 | 221 | begin | |
| 222 | 222 | FillChar(sei, SizeOf(sei), 0); | |
| 223 | 223 | sei.cbSize := SizeOf(sei); | |
| 224 | 224 | sei.Wnd := Handle; | |
| 225 | 225 | sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI; | |
| 226 | 226 | sei.lpVerb := 'runas'; | |
| 227 | 227 | sei.lpFile := PAnsiChar(Path); | |
| 228 | 228 | sei.lpParameters := PAnsiChar(Params); | |
| 229 | 229 | sei.nShow := SW_SHOWNORMAL; | |
| 230 | 230 | Result := ShellExecuteExA(@sei); | |
| 231 | 231 | end; | |
| 232 | 232 | ||
| 233 | 233 | procedure TFormMain.RunAddOrRemoveApplication; | |
| 234 | 234 | begin | |
| 235 | 235 | // Przykład, który używa podwyższonego uprawnienia dla rundll, aby otworzyć Panel sterowania do programów i funkcji | |
| 236 | 236 | RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', ''); | |
| 237 | 237 | end; | |
| 238 | 238 | ``` | |
| 239 | 239 | ||
| 240 | 240 | ## Unix fpsystem, fpexecve i shell | |
| 241 | 241 | ||
| 242 | 242 | Te funkcje są zależne od platformy. | |
| 243 | 243 | ||
| 244 | 244 | - Odniesienie do [fpsystem](http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html) | |
| 245 | 245 | - Odniesienie do [fpexecve](http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html) | |
| 246 | 246 | ||
| 247 | 247 | 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. | |
| 248 | 248 | ||
| 249 | 249 | ## TProcess | |
| 250 | 250 | ||
| 251 | 251 | Możesz użyć TProcess do uruchamiania zewnętrznych programów. Niektóre z korzyści płynących z używania TProcess to: | |
| 252 | 252 | ||
| 253 | 253 | - Jest niezależny od platformy. | |
| 254 | 254 | - Potrafi czytać ze standardowego wyjścia i zapisywać na standardowe wejście. | |
| 255 | 255 | - Możliwe jest oczekiwanie na zakończenie polecenia lub pozwolenie na jego działanie podczas działania programu. | |
| 256 | 256 | ||
| 257 | 257 | Ważne informacje: | |
| 258 | 258 | ||
| 259 | 259 | - 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. | |
| 260 | 260 | - 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. | |
| 261 | 261 | - W systemie Windows, jeśli polecenie znajduje się w ścieżce, nie musisz określać pełnej ścieżki. | |
| 262 | 262 | - Odnośnik do [[doc:fcl/process/tprocess.html|TProcess]] | |
| 263 | 263 | ||
| 264 | 264 | ### Najprostszy przykład | |
| 265 | 265 | ||
| 266 | 266 | Wiele typowych przypadków zostało przygotowanych w funkcjach [[Executing External Programs/pl|RunCommand]]. Zanim zaczniesz kopiować i wklejać poniższe przykłady, najpierw je sprawdź. | |
| 267 | 267 | ||
| 268 | 268 | ### Prosty przykład | |
| 269 | 269 | ||
| 270 | 270 | 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]] lub, lepiej, [[Executing External Programs/pl|RunCommand]]**) pokazuje tylko, jak uruchomić zewnętrzny program, nic więcej: | |
| 271 | 271 | ||
| 272 | 272 | ``` pascal | |
| 273 | 273 | // Jest to program demonstracyjny, który | |
| 274 | 274 | // pokazuje, jak uruchomić zewnętrzny program. | |
| 275 | 275 | program launchprogram; | |
| 276 | 276 | ||
| 277 | 277 | // Tutaj dołączamy moduły zawierające przydatne | |
| 278 | 278 | // funkcje i procedury, których będziemy potrzebować. | |
| 279 | 279 | uses | |
| 280 | 280 | Classes, SysUtils, Process; | |
| 281 | 281 | ||
| 282 | 282 | // Definiuje zmienną "AProcess" jako zmienną typu "TProcess" | |
| 283 | 283 | var | |
| 284 | 284 | AProcess: TProcess; | |
| 285 | 285 | ||
| 286 | 286 | // Tu zaczyna działać nasz program | |
| 287 | 287 | begin | |
| 288 | 288 | // Teraz utworzymy obiekt TProcess i przypiszemy go | |
| 289 | 289 | // do zmiennej AProcess. | |
| 290 | 290 | AProcess := TProcess.Create(nil); | |
| 291 | 291 | ||
| 292 | 292 | // Powiedz nowemu AProcess, jakie jest polecenie do wykonania. | |
| 293 | 293 | // Użyjmy kompilatora Free Pascal (dokładniej wersji x64) | |
| 294 | 294 | // Lecz jeśli używasz kompilatora 32-bit zmień ppcx64 na ppc386 | |
| 295 | 295 | AProcess.Executable:= 'ppcx64'; | |
| 296 | 296 | ||
| 297 | 297 | // Przekaż opcję -h razem z ppcx64, więc faktycznie wykonywane | |
| 298 | 298 | // jest polecenie 'ppcx64 -h': | |
| 299 | 299 | AProcess.Parameters.Add('-h'); | |
| 300 | 300 | ||
| 301 | 301 | // Zdefiniujemy opcję działającą, w czasie wykonywania programu. | |
| 302 | 302 | // Ta opcja sprawi, że nasz program nie będzie kontynuowany, | |
| 303 | 303 | // dopóki program zewnętrzy, który uruchomimy, nie przestanie działać. | |
| 304 | 304 | // /------------\ | |
| 305 | 305 | AProcess.Options := AProcess.Options + [poWaitOnExit]; | |
| 306 | 306 | ||
| 307 | 307 | // Teraz pozwól AProcess uruchomić program zewnętrzny | |
| 308 | 308 | AProcess.Execute; | |
| 309 | 309 | ||
| 310 | 310 | // Poniższy kod nie wykona się, dopóki ppcx64 nie przestanie działać. | |
| 311 | 311 | AProcess.Free; | |
| 312 | 312 | end. | |
| 313 | 313 | ``` | |
| 314 | 314 | ||
| 315 | 315 | Otóż to! Właśnie nauczyłeś się uruchamiać zewnętrzny program z wnętrza własnego programu. | |
| 316 | 316 | ||
| 317 | 317 | ### Ulepszony przykład (ale jeszcze niepoprawny) | |
| 318 | 318 | ||
| 319 | 319 | Wszystko fajnie, ale jak odczytać dane wyjściowe uruchomionego programu? | |
| 320 | 320 | ||
| 321 | 321 | 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]].** | |
| 322 | 322 | ||
| 323 | 323 | ``` pascal | |
| 324 | 324 | // To jest | |
| 325 | 325 | // WADLIWY | |
| 326 | 326 | // program demonstracyjny, który pokazuje, | |
| 327 | 327 | // jak uruchomić zewnętrzny program | |
| 328 | 328 | // i odczytać z jego dane wyjściowe. | |
| 329 | 329 | program launchprogram; | |
| 330 | 330 | ||
| 331 | 331 | // Tutaj dołączamy moduły zawierające przydatne | |
| 332 | 332 | // funkcje i procedury, których będziemy potrzebować. | |
| 333 | 333 | uses | |
| 334 | 334 | Classes, SysUtils, Process; | |
| 335 | 335 | ||
| 336 | 336 | // Definiuje zmienną "AProcess" jako zmienną typu "TProcess" | |
| 337 | 337 | // Teraz także dodajemy TStringList do przechowywania danych | |
| 338 | 338 | // odczytanych z wyjścia programu. | |
| 339 | 339 | var | |
| 340 | 340 | AProcess: TProcess; | |
| 341 | 341 | AStringList: TStringList; | |
| 342 | 342 | ||
| 343 | 343 | // Tu zaczyna działać nasz program | |
| 344 | 344 | begin | |
| 345 | 345 | // Teraz utworzymy obiekt TProcess i przypiszemy go | |
| 346 | 346 | // do zmiennej AProcess. | |
| 347 | 347 | AProcess := TProcess.Create(nil); | |
| 348 | 348 | ||
| 349 | 349 | // Powiedz nowemu AProcess, jakie jest polecenie do wykonania. | |
| 350 | 350 | AProcess.Executable := '/usr/bin/ppcx64'; | |
| 351 | 351 | AProcess.Parameters.Add('-h'); | |
| 352 | 352 | ||
| 353 | 353 | // Zdefiniujemy opcję działającą, w czasie wykonywania programu. | |
| 354 | 354 | // Ta opcja sprawi, że nasz program nie będzie kontynuowany, | |
| 355 | 355 | // dopóki program zewnętrzy, który uruchomimy, nie przestanie działać. | |
| 356 | 356 | // Dodatkowo teraz powiemy mu, że chcemy odczytać dane wyjściowe. | |
| 357 | 357 | // /------------|-----------\ | |
| 358 | 358 | AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes]; | |
| 359 | 359 | ||
| 360 | 360 | // Teraz, gdy AProcess wie, jaki jest wiersz poleceń, można go uruchomić. | |
| 361 | 361 | AProcess.Execute; | |
| 362 | 362 | ||
| 363 | 363 | // Po zakończeniu AProcess zostanie wykonana reszta programu. | |
| 364 | 364 | ||
| 365 | 365 | // Teraz odczytaj wynik działania programu, za pomocą TStringList. | |
| 366 | 366 | AStringList := TStringList.Create; | |
| 367 | 367 | AStringList.LoadFromStream(AProcess.Output); | |
| 368 | 368 | ||
| 369 | 369 | // Zapisz dane wyjściowe do pliku i zwolnij obiekt AStringList. | |
| 370 | 370 | AStringList.SaveToFile('output.txt'); | |
| 371 | 371 | AStringList.Free; | |
| 372 | 372 | ||
| 373 | 373 | // Teraz, gdy dane wyjściowe z procesu są przetworzone, także można je zwolnić. | |
| 374 | 374 | AProcess.Free; | |
| 375 | 375 | end. | |
| 376 | 376 | ``` | |
| 377 | 377 | ||
| 378 | 378 | ### Czytanie dużych ilości danych wyjściowych | |
| 379 | 379 | ||
| 380 | 380 | W poprzednim przykładzie czekaliśmy, aż program się zakończy. Następnie czytaliśmy, co program zapisał na swoim wyjściu. | |
| 381 | 381 | ||
| 382 | 382 | 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. | |
| 383 | 383 | ||
| 384 | 384 | Ale program wywołujący nie czyta z niego, dopóki wywoływany program się nie zakończy. Następuje impas. | |
| 385 | 385 | ||
| 386 | 386 | 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. | |
| 387 | 387 | ||
| 388 | 388 | 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. | |
| 389 | 389 | ||
| 390 | 390 | ``` pascal | |
| 391 | 391 | program LargeOutputDemo; | |
| 392 | 392 | ||
| 393 | 393 | {$mode objfpc}{$H+} | |
| 394 | 394 | ||
| 395 | 395 | uses | |
| 396 | 396 | Classes, SysUtils, Process; // Process to moduł, który zawiera TProcess | |
| 397 | 397 | ||
| 398 | 398 | const | |
| 399 | 399 | BUF_SIZE = 2048; // Rozmiar bufora do odczytu danych wyjściowych w porcjach | |
| 400 | 400 | ||
| 401 | 401 | var | |
| 402 | 402 | AProcess : TProcess; | |
| 403 | 403 | OutputStream : TStream; | |
| 404 | 404 | BytesRead : longint; | |
| 405 | 405 | Buffer : array[1..BUF_SIZE] of byte; | |
| 406 | 406 | ||
| 407 | 407 | begin | |
| 408 | 408 | // Skonfiguruj proces; jako przykład używane jest rekurencyjne przeszukiwanie | |
| 409 | 409 | // katalogów, ponieważ zwykle skutkuje to dużą ilością danych. | |
| 410 | 410 | AProcess := TProcess.Create(nil); | |
| 411 | 411 | ||
| 412 | 412 | // Polecenia dla Windows i *nix są różne, stąd dyrektywy $IFDEF | |
| 413 | 413 | {$IFDEF Windows} | |
| 414 | 414 | // W systemie Windows polecenie dir nie może być użyte bezpośrednio, ponieważ jest to | |
| 415 | 415 | // wbudowane polecenie powłoki. Dlatego potrzebny jest cmd.exe i dodatkowe parametry. | |
| 416 | 416 | AProcess.Executable := 'c:\windows\system32\cmd.exe'; | |
| 417 | 417 | AProcess.Parameters.Add('/c'); | |
| 418 | 418 | AProcess.Parameters.Add('dir /s c:\windows'); | |
| 419 | 419 | {$ENDIF Windows} | |
| 420 | 420 | ||
| 421 | 421 | {$IFDEF Unix} | |
| 422 | 422 | AProcess.Executable := '/bin/ls'; | |
| 423 | 423 | ||
| 424 | 424 | {$IFDEF Darwin} | |
| 425 | 425 | AProcess.Parameters.Add('-recursive'); | |
| 426 | 426 | AProcess.Parameters.Add('-all'); | |
| 427 | 427 | {$ENDIF Darwin} | |
| 428 | 428 | ||
| 429 | 429 | {$IFDEF Linux} | |
| 430 | 430 | AProcess.Parameters.Add('--recursive'); | |
| 431 | 431 | AProcess.Parameters.Add('--all'); | |
| 432 | 432 | {$ENDIF Linux} | |
| 433 | 433 | ||
| 434 | 434 | {$IFDEF FreeBSD} | |
| 435 | 435 | AProcess.Parameters.Add('-R'); | |
| 436 | 436 | AProcess.Parameters.Add('-a'); | |
| 437 | 437 | {$ENDIF FreeBSD} | |
| 438 | 438 | ||
| 439 | 439 | AProcess.Parameters.Add('-l'); | |
| 440 | 440 | {$ENDIF Unix} | |
| 441 | 441 | ||
| 442 | 442 | // Aby można było przechwycić dane wyjściowe, należy użyć opcji procesu poUsePipes. | |
| 443 | 443 | // Nie można użyć opcji procesu poWaitOnExit, ponieważ zablokuje to program główny, | |
| 444 | 444 | // uniemożliwiając mu odczytanie danych wyjściowych procesu zewnętrznego. | |
| 445 | 445 | AProcess.Options := [poUsePipes]; | |
| 446 | 446 | ||
| 447 | 447 | // Uruchom proces (uruchom polecenie dir dla Windows lub ls dla Linux/Unix) | |
| 448 | 448 | AProcess.Execute; | |
| 449 | 449 | ||
| 450 | 450 | // Utwórz obiekt strumienia, w którym będą przechowywane wygenerowane dane wyjściowe. | |
| 451 | 451 | // Może to być również strumień plikowy do bezpośredniego zapisywania danych wyjściowych na dysku. | |
| 452 | 452 | OutputStream := TMemoryStream.Create; | |
| 453 | 453 | ||
| 454 | 454 | // Wszystkie wygenerowane dane wyjściowe z AProcess są odczytywane w pętli, | |
| 455 | 455 | // dopóki nie będzie dostępnych więcej danych | |
| 456 | 456 | repeat | |
| 457 | 457 | // Pobierz nowe dane z procesu do maksymalnego rozmiaru buforu, który został przydzielony. | |
| 458 | 458 | // Zauważ, że wszystkie wywołania Read(...) będą blokowane z wyjątkiem ostatniego, które zwraca 0 (zero). | |
| 459 | 459 | BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE); | |
| 460 | 460 | ||
| 461 | 461 | // Bajty, które zostały odczytane, dodaj do strumienia w celu późniejszego wykorzystania | |
| 462 | 462 | OutputStream.Write(Buffer, BytesRead) | |
| 463 | 463 | ||
| 464 | 464 | until BytesRead = 0; // Zakończ pętlę, jeśli nie ma więcej danych | |
| 465 | 465 | ||
| 466 | 466 | // Proces się zakończył, więc można go posprzątać | |
| 467 | 467 | AProcess.Free; | |
| 468 | 468 | ||
| 469 | 469 | // Teraz, gdy wszystkie dane zostały odczytane, można z nich korzystać; | |
| 470 | 470 | // na przykład po to, by zapisać je do pliku na dysku | |
| 471 | 471 | with TFileStream.Create('output.txt', fmCreate) do | |
| 472 | 472 | begin | |
| 473 | 473 | OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku | |
| 474 | 474 | CopyFrom(OutputStream, OutputStream.Size); | |
| 475 | 475 | Free | |
| 476 | 476 | end; | |
| 477 | 477 | ||
| 478 | 478 | // Lub dane mogą być wyświetlane na ekranie | |
| 479 | 479 | with TStringList.Create do | |
| 480 | 480 | begin | |
| 481 | 481 | OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku | |
| 482 | 482 | LoadFromStream(OutputStream); | |
| 483 | 483 | writeln(Text); | |
| 484 | 484 | writeln('--- Liczba linii = ', Count, '----'); | |
| 485 | 485 | Free | |
| 486 | 486 | end; | |
| 487 | 487 | ||
| 488 | 488 | // Sprzątanie | |
| 489 | 489 | OutputStream.Free; | |
| 490 | 490 | end. | |
| 491 | 491 | ``` | |
| 492 | 492 | ||
| 493 | 493 | Zauważ, że powyższe można również osiągnąć za pomocą RunCommand: | |
| 494 | 494 | ||
| 495 | 495 | ``` pascal | |
| 496 | 496 | var s: string; | |
| 497 | 497 | ... | |
| 498 | 498 | RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s); | |
| 499 | 499 | ``` | |
| 500 | 500 | ||
| 501 | 501 | ### Korzystanie z wejścia i wyjścia TProcess | |
| 502 | 502 | ||
| 503 | 503 | Zobacz przykładowe demo procesu w [Lazarus-CCR SVN](https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/examples/process). | |
| 504 | 504 | ||
| 505 | 505 | ### Wskazówki dotyczące korzystania z TProcess | |
| 506 | 506 | ||
| 507 | 507 | Podczas tworzenia programu wieloplatformowego nazwę pliku wykonywalnego specyficzną dla systemu operacyjnego można ustawić za pomocą dyrektyw „{\$IFDEF}” i „{\$ENDIF}”. Przykład: | |
| 508 | 508 | ||
| 509 | 509 | ``` pascal | |
| 510 | 510 | {...} | |
| 511 | 511 | AProcess := TProcess.Create(nil) | |
| 512 | 512 | ||
| 513 | 513 | {$IFDEF WIN32} | |
| 514 | 514 | AProcess.Executable := 'calc.exe'; | |
| 515 | 515 | {$ENDIF} | |
| 516 | 516 | ||
| 517 | 517 | {$IFDEF LINUX} | |
| 518 | 518 | AProcess.Executable := FindDefaultExecutablePath('kcalc'); | |
| 519 | 519 | {$ENDIF} | |
| 520 | 520 | ||
| 521 | 521 | AProcess.Execute; | |
| 522 | 522 | {...} | |
| 523 | 523 | ``` | |
| 524 | 524 | ||
| 525 | 525 | ### macOS pokazuje pakiet aplikacji na pierwszym planie | |
| 526 | 526 | ||
| 527 | 527 | Możesz uruchomić **pakiet aplikacji** przez TProcess, uruchamiając plik wykonywalny w pakiecie. Na przykład: | |
| 528 | 528 | ||
| 529 | 529 | ``` pascal | |
| 530 | 530 | AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal'; | |
| 531 | 531 | ``` | |
| 532 | 532 | ||
| 533 | 533 | 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**: | |
| 534 | 534 | ||
| 535 | 535 | ``` pascal | |
| 536 | 536 | AProcess.Executable:='/usr/bin/open'; | |
| 537 | 537 | AProcess.Parameters.Add('-n'); | |
| 538 | 538 | AProcess.Parameters.Add('-a'); // opcjonalny: określa aplikację do użycia; przeszukuje tylko katalogi aplikacji | |
| 539 | 539 | AProcess.Parameters.Add('-W'); // opcjonalnie: open czeka, aż aplikacje, które otwiera (lub były już otwarte) zostaną zamknięte | |
| 540 | 540 | AProcess.Parameters.Add('Pages.app'); // dołączenie .app jest opcjonalne | |
| 541 | 541 | ``` | |
| 542 | 542 | ||
| 543 | 543 | 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: | |
| 544 | 544 | ||
| 545 | 545 | ``` pascal | |
| 546 | 546 | AProcess.Parameters.Add('--args'); | |
| 547 | 547 | AProcess.Parameters.Add('argument1'); | |
| 548 | 548 | AProcess.Parameters.Add('argument2'); | |
| 549 | 549 | ``` | |
| 550 | 550 | ||
| 551 | 551 | Zobacz także: [[macOS Open Sesame|macOS polecenie open]]. | |
| 552 | 552 | ||
| 553 | 553 | ### Uruchom oddzielny program | |
| 554 | 554 | ||
| 555 | 555 | 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ć: | |
| 556 | 556 | ||
| 557 | 557 | ``` pascal | |
| 558 | 558 | var | |
| 559 | 559 | Process: TProcess; | |
| 560 | 560 | I: Integer; | |
| 561 | 561 | begin | |
| 562 | 562 | Process := TProcess.Create(nil); | |
| 563 | 563 | try | |
| 564 | 564 | Process.InheritHandles := False; | |
| 565 | 565 | Process.Options := []; | |
| 566 | 566 | Process.ShowWindow := swoShow; | |
| 567 | 567 | ||
| 568 | 568 | // Skopiuj domyślne zmienne środowiskowe, w tym zmienną DISPLAY, aby aplikacja GUI działała | |
| 569 | 569 | for I := 1 to GetEnvironmentVariableCount do | |
| 570 | 570 | Process.Environment.Add(GetEnvironmentString(I)); | |
| 571 | 571 | ||
| 572 | 572 | Process.Executable := '/usr/bin/gedit'; | |
| 573 | 573 | Process.Execute; | |
| 574 | 574 | finally | |
| 575 | 575 | Process.Free; | |
| 576 | 576 | end; | |
| 577 | 577 | end; | |
| 578 | 578 | ``` | |
| 579 | 579 | ||
| 580 | 580 | ### Przykład „rozmowy” z procesem aspell | |
| 581 | 581 | ||
| 582 | 582 | Wewnątrz kodu źródłowego [pasdoc](https://github.com/pasdoc/pasdoc/wiki) można znaleźć dwa moduły, które sprawdzają pisownię poprzez „rozmawianie” przez potoki z uruchomionym procesem aspell: | |
| 583 | 583 | ||
| 584 | 584 | - [Moduł PasDoc_ProcessLineTalk.pas](https://github.com/pasdoc/pasdoc/blob/master/source/component/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. | |
| 585 | 585 | ||
| 586 | 586 | - [Moduł PasDoc_Aspell.pas](https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas) implementuje klasę TAspellProcess, która sprawdza pisownię za pomocą bazowej instancji TProcessLineTalk do wykonania aspell i komunikacji z uruchomionym procesem aspell. | |
| 587 | 587 | ||
| 588 | 588 | 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. | |
| 589 | 589 | ||
| 590 | 590 | ### Zastępowanie operatorów powłoki, takich jak „\| \< \>” | |
| 591 | 591 | ||
| 592 | 592 | Czasami chcesz uruchomić bardziej skomplikowane polecenie, które przesyła dane do innego polecenia lub do pliku. Coś podobnego do | |
| 593 | 593 | ||
| 594 | 594 | ``` pascal | |
| 595 | 595 | ShellExecute('firstcommand.exe | secondcommand.exe'); | |
| 596 | 596 | ``` | |
| 597 | 597 | ||
| 598 | 598 | lub | |
| 599 | 599 | ||
| 600 | 600 | ``` pascal | |
| 601 | 601 | ShellExecute('dir > output.txt'); | |
| 602 | 602 | ``` | |
| 603 | 603 | ||
| 604 | 604 | Wykonanie tego za pomocą TProcess nie zadziała. tj: | |
| 605 | 605 | ||
| 606 | 606 | ``` pascal | |
| 607 | 607 | // to nie zadziała! | |
| 608 | 608 | Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | |
| 609 | 609 | Process.Execute; | |
| 610 | 610 | ``` | |
| 611 | 611 | ||
| 612 | 612 | #### Dlaczego używanie specjalnych operatorów do przekierowywania wyjścia nie działa? | |
| 613 | 613 | ||
| 614 | 614 | 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|następną sekcję]]. | |
| 615 | 615 | ||
| 616 | 616 | ### Jak przekierować wyjście za pomocą TProcess | |
| 617 | 617 | ||
| 618 | 618 | Możesz przekierować dane wyjściowe polecenia do innego polecenia, używając osobnej instancji TProcess dla **każdego** polecenia. | |
| 619 | 619 | ||
| 620 | 620 | 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]]. | |
| 621 | 621 | ||
| 622 | 622 | 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. | |
| 623 | 623 | ||
| 624 | 624 | ``` pascal | |
| 625 | 625 | program Project1; | |
| 626 | 626 | ||
| 627 | 627 | uses | |
| 628 | 628 | Classes, sysutils, process; | |
| 629 | 629 | ||
| 630 | 630 | var | |
| 631 | 631 | FirstProcess, | |
| 632 | 632 | SecondProcess: TProcess; | |
| 633 | 633 | Buffer: array[0..127] of char; | |
| 634 | 634 | ReadCount: Integer; | |
| 635 | 635 | ReadSize: Integer; | |
| 636 | 636 | begin | |
| 637 | 637 | FirstProcess := TProcess.Create(nil); | |
| 638 | 638 | SecondProcess := TProcess.Create(nil); | |
| 639 | 639 | ||
| 640 | 640 | FirstProcess.Options := [poUsePipes]; | |
| 641 | 641 | FirstProcess.Executable := 'pwd'; | |
| 642 | 642 | ||
| 643 | 643 | SecondProcess.Options := [poUsePipes,poStderrToOutPut]; | |
| 644 | 644 | SecondProcess.Executable := 'grep'; | |
| 645 | 645 | SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); | |
| 646 | 646 | // byłoby to to samo co "pwd | grep / -" | |
| 647 | 647 | ||
| 648 | 648 | FirstProcess.Execute; | |
| 649 | 649 | SecondProcess.Execute; | |
| 650 | 650 | ||
| 651 | 651 | while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do | |
| 652 | 652 | begin | |
| 653 | 653 | if FirstProcess.Output.NumBytesAvailable > 0 then | |
| 654 | 654 | begin | |
| 655 | 655 | // upewnij się, że nie czytamy więcej danych niż przydzieliliśmy w buforze | |
| 656 | 656 | ReadSize := FirstProcess.Output.NumBytesAvailable; | |
| 657 | 657 | if ReadSize > SizeOf(Buffer) then | |
| 658 | 658 | ReadSize := SizeOf(Buffer); | |
| 659 | 659 | // teraz wczytaj dane wyjściowe do bufora | |
| 660 | 660 | ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | |
| 661 | 661 | // i zapisz bufor do drugiego procesu | |
| 662 | 662 | SecondProcess.Input.Write(Buffer[0], ReadCount); | |
| 663 | 663 | ||
| 664 | 664 | // jeśli SecondProcess zapisuje dużo danych do swojego wyjścia, | |
| 665 | 665 | // powinniśmy odczytać te dane tutaj, aby zapobiec zakleszczeniu, | |
| 666 | 666 | // patrz poprzedni przykład "Czytanie dużych ilości danych wyjściowych" | |
| 667 | 667 | end; | |
| 668 | 668 | end; | |
| 669 | 669 | // Zamknij dane wejściowe w SecondProcess, | |
| 670 | 670 | // aby zakończyć przetwarzanie swoich danych | |
| 671 | 671 | SecondProcess.CloseInput; | |
| 672 | 672 | ||
| 673 | 673 | // i poczekaj, aż się zakończy, lecz uważaj, jakie polecenie uruchamiasz, | |
| 674 | 674 | // ponieważ może się ono nie zakończyć, gdy jego dane wejściowe są zamknięte, | |
| 675 | 675 | // to następująca linia może zapętlić się w nieskończoność | |
| 676 | 676 | while SecondProcess.Running do | |
| 677 | 677 | Sleep(1); | |
| 678 | 678 | // Gotowe! Reszta programu jest tylko po to, aby przykład był bardziej 'użyteczny' | |
| 679 | 679 | ||
| 680 | 680 | // użyjemy ponownie Buffer, aby wyprowadzić wyjście SecondProcess do *tego* programu stdout | |
| 681 | 681 | WriteLn('Rozpoczęcie wyjścia grep:'); | |
| 682 | 682 | ReadSize := SecondProcess.Output.NumBytesAvailable; | |
| 683 | 683 | if ReadSize > SizeOf(Buffer) then | |
| 684 | 684 | ReadSize := SizeOf(Buffer); | |
| 685 | 685 | if ReadSize > 0 then | |
| 686 | 686 | begin | |
| 687 | 687 | ReadCount := SecondProcess.Output.Read(Buffer, ReadSize); | |
| 688 | 688 | WriteLn(Copy(Buffer,0, ReadCount)); | |
| 689 | 689 | end | |
| 690 | 690 | else | |
| 691 | 691 | WriteLn('grep nie znalazł tego, czego szukaliśmy. ', SecondProcess.ExitStatus); | |
| 692 | 692 | WriteLn('Zakończenie wyjścia grep:'); | |
| 693 | 693 | ||
| 694 | 694 | // zwolnij obiekty naszych procesów | |
| 695 | 695 | FirstProcess.Free; | |
| 696 | 696 | SecondProcess.Free; | |
| 697 | 697 | end. | |
| 698 | 698 | ``` | |
| 699 | 699 | ||
| 700 | 700 | W ten sposób możesz przekierować dane wyjściowe z jednego programu do drugiego. | |
| 701 | 701 | ||
| 702 | 702 | #### Uwagi | |
| 703 | 703 | ||
| 704 | 704 | 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: | |
| 705 | 705 | ||
| 706 | 706 | ``` pascal | |
| 707 | 707 | Process.Commandline := 'sh -c "pwd | grep / -"'; | |
| 708 | 708 | ``` | |
| 709 | 709 | ||
| 710 | 710 | 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. | |
| 711 | 711 | ||
| 712 | 712 | ### Przekierowywanie wejścia i wyjścia oraz działanie z rootem | |
| 713 | 713 | ||
| 714 | 714 | 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*. | |
| 715 | 715 | ||
| 716 | 716 | 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](http://lazarus.freepascal.org/index.php/topic,14479.0.html)). Ten przykład uruchamia polecenie `ls` w katalogu `/root`, ale oczywiście można go dostosować. | |
| 717 | 717 | ||
| 718 | 718 | **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.](http://lazarus.freepascal.org/index.php/topic,14479.0.html) | |
| 719 | 719 | ||
| 720 | 720 | 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. | |
| 721 | 721 | ||
| 722 | 722 | ``` pascal | |
| 723 | 723 | program rootls; | |
| 724 | 724 | ||
| 725 | 725 | { Demonstracja użycia TProcess, przekierowania stdout/stderr do naszego stdout/stderr, | |
| 726 | 726 | wywoływania sudo w systemach FreeBSD/Linux/macOS i dostarczania danych wejściowych na stdin} | |
| 727 | 727 | {$mode objfpc}{$H+} | |
| 728 | 728 | ||
| 729 | 729 | uses | |
| 730 | 730 | Classes, | |
| 731 | 731 | Math, {for min} | |
| 732 | 732 | Process; | |
| 733 | 733 | ||
| 734 | 734 | procedure RunsLsRoot; | |
| 735 | 735 | var | |
| 736 | 736 | Proc: TProcess; | |
| 737 | 737 | CharBuffer: array [0..511] of char; | |
| 738 | 738 | ReadCount: integer; | |
| 739 | 739 | ExitCode: integer; | |
| 740 | 740 | SudoPassword: string; | |
| 741 | 741 | begin | |
| 742 | 742 | WriteLn('Please enter the sudo password:'); | |
| 743 | 743 | Readln(SudoPassword); | |
| 744 | 744 | ExitCode := -1; //Zacznij od porażki, zobaczmy później, czy to zadziała | |
| 745 | 745 | Proc := TProcess.Create(nil); //Utwórz nowy proces | |
| 746 | 746 | try | |
| 747 | 747 | Proc.Options := [poUsePipes, poStderrToOutPut]; //Użyj potoków, aby przekierować stdin,stdout,stderr | |
| 748 | 748 | Proc.CommandLine := 'sudo -S ls /root'; //Uruchom ls /root jako root za pomocą sudo | |
| 749 | 749 | // -S powoduje, że sudo odczytuje hasło z stdin. | |
| 750 | 750 | Proc.Execute; //Uruchom to. sudo prawdopodobnie poprosi teraz o hasło | |
| 751 | 751 | ||
| 752 | 752 | // wpisz hasło do stdin programu sudo: | |
| 753 | 753 | SudoPassword := SudoPassword + LineEnding; | |
| 754 | 754 | Proc.Input.Write(SudoPassword[1], Length(SudoPassword)); | |
| 755 | 755 | SudoPassword := '%*'; // krótki string, mam nadzieję, że trochę pomiesza pamięć; uwaga: używanie PChars jest bardziej niezawodne | |
| 756 | 756 | SudoPassword := ''; // i sprawić, by program był nieco bezpieczniejszy przed węszeniem?!? | |
| 757 | 757 | ||
| 758 | 758 | // główna pętla do odczytu wyjścia z stdout i stderr dla sudo | |
| 759 | 759 | while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or | |
| 760 | 760 | (Proc.Stderr.NumBytesAvailable > 0) do | |
| 761 | 761 | begin | |
| 762 | 762 | // odczytaj stdout i zapisz do naszego stdout | |
| 763 | 763 | while Proc.Output.NumBytesAvailable > 0 do | |
| 764 | 764 | begin | |
| 765 | 765 | ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Czytaj do bufora, nie więcej | |
| 766 | 766 | Proc.Output.Read(CharBuffer, ReadCount); | |
| 767 | 767 | Write(StdOut, Copy(CharBuffer, 0, ReadCount)); | |
| 768 | 768 | end; | |
| 769 | 769 | // odczytaj stderr i zapisz do naszego stderr | |
| 770 | 770 | while Proc.Stderr.NumBytesAvailable > 0 do | |
| 771 | 771 | begin | |
| 772 | 772 | ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Czytaj do bufora, nie więcej | |
| 773 | 773 | Proc.Stderr.Read(CharBuffer, ReadCount); | |
| 774 | 774 | Write(StdErr, Copy(CharBuffer, 0, ReadCount)); | |
| 775 | 775 | end; | |
| 776 | 776 | end; | |
| 777 | 777 | ExitCode := Proc.ExitStatus; | |
| 778 | 778 | finally | |
| 779 | 779 | Proc.Free; | |
| 780 | 780 | Halt(ExitCode); | |
| 781 | 781 | end; | |
| 782 | 782 | end; | |
| 783 | 783 | ||
| 784 | 784 | begin | |
| 785 | 785 | RunsLsRoot; | |
| 786 | 786 | end. | |
| 787 | 787 | ``` | |
| 788 | 788 | ||
| 789 | 789 | 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. | |
| 790 | 790 | ||
| 791 | 791 | ### Używanie fdisk z sudo w systemie Linux | |
| 792 | 792 | ||
| 793 | 793 | 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.** | |
| 794 | 794 | ||
| 795 | 795 | ``` pascal | |
| 796 | 796 | program getpartitioninfo; | |
| 797 | 797 | {Pierwotnie napisany przez użytkownika forum Lazarus wjackon153. Prosimy o kontakt w przypadku pytań, uwag itp. | |
| 798 | 798 | Zmodyfikowan z fragmentu kodu Lazarusa na program FPC dla ułatwienia zrozumienia/zwięzłości przez BigChimp} | |
| 799 | 799 | ||
| 800 | 800 | Uses | |
| 801 | 801 | Classes, SysUtils, FileUtil, Process; | |
| 802 | 802 | ||
| 803 | 803 | var | |
| 804 | 804 | hprocess: TProcess; | |
| 805 | 805 | sPass: String; | |
| 806 | 806 | OutputLines: TStringList; | |
| 807 | 807 | ||
| 808 | 808 | begin | |
| 809 | 809 | sPass := 'yoursudopasswordhere'; // Musisz to zmienić na własne hasło sudo | |
| 810 | 810 | OutputLines:=TStringList.Create; //dla pewności warto zamknąć to w bloku try...finally...end | |
| 811 | 811 | // OutputLines został utworzony ... To samo dla hProcess. | |
| 812 | 812 | ||
| 813 | 813 | // Poniższy przykład otworzy fdisk w tle i da nam informacje o partycjach | |
| 814 | 814 | // Ponieważ fdisk wymaga podwyższonych uprawnień, musimy przekazać | |
| 815 | 815 | // nasze hasło jako parametr do sudo za pomocą opcji -S, więc proces | |
| 816 | 816 | // poczeka, aż nasz program wyśle nasze hasło do aplikacji sudo | |
| 817 | 817 | ||
| 818 | 818 | hProcess := TProcess.Create(nil); | |
| 819 | 819 | // W systemach Linux/Unix/FreeBSD/macOS musimy podać pełną ścieżkę do naszego pliku wykonywalnego: | |
| 820 | 820 | hProcess.Executable := '/bin/sh'; | |
| 821 | 821 | // Teraz dodajemy wszystkie parametry w wierszu poleceń: | |
| 822 | 822 | hprocess.Parameters.Add('-c'); | |
| 823 | 823 | // Tutaj przesyłamy potokiem hasło do polecenia sudo, które następnie wykonuje fdisk -l: | |
| 824 | 824 | hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); | |
| 825 | 825 | // Uruchom asynchronicznie (poczekaj na zakończenie procesu) i użyj potoków, abyśmy mogli odczytać potok wyjściowy | |
| 826 | 826 | hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; | |
| 827 | 827 | // Teraz wykonaj: | |
| 828 | 828 | hProcess.Execute; | |
| 829 | 829 | ||
| 830 | 830 | // hProcess powinien teraz uruchomić zewnętrzny plik wykonywalny (ponieważ używamy poWaitOnExit). | |
| 831 | 831 | // Teraz możesz przetworzyć wyjście procesu (standardowe wyjście i standardowy błąd), np.: | |
| 832 | 832 | OutputLines.Add('stdout:'); | |
| 833 | 833 | OutputLines.LoadFromStream(hprocess.Output); | |
| 834 | 834 | OutputLines.Add('stderr:'); | |
| 835 | 835 | OutputLines.LoadFromStream(hProcess.Stderr); | |
| 836 | 836 | // Pokaż dane wyjściowe na ekranie: | |
| 837 | 837 | writeln(OutputLines.Text); | |
| 838 | 838 | ||
| 839 | 839 | // Wyczyść, aby uniknąć wycieków pamięci: | |
| 840 | 840 | hProcess.Free; | |
| 841 | 841 | OutputLines.Free; | |
| 842 | 842 | ||
| 843 | 843 | //Poniżej znajduje się kilka przykładów, jak widać, możemy przekazywać niedozwolone znaki, tak jak robimy to z terminala | |
| 844 | 844 | //Nawet jeśli przeczytałeś gdzie indziej, że nie możesz upewnić się tą metodą, to możesz :) | |
| 845 | 845 | ||
| 846 | 846 | //hprocess.Parameters.Add('ping -c 1 www.google.com'); | |
| 847 | 847 | //hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2'); | |
| 848 | 848 | ||
| 849 | 849 | //Użycie QuotedStr() nie jest wymagane, chociaż zapewnia czystszy kod; | |
| 850 | 850 | //możesz użyć podwójnego cudzysłowu i mieć ten sam efekt. | |
| 851 | 851 | ||
| 852 | 852 | //hprocess.Parameters.Add('glxinfo | grep direct'); | |
| 853 | 853 | ||
| 854 | 854 | // Ta metoda może być również używana do instalowania aplikacji z repozytorium: | |
| 855 | 855 | ||
| 856 | 856 | //hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); | |
| 857 | 857 | ||
| 858 | 858 | end. | |
| 859 | 859 | ``` | |
| 860 | 860 | ||
| 861 | 861 | ### Parametry zawierające spacje (Zastępowanie cudzysłowów powłoki) | |
| 862 | 862 | ||
| 863 | 863 | W powłoce Linuksa możliwe jest pisanie cytowanych argumentów w następujący sposób: | |
| 864 | 864 | ||
| 865 | - | ||
| 865 | + | ```text | |
| 866 | + | gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram | |
| 867 | + | ``` | |
| 866 | 868 | ||
| 867 | 869 | Polecenie GDB otrzyma 3 argumenty (oprócz pierwszego argumentu, który jest pełną ścieżką do pliku wykonywalnego): | |
| 868 | 870 | ||
| 869 | 871 | 1. --batch | |
| 870 | 872 | 2. --eval-command=info symbol 0x0000DDDD | |
| 871 | 873 | 3. pełna ścieżka do myprogram | |
| 872 | 874 | ||
| 873 | 875 | Najlepszym rozwiązaniem, aby uniknąć skomplikowanego cytowania, jest przejście na TProcess.Parameters.Add zamiast ustawiania wiersza poleceń dla nietrywialnych przypadków, np. | |
| 874 | 876 | ||
| 875 | - | ||
| 876 | - | ||
| 877 | - | ||
| 878 | - | ||
| 877 | + | ``` pascal | |
| 878 | + | AProcess.Executable:='/usr/bin/gdb'; | |
| 879 | + | AProcess.Parameters.Add('--batch'); | |
| 880 | + | AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // zwróć tutaj uwagę na brak cytowania | |
| 881 | + | AProcess.Parameters.Add('/home/me/myprogram'); | |
| 882 | + | ``` | |
| 879 | 883 | ||
| 880 | 884 | Pamiętaj też, aby używać tylko pełne ścieżki. | |
| 881 | 885 | ||
| 882 | 886 | 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: | |
| 883 | 887 | ||
| 884 | - | ||
| 888 | + | ``` pascal | |
| 889 | + | AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram'; | |
| 890 | + | ``` | |
| 885 | 891 | ||
| 886 | 892 | Właściwość .CommandLine jest [przestarzała](http://bugs.freepascal.org/view.php?id=14446), a raporty o błędach do obsługi bardziej skomplikowanych przypadków cytowania nie będą akceptowane. | |
| 887 | 893 | ||
| 888 | 894 | ## Alternatywy LCLIntf | |
| 889 | 895 | ||
| 890 | 896 | 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. | |
| 891 | 897 | ||
| 892 | 898 | ### Otwieranie dokumentu w domyślnej aplikacji | |
| 893 | 899 | ||
| 894 | 900 | 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. | |
| 895 | 901 | ||
| 896 | 902 | ``` pascal | |
| 897 | 903 | uses LCLIntf; | |
| 898 | 904 | ... | |
| 899 | 905 | OpenDocument('manual.pdf'); | |
| 900 | 906 | ... | |
| 901 | 907 | ``` | |
| 902 | 908 | ||
| 903 | 909 | - [[opendocument|Dokumentacja OpenDocument]] | |
| 904 | 910 | ||
| 905 | 911 | ### Otwieranie strony web w domyślnej przeglądarce internetowej | |
| 906 | 912 | ||
| 907 | 913 | 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() | |
| 908 | 914 | ||
| 909 | 915 | ``` pascal | |
| 910 | 916 | uses LCLIntf; | |
| 911 | 917 | ... | |
| 912 | 918 | OpenURL('www.lazarus.freepascal.org/'); | |
| 913 | 919 | ``` | |
| 914 | 920 | ||
| 915 | 921 | Zobacz także: | |
| 916 | 922 | ||
| 917 | 923 | - [[OpenURL|Odnośnik do OpenURL]] | |
| 918 | 924 | ||
| 919 | 925 | Lub możesz użyć **TProcess** w taki sposób: | |
| 920 | 926 | ||
| 921 | 927 | ``` pascal | |
| 922 | 928 | uses Process; | |
| 923 | 929 | ||
| 924 | 930 | procedure OpenWebPage(URL: string); | |
| 925 | 931 | // Możliwe, że musisz podać swój adres URL w cudzysłowiu np. "www.lazarus.freepascal.org" | |
| 926 | 932 | var | |
| 927 | 933 | Browser, Params: string; | |
| 928 | 934 | begin | |
| 929 | 935 | FindDefaultBrowser(Browser, Params); | |
| 930 | 936 | with TProcess.Create(nil) do | |
| 931 | 937 | try | |
| 932 | 938 | Executable := Browser; | |
| 933 | 939 | Params:=Format(Params, [URL]); | |
| 934 | 940 | Params:=copy(Params,2,length(Params)-2); // usuń znaki "", nowa wersja TProcess.Parameters robi to sama | |
| 935 | 941 | Parameters.Add(Params); | |
| 936 | 942 | Options := [poNoConsole]; | |
| 937 | 943 | Execute; | |
| 938 | 944 | finally | |
| 939 | 945 | Free; | |
| 940 | 946 | end; | |
| 941 | 947 | end; | |
| 942 | 948 | ``` | |
| 943 | 949 | ||
| 944 | 950 | ## Zobacz także | |
| 945 | 951 | ||
| 946 | 952 | - [dokumentacja TProcess](http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html) | |
| 947 | 953 | - [[opendocument|OpenDocument]] | |
| 948 | 954 | - [[OpenURL]] | |
| 949 | 955 | - [[TProcessUTF8]] | |
| 950 | 956 | - [[TXMLPropStorage]] | |
| 951 | 957 | - [[Webbrowser]] | |
| 952 | 958 | ||
| 953 | 959 | --- | |
| 954 | 960 | *From the Free Pascal wiki, page "Executing External Programs/pl", as it stood on 2023-11-03 after a change by Slawek, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
