Technical Article

Dynamické vytváranie a uvoľňovanie komponentu HotPDF v C++Builderi

Umiestnenie komponentu THotPDF na formulár v čase návrhu (design-time) je v poriadku pre rýchly prototyp, ale spája životnosť komponentu so životnosťou formulára, čo je v produkčnom kóde málokedy žiaduce. Generátor zostáv spúšťaný jedným kliknutím na tlačidlo, servisné vlákno na nočný dávkový export, pomocná trieda bez formulára: v každej z týchto situácií chcete, aby komponent existoval presne počas trvania jednej úlohy generovania PDF a potom zanikol. To znamená alokáciu za behu (runtime), čo mení dve veci, ktoré je potrebné pochopiť skôr, než napíšete prvý riadok kódu: kto objekt vlastní a ako prebieha čistenie, keď sa niečo pokazí.

Sémantika vlastníctva vo VCL

Každý konštruktor komponentu VCL berie parameter Owner typu TComponent*. Odovzdaním this (formulár) sa nový objekt zaregistruje do zoznamu vlastnených komponentov formulára, takže ak sa formulár zničí, kým je komponent ešte aktívny, VCL ho automaticky uvoľní. Odovzdanie nullptr znamená, že komponent nemá vlastníka: preberáte výhradnú zodpovednosť za ukazovateľ a nič ho za vás neuvoľní, ak výnimka rozvinie zásobník (unwind) pred vaším explicitným volaním delete.

Pre jednorazový export, ktorý sa dokončí v rámci jednej funkcie, funguje každá možnosť, ale obe majú rôzne chybové stavy. S this ako vlastníkom je únik pamäte (leak) nemožný, pokiaľ sa formulár nakoniec zatvorí; s nullptr sa ukazovateľ musí dostať do bloku __finally. V praxi je vzor nullptr plus __finally o niečo čistejší pre krátkodobé objekty, pretože robí hranicu životnosti viditeľnou na prvý pohľad a zabraňuje formuláru hromadiť vlastnené objekty, ktoré mali byť len dočasné.

Výnimkovo bezpečná štruktúra

Generovanie PDF môže zlyhať z dôvodov, ktoré nemajú nič spoločné s API: výstupný adresár je iba na čítanie, chýba súbor písma, prúd (stream) sa predčasne vyprázdni (flush) alebo dáta dodané volajúcim narazia na limit dĺžky. Bez ohľadu na príčinu musí záchranná cesta čistenia prebehnúť. Idiomatickým spôsobom v C++Builderi, ako to zaručiť, je try/__finally:

#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma link "HPDFDoc"
#pragma resource "*.dfm"

TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    THotPDF* Pdf = new THotPDF(nullptr);
    try
    {
        Pdf->FileName = "output.pdf";
        Pdf->Compression = cmFlateDecode;
        Pdf->FontEmbedding = true;
        Pdf->BeginDoc();
        Pdf->CurrentPage->SetFont("Arial", TFontStyles(), 12);
        Pdf->CurrentPage->TextOut(72, 720, 0, L"Hello from C++Builder");
        Pdf->EndDoc();
    }
    __finally
    {
        delete Pdf;
    }
}

V tomto výpise kódu stojí za zmienku niekoľko vecí. Vlastníkom je nullptr, vďaka čomu je životnosť explicitná. Vlastnosti Compression a FontEmbedding sa nastavujú pred volaním BeginDoc: obe sú voľby na úrovni dokumentu, ktoré HotPDF potvrdzuje pri otvorení dokumentu, a ich neskoršie priradenie nemá žiadny vplyv. Metóda TextOut berie súradnice v bodoch meraných od ľavého dolného rohu stránky, pričom os Y rastie smerom nahor; súradnice 72, 720 umiestnia text blízko horného ľavého okraja stránky veľkosti Letter s jednopásovým ľavým okrajom. Príkaz delete Pdf v bloku __finally sa spustí bez ohľadu na to, či BeginDoc, kreslenie alebo EndDoc vyvolali výnimku alebo nie.

Vyhnite sa volaniu akejkoľvek metódy na objekte Pdf po jeho vymazaní. Ak je ukazovateľ uložený v členskej premennej, nastavte ho hneď po zmazaní na nullptr, aby akýkoľvek neskorší náhodný prístup spôsobil čistý pád namiesto tichej korupcie dát.

Konfigurácia projektu

C++Builder lokalizuje THotPDF prostredníctvom kombinácie ciest pre include, knižnice a direktívy pragma. Vygenerovaná hlavička (.hpp) sa nachádza vedľa súboru HPDFDoc.pas v zdrojovom adresári HotPDF; pridajte tento adresár do ponuky Project > Options > C++ Compiler > Include path. Direktíva #pragma link "HPDFDoc" hovorí linkeru, aby vtiahol skompilovanú jednotku bez toho, aby ju bolo nutné manuálne uvádzať v súbore projektu. Ak namiesto statického linkovania používate balík (package) pre runtime, nainštalujte najprv balíky HotPDF pre dizajn a runtime; pragma stále platí.

Názov jednotky HPDFDoc ponechajte nezmenený. C++Builder odvodzuje názov hlavičkového súboru od názvu jednotky Pascal, takže premenovanie súboru alebo použitie aliasu cesty v pragma ticho naruší vyhľadávanie.

Rozsah platnosti (scoping) a viacdokumentové úlohy

Pre jeden export spustený akciou používateľa je správnou odpoveďou lokálna premenná obmedzená na obsluhu tlačidla: vytvorí sa, použije a zničí v rámci jedného rámca volania a jej účel je zrejmý pre každého, kto bude neskôr čítať kód. Alternatíva v čase návrhu je opodstatnená vtedy, keď ten istý formulár riadi nepretržitý pracovný postup, ako je napríklad panel náhľadu tlače, ktorý znova zostaví dokument vždy, keď používateľ zmení nastavenie; v takom prípade je udržiavanie komponentu nažive a opakované volanie BeginDoc/EndDoc menej zaťažujúce ako opakované alokovanie a uvoľňovanie objektov z haldy (heap).

Pri dávkových úlohách, ktoré vytvárajú veľa dokumentov za sebou, stojí alokácia samostatného THotPDF pre každý dokument za túto réžiu. Stav sa medzi dokumentmi neprenáša, ak neexistuje objekt, ktorý by ho niesol, čím predídete občasným a ťažko laditeľným chybám. Alokovať, vygenerovať, vymazať a opakovať.

Jednou z vlastností, ktorá sa objavuje v niekoľkých ukážkach HotPDF, je AutoLaunch, ktorá otvára vygenerovaný súbor v systémovom prehliadači PDF ihneď po volaní EndDoc. To je užitočné pri tvorbe prvého návrhu rozloženia stránky. V produkcii sa tomu vyhnite: otvorte výstupnú cestu explicitne, overte, či súbor existuje a má nenulovú veľkosť, zapíšte výsledok do logu a nechajte volajúci pracovný postup rozhodnúť, či je prehliadač potrebný. V dávkovej úlohe by AutoLaunch otvoril jedno okno prehliadača pre každý dokument a na niektorých systémoch by zablokoval proces čakaním na zatvorenie prehliadača.

Komponent THotPDF a všetky zobrazené volania na kreslenie sú súčasťou HotPDF Component pre Delphi a C++Builder.