Technical Article

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

Postavitev komponente THotPDF na obrazec v času načrtovanja je primerna za hiter prototip, vendar poveže življenjsko dobo komponente z življenjsko dobo obrazca, kar je redko tisto, kar želimo v produkcijski kodi. Generator poročil, ki se zažene ob kliku gumba, servisna nit, ki izvaja serijski izvoz ponoči, ali pomožni razred, ki sploh nima obrazca: v vsaki od teh situacij želite, da komponenta obstaja natanko toliko časa, kolikor traja eno opravilo PDF, in nato izgine. To pomeni dodelitev med delovanjem, kar pa spremeni dve stvari, ki jih je vredno razumeti pred pisanjem prve vrstice: kdo je lastnik objekta in kako poteka čiščenje, ko gre kaj narobe.

Semantika lastništva v VCL

Vsak konstruktor komponente VCL sprejme parameter Owner tipa TComponent*. Če posredujete this (obrazec), se nov objekt registrira na seznamu komponent v lasti obrazca. Če se obrazec uniči, ko je komponenta še vedno aktivna, jo VCL samodejno sprosti. Posredovanje vrednosti nullptr pomeni, da ni lastnika: sami prevzamete polno odgovornost za kazalec in nič ga ne bo očistilo namesto vas, če izjema odvije sklad pred vašim eksplicitnim klicem delete.

Za enkraten izvoz, ki se zaključi znotraj ene funkcije, delujeta obe možnosti, vendar imata različne načine odpovedi. Z this kot lastnikom puščanje pomnilnika ni mogoče, dokler se obrazec na koncu zapre; z nullptr pa mora kazalec doseči blok __finally. V praksi je vzorec nullptr skupaj z __finally nekoliko čistejši za kratkotrajne objekte, saj naredi mejo življenjske dobe vidno na prvi pogled in preprečuje, da bi obrazec kopičil lastniške objekte, ki so bili mišljeni kot začasni.

Izjemno varna struktura (Exception-safe structure)

Ustvarjanje PDF-ja lahko ne uspe iz razlogov, ki nimajo nobene zveze z API-jem: izhodna mapa je samo za branje, datoteka pisave manjka, tok se predčasno izprazni ali pa podatki klicatelja dosežejo omejitev dolžine. Ne glede na vzrok se mora pot čiščenja zagnati. Idiomatski način za zagotovitev tega v C++Builderju je uporaba bloka 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;
    }
}

Nekaj stvari v tem izseku je vredno izpostaviti. Lastnik je nullptr, kar naredi življenjsko dobo eksplicitno. Lastnosti Compression in FontEmbedding sta nastavljeni pred klicem BeginDoc: obe sta možnosti na ravni dokumenta, ki jih HotPDF uveljavi ob odprtju dokumenta, kasnejše dodeljevanje pa nima učinka. Metoda TextOut sprejme koordinate v točkah, merjenih od spodnjega levega kota strani, pri čemer se os Y povečuje navzgor; par 72, 720 postavi besedilo blizu zgornjega levega kota strani velikosti Letter z enopalčnim levim robom. Klic delete Pdf v bloku __finally se izvede ne glede na to, ali so BeginDoc, izrisovanje ali EndDoc sprožili izjemo ali ne.

Izogibajte se klicanju katere koli metode na objektu Pdf po izvedbi delete. Če je kazalec shranjen v spremenljivki člana, jo takoj po brisanju nastavite na nullptr, tako da morebiten kasnejši dostop povzroči jasen sesutje namesto tihe poškodbe pomnilnika.

Konfiguracija projekta

C++Builder najde komponento THotPDF prek kombinacije iskalnih poti za vključitev (include paths), poti knjižnic in direktive pragma. Ustvarjena glava se nahaja poleg HPDFDoc.pas v izvorni mapi HotPDF; dodajte to mapo v Project > Options > C++ Compiler > Include path. Direktiva #pragma link "HPDFDoc" naroči povezovalniku (linkerju), da povleče prevedeno enoto, ne da bi jo morali ročno navesti v projektni datoteki. Če namesto statičnega povezovanja uporabljate paket med delovanjem (runtime package), najprej namestite pakete HotPDF za oblikovanje in izvajanje; pragma še vedno velja.

Ime enote HPDFDoc pustite nespremenjeno. C++Builder izpelje ime datoteke glave iz imena enote Pascal, zato preimenovanje datoteke ali uporaba vzdevka poti v pragmi tiho prekine iskanje.

Obseg in delo z več dokumenti

Za enkraten izvoz, ki se zaključi znotraj ene funkcije, delujeta obe možnosti, vendar imata različne načine odpovedi. Z this kot lastnikom puščanje pomnilnika ni mogoče, dokler se obrazec na koncu zapre; z nullptr pa mora kazalec doseči blok __finally. V praksi je vzorec nullptr skupaj z __finally nekoliko čistejši za kratkotrajne objekte, saj naredi mejo življenjske dobe vidno na prvi pogled in preprečuje, da bi obrazec kopičil lastniške objekte, ki so bili mišljeni kot začasni.

Pri serijskih opravilih, ki zaporedoma ustvarijo veliko dokumentov, je dodelitev ene komponente THotPDF na dokument vredna dodatnih stroškov delovanja. Stanje se ne prenaša med dokumenti, če ni objekta, ki bi ga prenašal, kar odpravi celoten razred občasnih hroščev, ki jih je težko odkriti. Dodelite, ustvarite, izbrišite, ponovite.

Ena od lastnosti, ki se pojavi v več predstavitvenih programih HotPDF, je AutoLaunch, ki odpre ustvarjeno datoteko v sistemskem pregledovalniku PDF takoj po klicu EndDoc. To je uporabno med pisanjem prvega osnutka postavitve. V produkciji jo preskočite: izrecno odprite izhodno pot, potrdite, da datoteka obstaja in ima velikost večjo od nič, zabeležite rezultat in pustite, da potek dela odloči, ali je pregledovalnik potreben. Pri serijskem opravilu bo AutoLaunch odprl eno okno pregledovalnika za vsak dokument in na nekaterih sistemih blokiral postopek, medtem ko bo čakal na zaprtje pregledovalnika.

Komponenta THotPDF in vsi prikazani klici za risanje so del komponente HotPDF za Delphi in C++Builder.