Å slippe en THotPDF på et skjema (form) i utformingstid er greit for en rask prototype, men det binder komponenten til skjemaets levetid, som sjelden er det produksjonskode ønsker. En rapportgenerator som kjører én gang per knappetrykk, en tjenestetråd som samler eksporter for natten, en hjelperklasse som ikke har noe skjema i det hele tatt: i hver av disse situasjonene vil du at komponenten skal eksistere nøyaktig like lenge som én PDF-jobb og deretter forsvinne. Det betyr allokering under kjøretid, og det endrer to ting som er verdt å forstå før du skriver den første linjen: hvem som eier objektet, og hvordan opprydding kjøres når noe går galt
Eierskapssemantikk i VCL
Hver VCL-komponents konstruktør tar en Owner-parameter av typen TComponent*. Å sende this (skjemaet) registrerer det nye objektet med skjemaets liste over eide komponenter, så hvis skjemaet ødelegges mens komponenten fortsatt er i live, frigjør VCL den automatisk. Å sende nullptr betyr at det ikke er noen eier: du tar eneansvaret for pekeren, og ingenting vil rydde den opp for deg hvis et unntak pakker ut (unwinds) stakken før ditt eksplisitte delete
For en engangseksport som fullføres innenfor en enkelt funksjon fungerer begge valg, men de to har forskjellige feilmodi. Med this som eier er en lekkasje umulig så lenge skjemaet til slutt lukkes; med nullptr må pekeren nå en __finally-blokk. I praksis er mønsteret nullptr pluss __finally litt renere for kortlivede objekter fordi det gjør levetidsgrensen synlig ved et øyekast, og unngår at skjemaet akkumulerer eide objekter som var ment å være midlertidige
Unntakssikker struktur
PDF-generering kan mislykkes av årsaker som ikke har noe å gjøre med API-et: utdatakatalogen er skrivebeskyttet, en fontfil mangler, en strøm (stream) tømmes (flushes) for tidlig, eller brukerleverte data treffer en lengdegrense. Uansett årsak, oppryddingsbanen må kjøre. Den idiomatiske C++Builder-måten å garantere det på er 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;
}
}
Noen få ting i denne kodesnutten er verdt å peke på. Eieren er nullptr, noe som gjør levetiden eksplisitt. Compression og FontEmbedding settes før BeginDoc: begge er alternativer på dokumentnivå som HotPDF forplikter (commits) når dokumentet åpnes, og å tilordne dem etterpå har ingen effekt. TextOut tar koordinater i punkter målt fra nederste venstre hjørne av siden, hvor Y øker oppover; paret 72, 720 plasserer tekst nær toppen til venstre av en side i Letter-størrelse med én tommes venstremarg. delete Pdf i __finally-blokken kjører uavhengig av om BeginDoc, tegning eller EndDoc utløste et unntak eller ikke
Unngå å kalle noen metoder på Pdf etter delete. Hvis pekeren er lagret i en medlemsvariabel, sett den til nullptr umiddelbart etter sletting, slik at enhver utilsiktet senere tilgang produserer en ren krasj fremfor stille korrupsjon
Prosjektoppsett
C++Builder finner THotPDF gjennom en kombinasjon av inkluderingsbaner, bibliotekbaner og et pragma-direktiv. Den genererte header-filen ligger sammen med HPDFDoc.pas i HotPDF-kildekatalogen; legg den katalogen til under Project > Options > C++ Compiler > Include path. Direktivet #pragma link "HPDFDoc" ber lenkeren (linker) om å trekke inn den kompilerte enheten uten å liste den manuelt i prosjektfilen. Hvis du bruker kjøretidspakken (runtime package) i stedet for statisk lenking, installerer du HotPDF-design og kjøretidspakkene først; pragma-direktivet gjelder fortsatt
Hold enhetsnavnet HPDFDoc uendret. C++Builder avleder header-navnet fra Pascal-enhetsnavnet, så å gi filen nytt navn eller bruke et bane-alias i pragma-direktivet bryter oppslaget i stillhet
Virkeområde (scoping) og fler-dokumentjobber
For en enkelt eksport utløst av en brukerhandling, er en lokal variabel avgrenset til knappebehandleren det riktige svaret: den opprettes, brukes og ødelegges innenfor én anropsramme (call frame), og hensikten er åpenbar for alle som leser koden senere. Alternativet i utformingstid (design-time) er berettiget når det samme skjemaet driver en kontinuerlig arbeidsflyt, for eksempel et utskriftsforhåndsvisningspanel som gjenoppbygger dokumentet hver gang brukeren endrer en innstilling; i det tilfellet er det å holde komponenten i live og kalle BeginDoc/EndDoc gjentatte ganger mindre forstyrrende enn gjentatt allokering og frigjøring av heap-objekter
For batch-jobber som produserer mange dokumenter i sekvens, er det å opprette én THotPDF per dokument verdt allokeringsoverheaden. Tilstand (state) overføres ikke mellom dokumenter hvis det ikke er noe objekt å bære det over, og det er én type periodisk (intermittent) feil du aldri trenger å feilsøke. Alloker, generer, slett, gjenta
Én egenskap som dukker opp i flere HotPDF-demoer er AutoLaunch, som åpner den genererte filen i systemets PDF-visningsapp umiddelbart etter EndDoc. Det er nyttig mens man skriver det første utkastet av en layout. I produksjon bør du hoppe over det: åpne utdatabanen eksplisitt, bekreft at filen eksisterer og har en størrelse over null, loggfør resultatet, og la den kallende arbeidsflyten bestemme om en visningsapp er relevant. I en batch-jobb starter AutoLaunch ett visningsvindu per dokument og vil på noen systemer blokkere prosessen mens den venter på at visningsappen skal lukkes
THotPDF-komponenten og alle tegningsanrop som vises her, er en del av HotPDF-komponenten for Delphi og C++Builder