Technical Article

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

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

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

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

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

Структура, устойчива на изключения

Генерирането на PDF може да се провали по причини, които нямат нищо общо с API: изходната директория е само за четене, липсва шрифт, потокът се изчиства преждевременно или предоставените от извикващия данни надвишават ограничението за дължина. Каквато и да е причината, пътят за почистване трябва да се изпълни. Специфичният за 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. Ако указателят е запазен в член-променлива на класа, задайте му стойност nullptr веднага след изтриването, така че всеки случаен последващ достъп да предизвика ясен срив вместо скрита повреда на данни.

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

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

Оставете името на модула HPDFDoc непроменено. C++Builder извлича името на хедъра от името на Pascal модула, така че преименуването на файла или използването на псевдоним на път в прагмата ще наруши търсенето без никакво предупреждение.

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

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

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

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

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