Umíme to s Delphi: 149. díl – body přerušení, které nic nepřerušují

Dnešní článek vysvětlí zajímavou možnost, jak používat breakpointy k něčemu jinému než k zastavení programu. Ukážeme si, že breakpoint nemusí nutně nic zastavit, ale že může namísto toho např. spustit externí funkci, zapsat záznam do ĺogu a nebo vypnout zpracovávání následných výjimek.

Pomocí breakpointů můžeme dosáhnout zajímavých věcí: vytvoříme např. ladicí verzi aplikace a jen tím, že ji začneme šířit (a tedy spouštět vně Delphi) způsobíme, že se z ladicí verze stane verze ostrá. Bez jakéhokoliv našeho zásahu a tedy bez práce.

V minulém dílu našeho seriálu jsme se začali zabývat otázkou breakpointů (bodů přerušení). Řekli jsme si, že breakpointy jsou jednou ze základních a nejčastěji používaných metod k ladění programu: prostřednictvím breakpointu dokážeme zastavit provádění programu v daném (typicky problémovém) místě zdrojového kódu tak, abychom mohli prozkoumat situaci a najít zdroj problémů.

Řekli jsme si také několik dalších věcí. Za prvé, existuje několik druhů bodů přerušení:

  • Source Breakpoint si lze představit jako řádku ve zdrojovém kódu (source code), kterou označíme. Když pak spustíme aplikaci v integrovaném prostřední Delphi, provádění programu se zastaví v okamžiku, kdy dojde řada na tuto označenou řádku.
  • Data Breakpoint má podobnou funkci (také způsobí provádění aplikace spuštěné v integrovaném debuggeru), pouze zastavovací podmínka je trochu jiná: neoznačujeme řádku ve zdrojovém kódu, ale označíme určité paměťové místo a požádáme o pozastavení provádění v okamžiku, kdy se hodnota daného paměťového místa změní.
  • Module Load Breakpoint – tento breakpoint slouží pro pozastavení běhu programu v okamžiku, kdy je do paměti načten příslušný modul, typicky DLL knihovna nebo BPL balíček.
  • Konečně Address Breakpoint – jedná se opět o mechanismus umožňující pozastavit běžící program, nicméně neoznačujeme ani řádku ve zdrojáku ani paměťové místo, nýbrž paměťovou adresu obsahující instrukci, jejíž provedení způsobí pozastavení běhu.

Za druhé, řekli jsme si, jak breakpointy nastavit: například pro nejpoužívanější breakpoint (source breakpoint) najedeme kurzorem na požadovanou řádku ve zdrojovém kódu a zvolíme Run – Add Breakpoint – Source Breakpoint.

Za třetí, a tím už se pomalu začínáme dostávat k náplni dnešního článku, řekli jsme si, že breakpointy mohou mít nastaveny nejrůznější vlastnosti, parametry a podmínky. Jinak řečeno, pokud otevřeme dialog pro nastavování vlastností breakpointu (Breakpoint Properties Dialog), můžeme nastavovat typicky následující atributy:

  • Condition– podmínka, která musí být splněna k tomu, aby breakpoint “zabral”
  • Pass count – počet průchodů daného místa ve zdrojovém kódu. Jinak řečeno, nastavíme-li Pass count = 10, zabere breakpoint až poté, co je daná řádka zdrojového kódu provedena po jedenácté.
  • Group – slouží k vytváření skupin breakpointů. Do pole Group několika breakpointů se zadá tentýž název skupiny: takto označené breakpointy poté tvoří skupinu

Jen dodejme, že zmíněný dialog Breakpoint Properties Dialog lze zobrazit například takto: klepneme pravým tlačítkem na nastavený breakpoint (na červenou tečku na levém konci řádku s breakpointem) a z otevřené kontextové nabídky zvolíme Breakpoint Properties.

Bod přerušení nemusí nic přerušit

Dostali jsme se k tomu, co vám hodlám v dnešním článku sdělit. Zůstaňme na chvíli u dialogu pro nastavování vlastností breakpointu, viz obrázek:

Všimněte si v pravém spodním rohu dialogu tlačítka Advanced. Pokud na něj klepneme, otevře se další sekce dialogu, která nám umožňuje nastavovat další, pokročilejší atributy breakpointu, viz obrázek:

