Додавання THotPDF на форму під час розробки (design time) цілком підходить для швидкого прототипу, але це прив'язує компонент до тривалості життя форми, що рідко є бажаним у робочому коді. Генератор звітів, що запускається один раз після натискання кнопки, сервісний потік, який пакетно експортує дані вночі, допоміжний клас, який взагалі не має форми: у кожній із цих ситуацій вам потрібно, щоб компонент існував рівно стільки, скільки триває одне створення PDF-файлу, а потім зникав. Це означає виділення пам'яті під час виконання (runtime allocation), і воно змінює дві речі, які варто зрозуміти перед написанням першого рядка: хто є власником об'єкта, і як відбувається очищення, якщо щось іде не так
Семантика власника у VCL
Кожен конструктор компонента VCL приймає параметр Owner типу TComponent*. Передача this (форми) реєструє новий об'єкт у списку компонентів, що належать формі, тому якщо форма буде знищена, поки компонент ще існує, VCL автоматично його звільнить. Передача nullptr означає відсутність власника: ви берете на себе повну відповідальність за вказівник, і ніщо не очистить його за вас, якщо виняток (exception) розгорне стек до вашого явного виклику delete
Для одноразового експорту, який завершується в межах однієї функції, підійде будь-який варіант, але вони мають різні сценарії збоїв. Якщо власником є this, витік пам'яті неможливий, доки форма зрештою не закриється; з nullptr вказівник обов'язково має досягти блоку __finally. На практиці шаблон nullptr плюс __finally є трохи чистішим для короткоживучих об'єктів, оскільки він робить межі тривалості життя видимими з першого погляду та запобігає накопиченню у формі об'єктів, які мали бути тимчасовими
Структура, безпечна до винятків (Exception-safe)
Генерація 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 приймає координати в пунктах, що вимірюються від нижнього лівого кута сторінки, причому Y збільшується вгору; пара 72, 720 розміщує текст біля верхнього лівого краю сторінки формату Letter із відступом один дюйм ліворуч. Команда delete Pdf у блоці __finally виконується незалежно від того, чи викликали виняток функції BeginDoc, малювання або EndDoc
Уникайте виклику будь-яких методів для Pdf після delete. Якщо вказівник зберігається у змінній-члені класу, встановіть його на nullptr одразу після видалення, щоб будь-яке випадкове звернення згодом спричинило явний збій, а не приховане пошкодження пам'яті
Конфігурація проєкту
C++Builder знаходить THotPDF через комбінацію шляхів до файлів заголовків (include paths), шляхів до бібліотек та директиви pragma. Згенерований файл заголовка знаходиться поруч із HPDFDoc.pas у каталозі вихідного коду HotPDF; додайте цей каталог до Project > Options > C++ Compiler > Include path. Директива #pragma link "HPDFDoc" вказує компонувальнику (linker) підключити скомпільований модуль без необхідності вручну вказувати його у файлі проєкту. Якщо ви використовуєте пакет часу виконання (runtime package) замість статичного компонування, спочатку встановіть пакети розробки та часу виконання HotPDF; директива pragma все одно застосовується
Залишайте назву модуля HPDFDoc без змін. C++Builder створює назву файлу заголовка на основі назви модуля Pascal, тому перейменування файлу або використання псевдоніма шляху в pragma призведе до тихої помилки пошуку
Область видимості та завдання з кількома документами
Для одного експорту, ініційованого дією користувача, правильною відповіддю буде локальна змінна, область видимості якої обмежена обробником кнопки: вона створюється, використовується і знищується в межах одного кадру виклику, а її призначення очевидне для всіх, хто пізніше читатиме код. Альтернатива на етапі розробки (design-time) є виправданою, коли та сама форма керує безперервним робочим процесом, як-от панель попереднього перегляду друку, яка перебудовує документ щоразу, коли користувач змінює налаштування; у такому разі підтримка компонента в активному стані та багаторазовий виклик BeginDoc/EndDoc є менш витратними, ніж багаторазове виділення та звільнення об'єктів у купі (heap)
Для пакетних завдань, які створюють багато документів поспіль, створення окремого THotPDF для кожного документа варте накладних витрат на виділення пам'яті. Стан не переноситься між документами, якщо немає об'єкта для його перенесення, і це один із класів плаваючих помилок, які вам ніколи не доведеться відлагоджувати. Виділити пам'ять, згенерувати, видалити, повторити
Однією з властивостей, яка з'являється в кількох демонстраціях HotPDF, є AutoLaunch. Вона відкриває згенерований файл у системній програмі перегляду PDF одразу після EndDoc. Це корисно під час написання першої чернетки макета. У робочому коді краще цього уникати: відкривайте цільовий шлях явно, перевіряйте, чи існує файл і чи має він ненульовий розмір, записуйте результат у журнал і дозволяйте викликаючому робочому процесу вирішувати, чи потрібна програма перегляду. У пакетному завданні AutoLaunch запускає одне вікно програми перегляду на кожен документ і на деяких системах блокуватиме процес, очікуючи закриття програми перегляду
Компонент THotPDF та всі показані тут виклики малювання є частиною HotPDF Component для Delphi та C++Builder