Technical Article

Dinamičko kreiranje i oslobađanje HotPDF komponente u C++Builder-u

Postavljanje komponente THotPDF na formu u vreme dizajniranja (design-time) je sasvim u redu za brzi prototip, ali to povezuje životni vek komponente sa životnim vekom forme, što produkcioni kod retko želi. Generator izveštaja koji se pokreće jednom na klik dugmeta, servisna nit (service thread) koja vrši grupni izvoz preko noći, pomoćna klasa koja uopšte nema formu: u svakoj od tih situacija želite da komponenta postoji tačno tokom trajanja jednog PDF posla i da potom nestane. To podrazumeva alokaciju u vreme izvršavanja (runtime), a to menja dve stvari koje vredi razumeti pre pisanja prve linije koda: ko je vlasnik objekta i kako se vrši čišćenje kada nešto krene naopako.

Semantika vlasništva u VCL-u

Svaki konstruktor VCL komponente prihvata parametar Owner tipa TComponent*. Prosleđivanje pokazivača this (forma) registruje novi objekat u listi komponenti u vlasništvu forme, tako da ako se forma uništi dok je komponenta još uvek aktivna, VCL je automatski oslobađa. Prosleđivanje vrednosti nullptr znači da nema vlasnika: preuzimate isključivu odgovornost za pokazivač i ništa ga neće počistiti umesto vas ako izuzetak odmota stog (unwinds the stack) pre vašeg eksplicitnog poziva operatora delete.

Za jednokratni izvoz koji se završava unutar jedne funkcije, bilo koji izbor radi posao, ali ova dva pristupa imaju različite načine otkazivanja. Sa this kao vlasnikom, curenje memorije je nemoguće sve dok se forma na kraju ne zatvori; sa nullptr, pokazivač mora stići do __finally bloka. U praksi, obrazac nullptr plus __finally je malo čistiji za kratkovečne objekte jer čini granicu životnog veka vidljivom na prvi pogled i sprečava da forma akumulira objekte u vlasništvu koji su trebali biti privremeni.

Struktura bezbedna od izuzetaka

Generisanje PDF-a može otkazati iz razloga koji nemaju veze sa API-jem: direktorijum za izlaz je samo za čitanje (read-only), datoteka fonta nedostaje, tok se prerano isprazni ili podaci koje je pozivalac dostavio prelaze ograničenje dužine. Bez obzira na uzrok, putanja čišćenja mora da se izvrši. Karakterističan način u C++Builder-u da se to garantuje je upotreba 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;
    }
}

Nekoliko stvari u ovom primeru vredi istaći. Vlasnik je nullptr, što životni vek čini eksplicitnim. Svojstva Compression i FontEmbedding se podešavaju pre poziva BeginDoc: obe su opcije na nivou dokumenta koje HotPDF primenjuje kada se dokument otvori, a njihovo dodeljivanje nakon toga nema efekta. TextOut prihvata koordinate u tačkama (points) merenim od donjeg levog ugla stranice, gde Y raste nagore; par 72, 720 postavlja tekst blizu gornjeg levog ugla stranice veličine Letter sa levom marginom od jednog inča. Poziv delete Pdf u __finally bloku se izvršava bez obzira na to da li su BeginDoc, crtanje ili EndDoc izazvali izuzetak.

Izbegavajte pozivanje bilo koje metode na objektu Pdf nakon brisanja. Ako se pokazivač čuva u varijabli članici, postavite ga na nullptr odmah nakon brisanja, tako da eventualni slučajni kasniji pristup izazove jasan pad programa umesto tihe korupcije memorije.

Konfiguracija projekta

C++Builder locira THotPDF kroz kombinaciju include putanja, putanja biblioteka i pragma direktive. Generisano zaglavlje se nalazi pored HPDFDoc.pas u izvornom direktorijumu HotPDF-a; dodajte taj direktorijum u Project > Options > C++ Compiler > Include path. Direktiva #pragma link "HPDFDoc" govori linkeru da uveze kompajliranu jedinicu bez ručnog navođenja u datoteci projekta. Ako koristite paket u vreme izvršavanja (runtime package) umesto statičkog povezivanja, prvo instalirajte HotPDF pakete za dizajniranje i izvršavanje; pragma i dalje važi.

Zadržite naziv jedinice HPDFDoc nepromenjenim. C++Builder izvodi ime zaglavlja iz naziva Pascal jedinice, tako da preimenovanje datoteke ili korišćenje alijasa putanje u pragmi tiho prekida pronalaženje.

Opseg i poslovi sa više dokumenata

Za pojedinačni izvoz pokrenut akcijom korisnika, lokalna varijabla ograničena na rukovalac dugmeta je pravo rešenje: kreira se, koristi i uništava unutar jednog okvira poziva, a namera je očigledna svakome ko kasnije čita kod. Alternativa u vreme dizajniranja je opravdana kada ista forma upravlja kontinualnim tokom rada, kao što je panel za pregled štampe koji ponovo gradi dokument kad god korisnik promeni podešavanje; u tom slučaju, održavanje komponente aktivnom i višekratno pozivanje BeginDoc/EndDoc je manje zahtevno od stalnog alociranja i oslobađanja objekata na hipu.

Za grupne poslove (batch jobs) koji proizvode mnogo dokumenata u nizu, definisanje opsega od jedne THotPDF instance po dokumentu vredi dodatnih troškova alokacije. Stanje se ne prenosi između dokumenata ako ne postoji objekat koji bi ga preneo, što eliminiše jednu čitavu klasu povremenih grešaka koje nikada nećete morati da debug-ujete. Alocirajte, generišite, obrišite, ponovite.

Jedno svojstvo koje se pojavljuje u nekoliko HotPDF demonstracija jeste AutoLaunch, koje otvara generisanu datoteku u sistemskom PDF čitaču odmah nakon EndDoc. Korisno je dok pišete prvu verziju rasporeda elemenata. U produkciji ga preskočite: eksplicitno otvorite izlaznu putanju, potvrdite da datoteka postoji i da ima veličinu veću od nule, zabeležite rezultat u log i prepustite pozivajućem toku rada da odluči da li je otvaranje čitača relevantno. U grupnom poslu, AutoLaunch pokreće po jedan prozor čitača za svaki dokument i na nekim sistemima će blokirati proces čekajući da se čitač zatvori.

Komponenta THotPDF i svi pozivi za crtanje prikazani ovde deo su HotPDF komponente za Delphi i C++Builder.