Tehnički članak

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

Prebacivanje (dropping) THotPDF komponente na formu u vreme dizajna (design time) je u redu za brzi prototip, ali vezuje komponentu za životni vek forme, što produkcijski kod retko želi. Generator izveštaja koji se pokreće jednom po kliku na dugme, servisna nit (service thread) koja grupiše noćne izvoze, pomoćna klasa (helper class) koja uopšte nema formu: u svakoj od tih situacija želite da komponenta postoji tačno za vreme trajanja jednog PDF posla i da zatim nestane. To znači alokaciju tokom izvršavanja (runtime allocation), a ona menja dve stvari vredne razumevanja pre pisanja prve linije: ko je vlasnik objekta i kako se odvija čišćenje kada nešto krene naopako

Semantika vlasnika u VCL-u

Svaki konstruktor VCL komponente prihvata parametar Owner tipa TComponent*. Prosleđivanje this (forma) registruje novi objekat na forminu listu komponenti u vlasništvu, tako da ako je forma uništena dok je komponenta još uvek živa, VCL je oslobađa automatski. Prosleđivanje nullptr znači da nema vlasnika: vi preuzimate isključivu odgovornost za pokazivač i ništa ga neće očistiti umesto vas ako izuzetak odmota stek pre vašeg eksplicitnog poziva delete

Za jednokratni (one-shot) izvoz koji se završava unutar jedne funkcije, oba izbora rade, ali imaju različite načine neuspeha (failure modes). Sa this kao vlasnikom, curenje memorije je nemoguće sve dok se forma na kraju zatvori; sa nullptr, pokazivač mora stići do bloka __finally. U praksi, obrazac nullptr plus __finally je malo čistiji za objekte kratkog veka jer čini granicu životnog veka vidljivom na prvi pogled i izbegava da forma akumulira objekte u vlasništvu za koje je bilo predviđeno da budu privremeni

Struktura otporna na izuzetke (Exception-safe)

Generisanje PDF-a može propasti iz razloga koji nemaju nikakve veze sa API-jem: izlazni direktorijum je samo za čitanje, datoteka sa fontom nedostaje, tok (stream) se prazni (flushes) pre vremena, ili podaci koje je prosledio pozivalac dosegnu ograničenje dužine. Šta god da je uzrok, putanja čišćenja mora da se izvrši. Idiomatski C++Builder način da se to garantuje jeste 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 na tom spisku vredi istaći. Vlasnik je nullptr, čime životni vek postaje eksplicitan. Compression i FontEmbedding su postavljeni pre poziva BeginDoc: to su opcije na nivou dokumenta koje HotPDF primenjuje kada se dokument otvori i njihovo dodeljivanje naknadno nema efekta. TextOut prihvata koordinate u tačkama izmerene od donjeg levog ugla stranice, pri čemu se Y povećava nagore; par vrednosti 72, 720 postavlja tekst blizu gornjeg levog ugla stranice Letter formata 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 pokrenuli izuzetak ili ne

Izbegavajte pozivanje bilo koje metode nad promenljivom Pdf nakon brisanja. Ako je pokazivač sačuvan u promenljivoj članici (member variable), postavite ga na nullptr neposredno nakon brisanja kako bi eventualni slučajni kasniji pristup prouzrokovao čist pad sistema radije nego tihu korupciju memorije

Konfiguracija projekta

C++Builder pronalazi THotPDF kroz kombinaciju include putanja, library (bibliotečkih) putanja i pragma direktive. Generisana zaglavlja (header) žive pored datoteke HPDFDoc.pas u direktorijumu izvornog koda HotPDF-a; dodajte taj direktorijum u Project > Options > C++ Compiler > Include path. Direktiva #pragma link "HPDFDoc" govori linkeru da povuče prevedenu jedinicu (compiled unit) a da je pritom ručno ne navedete u projektnoj datoteci. Ako koristite paket za vreme izvršavanja (runtime package) umesto statičkog povezivanja, prvo instalirajte HotPDF pakete za dizajn i izvršavanje; pragma i dalje važi

Neka ime jedinice HPDFDoc ostane nepromenjeno. C++Builder izvodi ime zaglavlja iz Pascal imena jedinice, tako da preimenovanje datoteke ili korišćenje pseudonima putanje (path alias) u pragmi u tišini prekida pretragu (lookup)

Podešavanje obima (Scoping) i poslovi sa više dokumenata

Za pojedinačni izvoz koji se pokreće akcijom korisnika, lokalna promenljiva ograničena (scoped) na hendler dugmeta je pravi odgovor: kreira se, koristi i uništava unutar jednog okvira poziva, a namera je očigledna svima koji kasnije čitaju kod. Alternativa u vreme dizajna (design-time) je opravdana kada ista forma pokreće neprekidan tok posla (workflow), kao što je panel za pregled štampe koji obnavlja dokument kad god korisnik promeni neko podešavanje; u tom slučaju, održavanje komponente u životu i ponavljano pozivanje metoda BeginDoc/EndDoc izaziva manje ometanja od uzastopne alokacije i oslobađanja objekata na heap-u (heap objects)

Za grupne (batch) poslove koji proizvode mnoge dokumente u nizu, postavljanje obima jedne komponente THotPDF po dokumentu je vredno (overhead) troška same alokacije. Stanje se ne prenosi između dokumenata ako nema objekta da ga prenese, a to je jedna klasa naizmeničnih grešaka (intermittent bug) koje nikada nećete morati da debagujete. Alociraj, generiši, izbriši, ponovi

Svojstvo koje se pojavljuje u nekoliko HotPDF demonstracija (demos) je AutoLaunch, a ono otvara generisanu datoteku u sistemskom PDF čitaču odmah nakon poziva metode EndDoc. Veoma je korisno dok pišete prvu radnu verziju izgleda (layout). U produkciji to preskočite: eksplicitno otvorite izlaznu putanju, potvrdite da datoteka postoji i ima veličinu veću od nule, zabeležite rezultat i prepustite pozivnom toku posla (calling workflow) da odluči da li je pregledač relevantan. U grupnom (batch) poslu, opcija AutoLaunch pokreće po jedan prozor za pregled po dokumentu i blokiraće proces na nekim sistemima čekajući da se pregledač zatvori

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