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

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

Размещение THotPDF на форме во время разработки отлично подходит для быстрого прототипа, но это привязывает компонент к времени жизни формы, что редко бывает нужно в рабочем коде. Генератор отчетов, который запускается один раз по нажатию кнопки, служебный поток, который выполняет пакетный ночной экспорт, вспомогательный класс, у которого вообще нет формы: в каждой из этих ситуаций вы хотите, чтобы компонент существовал ровно столько, сколько длится одно задание PDF, а затем исчезал. Это означает выделение во время выполнения, и это меняет две вещи, которые стоит понять перед написанием первой строки: кто владеет объектом и как выполняется очистка, когда что-то идет не так

Семантика владельца в VCL

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

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

Хронология динамического жизненного цикла THotPDF в C++Builder: от new THotPDF(nullptr) через установку опций, BeginDoc и EndDoc до delete Pdf внутри блока __finally, выполняющегося даже при возбуждении исключения
Runtime-экземпляр THotPDF живёт ровно одно PDF-задание: задайте опции до BeginDoc, рисуйте, закрывайте, а блок __finally пусть выдаёт delete независимо от исключений

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

Генерация 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, 72, 0, L"Hello from C++Builder");
        Pdf->EndDoc();
    }
    __finally
    {
        delete Pdf;
    }
}

Несколько вещей в этом листинге стоит отметить. Владелец — nullptr, что делает время жизни явным. Compression и FontEmbedding выставляются до BeginDoc: оба — опции уровня документа, которые HotPDF фиксирует при открытии документа, и присвоение их после эффекта не имеет. TextOut берёт координаты в пунктах, отсчитываемых от левого верхнего угла страницы, Y растёт вниз, так что пара 72, 72 ставит текст на дюйм от верхнего и левого краёв. delete Pdf в блоке __finally выполняется независимо от того, подняли исключение BeginDoc, рисование или EndDoc

Избегайте вызова любых методов на Pdf после delete. Если указатель хранится в переменной-члене, сразу после освобождения выставляйте его в nullptr, чтобы любое случайное позднее обращение давало чистый крах, а не тихую порчу памяти

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

C++Builder находит THotPDF через комбинацию путей включения, путей к библиотекам и директивы pragma. Сгенерированный заголовочный файл находится рядом с HPDFDoc.pas в каталоге исходных кодов HotPDF; добавьте этот каталог в Project > Options > C++ Compiler > Include path. Директива #pragma link "HPDFDoc" указывает компоновщику подтянуть скомпилированный модуль без добавления его в файл проекта вручную. Если вы используете пакет времени выполнения вместо статической линковки, сначала установите design-time и runtime пакеты HotPDF; pragma по-прежнему применяется

Оставьте имя модуля HPDFDoc без изменений. C++Builder получает имя заголовка из имени модуля Pascal, поэтому переименование файла или использование псевдонима пути в pragma незаметно ломает поиск

Область видимости и многодокументные задания

Для одиночного экспорта, запускаемого действием пользователя, правильным ответом будет локальная переменная, ограниченная обработчиком кнопки: она создается, используется и уничтожается в пределах одного кадра вызова, и намерение очевидно для любого, кто позже будет читать код. Альтернатива времени разработки оправдана, когда одна и та же форма управляет непрерывным рабочим процессом, например панелью предварительного просмотра печати, которая перестраивает документ всякий раз, когда пользователь изменяет настройку; в этом случае сохранение активности компонента и повторные вызовы BeginDoc/EndDoc менее разрушительны, чем многократное выделение и освобождение объектов в куче

Для пакетных заданий, которые производят много документов последовательно, ограничение области видимости одного THotPDF на документ стоит затрат на выделение памяти. Состояние не переносится между документами, если нет объекта, который его переносит, и это один класс периодических ошибок, которые вам никогда не придется отлаживать. Выделить, сгенерировать, удалить, повторить

Одно свойство, которое появляется в нескольких демо-версиях HotPDF — это AutoLaunch, которое открывает сгенерированный файл в системном средстве просмотра PDF сразу после EndDoc. Это полезно при написании первого черновика макета. В рабочем коде пропустите его: откройте выходной путь явно, проверьте, существует ли файл и имеет ли он ненулевой размер, запишите результат в журнал и позвольте вызывающему рабочему процессу решить, уместна ли программа просмотра. В пакетном задании AutoLaunch запускает одно окно просмотра на документ и заблокирует процесс в некоторых системах в ожидании закрытия программы просмотра

Диаграмма пакетного цикла: выделение одного свежего THotPDF на документ и освобождение после EndDoc, чтобы состояние не утекало между проходами, с предупреждением против включения AutoLaunch в производственных PDF-экспортах
Создавайте один свежий THotPDF на пакетный документ, чтобы состояние не утекало между проходами, и держите AutoLaunch выключенным, чтобы бродячие окна просмотрщика не тормозили задание

Компонент THotPDF и все показанные здесь вызовы отрисовки являются частью компонента HotPDF для Delphi и C++Builder