Tehnični članak

Dinamično ustvarjanje in sproščanje komponente HotPDF v C++Builderju

Odlaganje komponente THotPDF na obrazec med načrtovanjem (design time) je v redu za hiter prototip, vendar komponento poveže z življenjsko dobo obrazca, kar redko želimo v produkcijski kodi. Generator poročil, ki se izvede enkrat na klik gumba, storitvena nit (service thread), ki ponoči serijsko izvaža podatke, ali pomožni razred, ki sploh nima obrazca: v vseh teh primerih želite, da komponenta obstaja natanko toliko časa, kolikor traja eno PDF opravilo, in nato izgine. To pomeni dodelitev (allocation) v času izvajanja (runtime), kar spremeni dve stvari, ki jih je vredno razumeti pred pisanjem prve vrstice: kdo je lastnik predmeta in kako se izvede čiščenje (cleanup), ko gre kaj narobe

Semantika lastnika v VCL

Vsak konstruktor VCL komponente sprejme parameter Owner tipa TComponent*. Če mu podate this (obrazec), registrira nov predmet na seznam lastniških komponent obrazca. Če se obrazec uniči, medtem ko je komponenta še vedno živa, jo VCL samodejno sprosti. Če podate nullptr, to pomeni, da lastnika ni: sami prevzamete odgovornost za kazalec in nič ga ne bo očistilo namesto vas, če izjema (exception) razvije sklad preden pride do vašega eksplicitnega klica delete

Za enkratni izvoz, ki se zaključi znotraj ene same funkcije, delujeta obe možnosti, vendar imata različne načine odpovedi. S this kot lastnikom puščanje pomnilnika (leak) ni mogoče, dokler se obrazec na koncu zapre; z nullptr pa mora kazalec doseči blok __finally. V praksi je vzorec nullptr in __finally nekoliko čistejši za kratkoživeče predmete, ker na prvi pogled naredi življenjsko dobo vidno in prepreči, da bi obrazec kopičil lastniške predmete, ki naj bi bili le začasni

Varna struktura pred izjemami (Exception-safe)

Generiranje PDF-ja lahko ne uspe iz razlogov, ki nimajo nič opraviti z API-jem: izhodna mapa je samo za branje (read-only), manjka datoteka pisave, tok (stream) se predčasno izprazni (flush) ali pa podatki, ki jih posreduje klicatelj, dosežejo omejitev dolžine. Ne glede na vzrok se mora pot za čiščenje izvesti. Idiomatski C++Builder način, ki to zagotavlja, 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;
    }
}

Na nekaj stvari v tem izpisu velja opozoriti. Lastnik je nullptr, kar naredi življenjsko dobo eksplicitno. Compression in FontEmbedding sta nastavljena pred BeginDoc: oboje sta možnosti na ravni dokumenta, ki ju HotPDF uveljavi, ko se dokument odpre, dodelitev po tem pa nima učinka. TextOut vzame koordinate v točkah, merjenih od spodnjega levega kota strani, pri čemer Y narašča navzgor; par 72, 720 postavi besedilo blizu zgornjega levega kota strani v formatu letter z levim robom enega palca (one-inch). delete Pdf v bloku __finally se izvede ne glede na to, ali je BeginDoc, risanje ali EndDoc sprožil izjemo ali ne

Izogibajte se klicanju katere koli metode na Pdf po klicu delete. Če je kazalec shranjen v spremenljivki člana (member variable), jo takoj po brisanju nastavite na nullptr, da vsak naključni poznejši dostop povzroči čisto zrušitev (crash) namesto tihe korupcije (silent corruption)

Konfiguracija projekta

C++Builder najde THotPDF s kombinacijo vključitvenih poti (include paths), poti do knjižnic in pragma direktive. Ustvarjena glava (header) se nahaja skupaj z HPDFDoc.pas v izvorni mapi HotPDF; dodajte to mapo v Project > Options > C++ Compiler > Include path. Direktiva #pragma link "HPDFDoc" pove povezovalniku (linker), naj potegne prevedeno enoto, ne da bi jo ročno navedli v projektni datoteki. Če uporabljate runtime paket (runtime package) namesto statičnega povezovanja (static linking), najprej namestite HotPDF design in runtime paketa; pragma še vedno velja

Ime enote HPDFDoc naj ostane nespremenjeno. C++Builder izpelje ime glave iz imena enote Pascal, zato preimenovanje datoteke ali uporaba psevdonima (alias) poti v pragmi tiho prekine iskanje

Obseg (Scoping) in večdokumentna opravila

Za posamezen izvoz, ki ga sproži uporabnikovo dejanje, je lokalna spremenljivka znotraj ročice (handler) gumba pravi odgovor: ustvarjena je, uporabljena in uničena znotraj enega okvira klica (call frame), namen pa je očiten vsakomur, ki kodo prebere kasneje. Alternativa med načrtovanjem (design-time) je upravičena, ko isti obrazec poganja neprekinjen potek dela (workflow), kot je plošča za predogled tiskanja, ki ponovno zgradi dokument vsakič, ko uporabnik spremeni nastavitev; v tem primeru je ohranjanje komponente pri življenju ter večkratno klicanje BeginDoc/EndDoc manj moteče kot večkratno dodeljevanje in sproščanje predmetov v kopici (heap)

Za serijska opravila (batch jobs), ki ustvarijo veliko dokumentov v zaporedju, je en THotPDF na dokument vreden dodatnih stroškov dodelitve (allocation overhead). Stanje se ne prenaša med dokumenti, če predmeta ni, kar pomeni en razred občasnih hroščev, ki ga ne bo treba nikoli odpravljati (debug). Dodelite, generirajte, izbrišite, ponovite

Ena lastnost, ki se pojavi v več demonstracijah HotPDF, je AutoLaunch, ki odpre ustvarjeno datoteko v sistemskem pregledovalniku PDF takoj po EndDoc. Je uporabno med pisanjem prvega osnutka postavitve. V produkciji jo preskočite: eksplicitno odprite izhodno pot, preverite, ali datoteka obstaja in ima velikost, večjo od nič, zabeležite (log) rezultat in prepustite klicujočemu poteku dela (workflow), da se odloči, ali je pregledovalnik ustrezen. V serijskem opravilu AutoLaunch odpre eno okno pregledovalnika na dokument in na nekaterih sistemih blokira postopek, medtem ko čaka, da se pregledovalnik zapre

Komponenta THotPDF in vsi klici risanja, prikazani tukaj, so del komponente HotPDF Component za Delphi in C++Builder