Техническа статия

Създаване и освобождаване на HotPDF компонент динамично в C++Builder

Пускането на THotPDF във форма по време на проектиране (design time) е добре за бърз прототип, но това обвързва компонента с жизнения цикъл на формата, което рядко е това, което продукционният код иска. Генератор на отчети, който се изпълнява веднъж при кликване на бутон, сервизна нишка (service thread), която обработва групово (batches) нощни експортирания, помощна класа, която изобщо няма форма: във всяка от тези ситуации искате компонентът да съществува точно за продължителността на една PDF задача и след това да изчезне. Това означава алокиране по време на изпълнение и променя две неща, които си струва да разберете, преди да напишете първия ред: кой притежава обекта и как протича почистването, когато нещо се обърка

Семантика на собственика във VCL

Конструкторът на всеки VCL компонент приема параметър Owner от тип TComponent*. Подаването на this (формата) регистрира новия обект в списъка на формата с притежавани компоненти, така че ако формата бъде унищожена, докато компонентът е все още жив, VCL го освобождава автоматично. Подаването на nullptr означава, че няма собственик: вие поемате еднолична отговорност за указателя и нищо няма да го почисти вместо вас, ако изключение (exception) развие стека преди вашия изричен delete

За еднократен (one-shot) експорт, който завършва в рамките на една функция, и двата избора работят, но двата имат различни режими на отказ (failure modes). При this като собственик, изтичане (leak) е невъзможно, стига формата в крайна сметка да се затвори; при nullptr, указателят трябва да достигне до блок __finally. На практика, моделът nullptr плюс __finally е малко по-чист за краткотрайни обекти, защото прави границата на жизнения цикъл видима с един поглед и избягва натрупването на притежавани обекти във формата, които е трябвало да бъдат временни

Безопасна за изключения структура

Генерирането на PDF може да се провали по причини, които нямат нищо общо с API: изходната директория е само за четене, липсва файл с шрифт, поток (stream) се изпразва преждевременно или предоставените от извикващия данни достигат ограничение за дължина. Каквато и да е причината, пътят за почистване трябва да се изпълни. Идиоматичният начин в C++Builder да се гарантира това е 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;
    }
}

Няколко неща в този списък си струва да бъдат отбелязани. Собственикът е nullptr, което прави жизнения цикъл изричен. Compression и FontEmbedding се задават преди BeginDoc: и двете са опции на ниво документ, които HotPDF записва, когато документът се отвори, и присвояването им след това няма ефект. TextOut приема координати в точки (points), измерени от долния ляв ъгъл на страницата, като Y се увеличава нагоре; двойката 72, 720 поставя текста близо до горния ляв ъгъл на страница с размер Letter с един инч ляво поле. delete Pdf в блока __finally се изпълнява независимо дали BeginDoc, рисуването или EndDoc са повдигнали изключение или не

Избягвайте извикването на какъвто и да е метод върху Pdf след delete. Ако указателят се съхранява в променлива на член (member variable), задайте я на nullptr веднага след изтриването, така че всеки случаен по-късен достъп да доведе до чист срив, вместо до безшумно повреждане

Конфигурация на проекта

C++Builder локализира THotPDF чрез комбинация от пътеки за включване (include paths), пътеки към библиотеки (library paths) и прагма директива (pragma directive). Генерираният хедър живее заедно с HPDFDoc.pas в директорията с изходния код на HotPDF; добавете тази директория към Project > Options > C++ Compiler > Include path. Директивата #pragma link "HPDFDoc" казва на линкера да изтегли компилирания модул (unit), без да го изброява ръчно във файла на проекта. Ако използвате рънтайм пакета (runtime package) вместо статично свързване, първо инсталирайте HotPDF пакетите за проектиране и рънтайм; прагмата все още важи

Запазете името на модула HPDFDoc непроменено. C++Builder извлича името на хедъра от името на Pascal модула, така че преименуването на файла или използването на псевдоним на пътя (path alias) в прагмата нарушава търсенето безшумно

Обхват и многодокументни задачи

За единичен експорт, задействан от потребителско действие, локална променлива, обхваната в манипулатора на бутона (button handler), е правилният отговор: тя се създава, използва и унищожава в рамките на една рамка на извикване (call frame), а намерението е очевидно за всеки, който чете кода по-късно. Алтернативата по време на проектиране е оправдана, когато същата форма задвижва непрекъснат работен процес (continuous workflow), като например панел за предварителен преглед на печат (print-preview panel), който изгражда отново документа, когато потребителя промени настройка; в този случай запазването на компонента жив и многократното извикване на BeginDoc/EndDoc е по-малко разрушително от многократното алокиране и освобождаване на обекти в хийпа (heap objects)

За пакетни задачи (batch jobs), които произвеждат много документи последователно, обхващането на един THotPDF за всеки документ си струва допълнителното време за алокиране (allocation overhead). Състоянието не се прехвърля между документите, ако няма обект, който да го носи, и това е един клас от периодични бъгове (intermittent bugs), които никога няма да се налага да отстранявате. Алокиране, генериране, изтриване, повтаряне

Едно свойство, което се появява в няколко HotPDF демонстрации, е AutoLaunch, което отваря генерирания файл в системния PDF viewer веднага след EndDoc. То е полезно, докато пишете първата чернова на макет (layout). В продукция го пропуснете: отворете изходния път изрично, проверете дали файлът съществува и има размер, различен от нула, логнете (log) резултата и оставете извикващия работен процес да реши дали визуализаторът (viewer) е уместен. При пакетна задача, AutoLaunch стартира по един прозорец на визуализатор на документ и ще блокира процеса на някои системи в очакване визуализаторът да се затвори

Компонентът THotPDF и всички показани тук извиквания за рисуване са част от компонента HotPDF за Delphi и C++Builder