Nebudeme zde podrobně popisovat všechny dostupné volby. Zaměříme se na jednu z nich – na tu, která je pro nás dnes nejzajímavější. Všimněte si prosím hned prvního zaškrtávacího pole v sekci Actions: pole Break.

Pokud zrušíme zaškrtnutí tohoto políčka, bod přerušení nic nepřeruší: breakpoint nic nebreakne. Pokud teď kroutíte hlavou a ptáte se, k čemu je nám bod přerušení, který nic nepřeruší, pokračujte ve čtení.

Co to je?

Začneme tím, že si povíme, jak vlastně funguje běžný, normální, klasický breakpoint (tedy takový, který jsme například popisovali v minulém dílu seriálu). V zásadě platí, že pokud spustíme jakoukoliv aplikaci uvnitř integrovaného prostředí Delphi (IDE), běží v rámci tzv. integrovaného debuggeru, právě proto, abychom měli možnost aplikaci ladit.

Předpokládejme, že vložíme do zdrojového kódu breakpoint a spustíme program v rámci IDE. Potom v okamžiku, kdy breakpoint zabere, dojde ve své podstatě ke spuštění integrovaného debuggeru a jeho součástí tak, abychom měli možnost využívat všechny ladicí nástroje.

Pojďme dál. Jediný rozdíl mezi takto definovaným (tedy klasickým) breakpointem a breakpointem, který nezastavuje (pojďme jej pro potřeby dalšího textu označovat jako „nepřerušující breakpoint“) spočívá v tom, že nepřerušující breakpoint nezpůsobí v okamžiku, kdy „zabere“, spuštění integrovaného debuggeru.

Takže: nepřerušující breakpoint je breakpoint jako každý jiný (zachází se s ním tedy úplně stejně, do kódu se vkládá úplně stejně, jeho parametry se nastavují úplně stejně), ale když zabere, nezpůsobí spuštění integrovaného debuggeru – a tedy neprovede pozastavení běhu aplikace.

Tohle je už asi jasné, zůstává tedy jen jedna, o to však důležitější otázka: k čemu to může být dobré?

K čemu to může být dobré?

Pravděpodobně si teď říkáte cosi ve smyslu „k čemu je nám breakpoint, který nepozastaví program?“. Tato otázka je na místě: uvidíte, že nepřerušující breakpointy mají mnohé využití.

Zdůrazněme totiž, že takový nepřerušující breakpoint může pořád udělat řadu věcí, například zapsat zprávu do chybového logu, spustit jinou funkci, vyhodnotit výraz, instruovat debugger ke zpracovávání, resp. ignorování výjimek, nebo povolit/zakázat celou skupinu breakpointů.

Nejlepší bude ukázat si celý problém v praxi. Vytvoříme společně jednoduchou aplikaci simulující použití nepřerušujících breakpointů.

Ukázka nepřerušujícího breakpointu

Vytvořte v Delphi novou aplikaci, na formulář umístěte jedinou komponentu – tlačítko Button. Následně ošetřete událost OnClick tohoto tlačítka.

V obsluze OnClick budeme simulovat jednoduchou činnost spočívající v následujících bodech:

  • otestujeme, zda na disku v daném adresáři existuje soubor s daným názvem (např. txt)
  • pokud soubor existuje, otevřeme jej pro přípis (pro zápis na jeho konec)
  • pokud soubor neexistuje, vytvoříme jej a vepíšeme do něj jednoduchou hlavičku
  • následně simulujeme jakousi práci se souborem, v našem případě do něj pouze zapíšeme řádku s aktuálním datem a časem.

Kód, na kterém si předvedeme použití nepřerušujícího breakpointu, bude tedy vypadat takto:

procedure TForm1.Button1Click(Sender: TObject);
var
  Soub: TextFile;

begin
  AssignFile(Soub, SOUB_NAZ);

  if FileExists(SOUB_NAZ) then
    Append(Soub)
  else begin
    Rewrite(Soub);
    WriteLn(Soub, `****************************`);
    WriteLn(Soub, `* * *        Test      * * *`);
    WriteLn(Soub, `****************************`);
  end;

  // pracujeme se souborem

  WriteLn(Soub, `Pracujeme se souborem ... ` + TimeToStr(Time));
  CloseFile(Soub);
