Executing External Programs/pl

From Lazarus wiki
Jump to navigationJump to search

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)

Wykonywanie programów zewnętrznych

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

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.

MS Windows: CreateProcess, ShellExecute i WinExec

Light bulb  Uwaga: 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.
Light bulb  Uwaga: 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 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):

  1. --batch
  2. --eval-command=info symbol 0x0000DDDD
  3. 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;

Zobacz także