Václav Kadlec 17.5.2005
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í:
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:
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.
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í.
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é?
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ů.
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:
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:
Ř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,
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:
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:

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á.