end;

Co nyní chceme? Dejme tomu, že jsme ve fázi ladění celé aplikace a chceme, aby soubor text.txt při spuštění aplikace nikdy neexistoval (abychom mohli otestovat funkčnost větve provádějící jeho založení a následné zapsání hlavičky do souboru). K tomu nám může pomoci právě nepřerušující breakpoint.

Vložme tedy na řádku AssignFile(Soub, SOUB_NAZ) breakpoint (vstupte na ni textovým kurzorem a z hlavní nabídky zvolte Run – Add Breakpoint – Source Breakpoint). Otevře se dialog určený pro nastavování vlastností breakpointu: klepněte na výše zmíněné tlačítko Advanced...

Dialog, který se objevil, jsme už viděli (na obrázku výše). Pojďme si vysvětlit všechna jeho pole:

  • Ignore subsequent exceptions – toto políčko slouží k vypnutí reakce na výjimky. Pokud toto pole zaškrtneme, přestane se od daného okamžiku spouštět integrovaný debugger v případě vzniku výjimky. Víme, že integrovaný debugger se standardně spustí pokaždé, když vznikne výjimka: zaškrtnutím tohoto políčka lze toto standardní chování změnit. Zde je také vidět význam nepřerušujícího breakpointu: můžeme chtít vypnout reakci na výjimky, ale nemusíme k tomu nutně chtít zastavit provádění programu. Dodejme, že reakci na výjimky lze opětovně spustit úplně stejně: kamsi do následných částí zdrojového kódu nastavíme další breakpoint (ať už přerušující či nikoliv), který reakci na výjimky zase „zapne“ zaškrtnutím pole Handle subsequent exceptions. Jednoduché a elegantní.
  • Log message – díky vestavěnému mechanismu můžeme jednoduše požádat Delphi, aby nám zapsaly do chybového logu určitou zprávu. Dejme tomu, že vytvoříme breakpoint (opět nezáleží na tom, zda bude přerušující či nikoliv) a budeme chtít, aby jeho spuštění (až „zabere“) způsobilo zapsání informace o datu a času do logu. Ideální pole pro zapsání takového požadavku je právě Log message. Dodejme, že obsah logu lze jednoduše prohlédnout zvolením View – Debug Windows – Event Log. Tímto způsobem lze například elegantně „trackovat“ - sledovat běh programu.
  • Eval Expression – pole pro zadání výrazu k vyhodnocení může sloužit dvěma účelům. Za prvé, může být (překvapivě) použito k vyhodnocení jakéhokoliv výrazu – proměnné, objektu, metody, konstanty,zdroje, funkce apod. Poté, co zapíšeme požadovaný výraz do pole Eval expression, zpřístupní se pro nás zaškrtávací pole Log result: je asi zřejmé, že jeho zaškrtnutí způsobí zapsání výsledku vyhodnocení daného výrazu do logu. Druhé, vtipné použití Eval expression pole může spočívat ve spuštění externí funkce, která pro nás může vykonat cennou činnost nebo potřebou službu. Tím se vracíme zpět k našemu příkladu, neboť právě to si předvedeme.

Řekli jsme si tedy, že naším cílem v dané fázi ladění aplikace je zajistit při každém spuštění aplikace, že soubor test.txt nebude existovat. Není proto nic jednoduššího, než vytvořit jednoduchou funkci, která tento soubor v případě jeho existence smaže. Tuto funkci potom zavoláme z pole Eval z breakpointu.

Nejprve vytvoříme tělo funkce (resp. procedury):

procedure SmazTestSoubor;
begin
  if FileExists(SOUB_NAZ) then
    DeleteFile(SOUB_NAZ);

end;

Nyní je potřeba zajistit vyvolání této funkce. Použijeme nepřerušující breakpoint: není důvod, aby se kvůli smazání souboru musel pozastavovat běh celé aplikace.

Důležitá poznámka: V této souvislosti je nutné upozornit na jednu nástrahu: aby breakpoint mohl spustit funkci uvedenou v poli Eval, musí tato funkce:

  • být viditelná z místa breakpointu,
  • být linkerem fyzicky vložena do spustitelné aplikace.

