Jak spustit program a počkat na jeho ukončení - podruhé - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



Delphi

Jak spustit program a počkat na jeho ukončení - podruhé

27. prosince 2001, 00.00 | Podruhé se vracíme k tématu, naznačeném v titulku. Tedy jak spustit ze svého programu jiný program a pak detekovat kdy se ukončí. Tentokrát ale do hry zapojíme multithreading.

V srpnu vyšel na Builderu můj článek o tom, jak spustit ze svého programu jiný program a pak detekovat, kdy se ukončí. Pokud si článek přečtete (což rozhodně doporučuji, jinak nejspíš v tomto článku nebudete úplně v kontextu), zjistíte, že jsme při tvorbě aplikace použili Win32 API funkci WaitForSingleObject, která čekala dokud program neskončil. Problémem ovšem bylo, jak mezitím přinutit aplikaci reagovat na zprávy zasílané Windows - například o nutnosti překreslit okno. Řešení, které jsem použil, bylo z těch méně čistých - spočívalo rozdělení čekání na 100ms intervaly, přičemž mezi každými takovými intervaly se zavolala procedura Application.ProcessMessages, která provedla potřebné ošetření zpráv.

V komentářích pod článkem se objevil mimo jiné i návrh použít v aplikaci vlákna (thready). Jelikož považuji tento nápad za dobrý, podíváme se v tomto článku právě na možnost začlenění vláken do aplikace z minulého článku.

Dva přístupy

Vlákna by se dala do aplikace začlenit dvěma způsoby:

  1. Celou implementaci vláken skrýt do volání procedury WinExecAndWait z původní verze programu, která by vrátila až po skončení vlákna otevírajícího spouštěný program. Tím pádem by "vnějšek" programu mohl zůstat stejný, ale na druhou stranu by hlavní vlákno zůstalo zablokováno po dobu, kdy by byl otevřený spuštěný program. Program by se ve výsledku choval v podstatě stejně jako předtím.
  2. Druhý přístup je proceduru WinExecAndWait nahradit celou vláknem, které by se spustilo, a program by dál pokračoval v normální činnosti. Po skončení vlákna by se zavolala jeho procedura pověšená na jeho události OnTerminate, ve které bychom enablovali ovládací prvky a připravili tak naši aplikaci na spuštění dalšího programu. Výhodou tohoto přístupu je snadnější implementace, ovšem za cenu změnění volání (je nutné přímo v programu vytvořit nové vlákno a nastavit mu některé parametry).

Schémata práce programu v obou případech shrnují následující dva obrázky:

Schéma znázorňující dva přístupy k začlenění mutlithreadingu do aplikace

Implementace

Rozhodl jsem se implementovat druhý způsob, protože lépe ukazuje mutithreading v praxi, navíc mi připadá o něco čistší. Unita s vláknem bude vypadat takto:

unit WEExecuteThread;

interface

uses
  Classes;

type
  TExecuteThread = class(TThread)
  protected
    procedure Execute; override;
  public
    FileName: string;    // co spustit?
    Visibility: Integer; // jak to spustit?
    Result: Integer;     // výsledek spuštění
  end;

implementation

uses
  Windows;

{ TExecuteThread }

procedure TExecuteThread.Execute;
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  { Naplníme strukturu StartupInfo. }
  FillChar(StartupInfo, SizeOf(StartupInfo), #0);
  StartupInfo.CB := SizeOf(StartupInfo);
  // spustit v normálním okně
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  // způsob zobrazení okna (normální, maximalizované,...)
  StartupInfo.wShowWindow := Visibility;

  if not CreateProcess
    nil,
    PChar(FileName),       // příkazová řádka - to, co se spustí
    nil,                   // bezpečnostní atributy vzniklého procesu...
    nil,                   // ... a vlákna - ani jedno nepotřebujeme
    False,                 // dědění handlů - nechceme
    NORMAL_PRIORITY_CLASS, // proces má normální prioritu
    nil,                   // ukazatel na proměnné prostředí - hodnota nil...
                           // ... znamená jejich zdědění od volajícího procesu
    nil,                   // aktuální adresář, nil opět znamená zdědění
    StartupInfo,           // ukazatele na potřebné struktury
    ProcessInfo)
  then
    Result := -1 // při chybě vracíme -1
  else
  begin
    { Pokud spuštění proběhne v pořádku, čekáme na ukončení procesu. }
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    { Nakonec vrátíme návratový kód procesu. }
    GetExitCodeProcess(ProcessInfo.hProcess, Result);
  end;
end;

end.

Jak je vidět, hlavní procedura vlákna značně připomíná předchozí WinExecuteAndWait, jen parametry jsou předávány jako pole (ve významu fields, ne array!) objektu vlákna.

Nyní hlavní procedura našeho programu, reagující na stisk tlačítka Spustit:

procedure TForm1.Button1Click(Sender: TObject);
var
  ET: TExecuteThread;
begin
  // disablujeme ovládací prvky
  Edit1.Enabled := False;
  Edit1.Color := clBtnFace;
  Button1.Enabled := False;

  // vytvoříme spouštěcí vlákno
  // (True znamená, že je uspané)
  ET := TExecuteThread.Create(True);
  with ET do
  begin
    FileName := Edit1.Text;
    Visibility := SW_SHOWNORMAL;
    // při ukončení vlákna zavoláme proceduru OnThreadTerminate
    OnTerminate := OnThreadTerminate;
    // po ukončení se vlákno samo odalokuje
    FreeOnTerminate := True;
    Resume; // aktivujeme vlákno
  end;
end;

No a nakonec ještě výpis procedury OnThreadTerminate, která se vykoná po skončení činnosti vlákna:

procedure TForm1.OnThreadTerminate(Sender: TObject);
begin
  // enablujeme ovládací prvky
  Edit1.Enabled := True;
  Edit1.Color := clWindow;
  Button1.Enabled := True;
end;

Povšimněte si, že objekt vlákna ET je deklarovaný jen jako lokální proměnná. To si můžeme dovolit ze dvou důvodů:

  1. Je to de facto jen ukazatel na objekt, když procedura skončí a ukazatel se odalokuje, neznamená to, že objekt už také neexistuje.
  2. Vláknu jsme nastavili vlastnost FreeOnTerminate na True, což zajistí, že po ukončení běhu vlákna se jeho objekt "sám od sebe" odalokuje. Nebudeme tedy nikdy volat ET.Free a nemusíme si tím pádem ET pamatovat.

Fakt že ET je deklarována lokálně navíc teoreticky umožňuje naši aplikaci snadno rozšířit o funkci volání více programů najednou.

Závěr

Nyní už máte k dispozici celkem dva způsoby, jak zajistit detekci ukončení spuštěného programu. Je na vás, který se vám bude do vaší aplikace víc hodit.

Můžete si stáhnout kompletní ukázkovou aplikaci i se zdrojáky pro Delphi 3 a vyšší.

Tématické zařazení:

 » Rubriky  » Delphi  

 » Rubriky  » Windows  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: