Teknisk artikel

Skapa och frigöra en HotPDF-komponent dynamiskt i C++Builder

Att släppa en THotPDF på ett formulär under designtid är bra för en snabb prototyp, men det binder komponenten till formulärets livstid, vilket sällan är vad produktionskod vill ha. En rapportgenerator som körs en gång per knapptryckning, en tjänsttråd (service thread) som buntar nattliga exporter, en hjälparklass som inte har något formulär alls: i var och en av dessa situationer vill du att komponenten ska existera i exakt den tid ett PDF-jobb tar och sedan försvinna. Det innebär allokering vid körning, och det ändrar två saker som är värda att förstå innan man skriver den första raden: vem äger objektet, och hur rensningen (cleanup) fungerar när något går fel

Ägarsemantik i VCL

Varje konstruktor för en VCL-komponent tar en Owner-parameter av typen TComponent*. Att skicka this (formuläret) registrerar det nya objektet i formulärets lista över ägda komponenter, så om formuläret förstörs medan komponenten fortfarande lever, frigör VCL den automatiskt. Att skicka nullptr innebär ingen ägare: du tar ensamt ansvar för pekaren, och inget kommer att rensa upp den åt dig om ett undantag rullar upp stacken (unwinds the stack) före din uttryckliga delete

För en engångsexport som slutförs inom en enda funktion fungerar båda valen, men de två har olika felbeteenden. Med this som ägare är en läcka omöjlig så länge formuläret så småningom stängs; med nullptr måste pekaren nå ett __finally-block. I praktiken är nullptr plus __finally-mönstret något renare för kortlivade objekt eftersom det gör livstidsgränsen synlig med en blick och undviker att formuläret samlar på sig ägda objekt som var tänkta att vara tillfälliga

Undantagssäker struktur (Exception-safe structure)

PDF-generering kan misslyckas av skäl som inte har något att göra med API:et: utdatakatalogen är skrivskyddad, en typsnittsfil saknas, en ström spolas ut (flushes) i förtid, eller data som anroparen tillhandahåller når en längdgräns. Oavsett orsaken måste rensningsvägen köras. Det idiomatiska C++Builder-sättet att garantera det är 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;
    }
}

Några saker i den listningen är värda att lyfta fram. Ägaren är nullptr, vilket gör livstiden uttrycklig. Compression och FontEmbedding ställs in före BeginDoc: båda är dokumentnivåalternativ som HotPDF bekräftar när dokumentet öppnas, och att tilldela dem i efterhand har ingen effekt. TextOut tar koordinater i punkter (points) mätt från sidans nedre vänstra hörn, Y ökar uppåt; paret 72, 720 placerar texten nära det övre vänstra hörnet på en sida i letter-format med en en-tums vänstermarginal. delete Pdf i __finally-blocket körs oavsett om BeginDoc, ritningen eller EndDoc utlöste ett undantag eller inte

Undvik att anropa någon metod på Pdf efter delete. Om pekaren lagras i en medlemsvariabel, sätt den till nullptr omedelbart efter borttagningen så att en eventuell oavsiktlig senare åtkomst producerar en ren krasch snarare än tyst korruption

Projektkonfiguration

C++Builder hittar THotPDF genom en kombination av inkluderingssökvägar, bibliotekssökvägar och ett pragma-direktiv. Den genererade headern finns tillsammans med HPDFDoc.pas i HotPDF:s källkatalog; lägg till den katalogen i Project > Options > C++ Compiler > Include path. Direktivet #pragma link "HPDFDoc" säger till länkaren att dra in den kompilerade enheten utan att lista den i projektfilen manuellt. Om du använder runtime-paketet istället för statisk länkning, installera HotPDF:s design- och runtime-paket först; pragmat gäller fortfarande

Håll enhetsnamnet HPDFDoc oförändrat. C++Builder härleder headernamnet från Pascal-enhetens namn, så att byta namn på filen eller använda ett sökvägsalias i pragmat bryter uppslaget (lookup) tyst

Omfattning (Scoping) och jobb med flera dokument

För en enskild export som utlöses av en användaråtgärd är en lokal variabel begränsad till knapphanteraren det rätta svaret: den skapas, används och förstörs inom en anropsram, och avsikten är uppenbar för alla som läser koden senare. Alternativet vid designtid är motiverat när samma formulär driver ett kontinuerligt arbetsflöde, till exempel en utskriftsgranskningspanel (print-preview) som bygger om dokumentet när användaren ändrar en inställning; i det fallet är det mindre störande att hålla komponenten vid liv och anropa BeginDoc/EndDoc upprepade gånger än att upprepade gånger allokera och frigöra heap-objekt

För batchjobb som producerar många dokument i sekvens, är en THotPDF per dokument värt allokeringsomkostnaden (overhead). Tillstånd överförs inte mellan dokument om det inte finns något objekt att bära det, och det är en klass av intermittenta buggar som du aldrig behöver felsöka. Allokera, generera, ta bort, upprepa

En egenskap som dyker upp i flera HotPDF-demos är AutoLaunch, som öppnar den genererade filen i systemets PDF-visare omedelbart efter EndDoc. Den är användbar när man skriver det första utkastet av en layout. I produktion, hoppa över den: öppna utdatasökvägen uttryckligen, verifiera att filen existerar och har en storlek över noll, logga resultatet, och låt det anropande arbetsflödet bestämma om en visare är relevant. I ett batchjobb lanserar AutoLaunch ett visarfönster per dokument och blockerar processen på vissa system i väntan på att visaren ska stängas

Komponenten THotPDF och alla ritanrop som visas här är en del av HotPDF-komponenten för Delphi och C++Builder