Především druhý bod je důležitý: funkce musela být linkerem vložena do výsledného kódu aplikace, což znamená, že tato funkce musí mít šanci býti zavolána odkudsi ze zdrojového kódu. Zopakujme, že pokud nějaká funkce není (a nemůže být) nikdy zavolána, linker ji z důvodu optimalizace do kódu vůbec neumístí. Linker přitom nemůže tušit, zda jsme nastavili a pozapínali nějaké breakpointy; pokud je tedy breakpoint tím jediným, kdo může potenciálně funkci volat, hlediska linkeru ji nebude volat nikdo, do kódu nebude vložena a breakpoint tedy v důsledku nezavolá skutečně nic.

Řešením této prekérní situace je například vytvořit obsluhu události, která nikdy nenastane, a zmíněnou funkci z této obsluhy zavolat.

Přesně to učiníme my. Vytvoříme obsluhu události OnGetSiteInfo formuláře Form1 a do její obsluhy umístíme zavolání funkce SmazTestSoubor. Tato obsluha se sice v praxi nikdy neprovede,to ale linker neví,takže k jeho „oblbnutí“ bude tato metoda stačit:

procedure TForm1.FormGetSiteInfo(Sender: TObject; DockClient: TControl;
  var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
begin
  SmazTestSoubor;
end;

Jsme skoro hotovi. Nyní stačí konečně dokončit vloženi breakpointu na zmíněnou řádku s příkazem AssignFile. Nastavíme jeho parametry takto:

Jsme hotovi. Program bude fungovat takto:

  • při každém spuštění se provede breakpoint,
  • breakpoint nemá zaškrtnuto políčko „Break“, nezpůsobí tedy pozastavení programu,
  • zato má za úkol zapsat záznam do logu a také
  • spustit proceduru SmazTestSoubor, která způsobí vymazání testovacího souboru

Zkuste si aplikaci přeložit a několikrát klepnout na tlačítko. Při každém klepnutí se do souboru test.txt v aktuálním adresáři zapíše nová hlavička a jedna řádka s aktuálním datem a časem. Následně program uzavřete, zkuste vypnout breakpoint, znovu program spustit a několikrát klepnout na tlačítko. Soubor nebude přepisován, bude do něj přidávána vždy jedna řádka s aktuálním časem.

Totéž platí pro spouštění aplikace mimo Delphi. Pokud spustíte aplikaci mimo Delphi (exe soubor přímo ve Windows), nebudou mít samozřejmě breakpointy žádný význam a aplikace poběží zcela normálně, do souboru se tedy budou připisovat řádky:

****************************
* * *        Test      * * *
****************************
Pracujeme se souborem ... 0:59:57
Pracujeme se souborem ... 1:00:40
Pracujeme se souborem ... 1:00:40
Pracujeme se souborem ... 1:00:40
Pracujeme se souborem ... 1:00:40
Pracujeme se souborem ... 1:00:40
Pracujeme se souborem ... 1:02:14
Pracujeme se souborem ... 1:02:14
Pracujeme se souborem ... 1:02:14
Pracujeme se souborem ... 1:02:14
Pracujeme se souborem ... 1:02:15
Pracujeme se souborem ... 1:02:15
Pracujeme se souborem ... 1:02:16
Pracujeme se souborem ... 1:02:16

Zkuste si také prohlédnout log – View – Debug Windows – Event Log:

Závěrem

Dnes jsme si ukázali zajímavou možnost, jak používat breakpointy k něčemu jinému než k zastavení programu. Už víme, že breakpoint nemusí nutně nic zastavit, ale může namísto toho třeba spustit externí funkci, zapsat záznam do logu a nebo vypnout zpracovávání následných výjimek. Výhodou breakpointů v tomto směru je, že ať s jejich pomocí vytvoříme jakoukoliv „ladicí konstrukci“, bude aktivní pouze při ladění – pouze při spouštění aplikace z Delphi. Jakmile je aplikace odladěna a začneme ji šířit, nemusíme v krajním případě kód nijak měnit: jen tím, že nebude spouštěn uvnitř Delphi způsobí, že se z testovací a ladicí verze stane verze ostrá.

Váš názor Další článek: Computer Press Media hledá redaktory pro časopis Connect! a FinExpert.cz

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,