مقاله فنی

ایجاد و آزادسازی پویای یک کامپوننت HotPDF در C++Builder

رها کردن یک THotPDF روی فرم در زمان طراحی (design time) برای یک نمونه اولیه سریع (prototype) خوب است، اما کامپوننت را به طول عمر فرم گره می‌زند، که به ندرت آن چیزی است که کد تولیدی (production code) می‌خواهد. یک تولیدکننده گزارش که با هر بار کلیک دکمه اجرا می‌شود، یک رشته سرویس که خروجی‌های شبانه را دسته‌ای انجام می‌دهد، یا یک کلاس کمکی که اصلاً فرمی ندارد: در هر یک از این موقعیت‌ها شما می‌خواهید کامپوننت دقیقاً به اندازه مدت زمان یک کار PDF وجود داشته باشد و سپس ناپدید شود. این به معنای تخصیص در زمان اجراست (runtime allocation) و دو چیز را تغییر می‌دهد که پیش از نوشتن اولین خط ارزش درک کردن را دارد: چه کسی مالک شی است و در صورت بروز خطا پاکسازی (cleanup) چگونه اجرا می‌شود

مفاهیم مالک در VCL

هر سازنده (constructor) کامپوننت VCL یک پارامتر Owner از نوع TComponent* دریافت می‌کند. پاس دادن this (فرم) شی جدید را در لیست کامپوننت‌های متعلق به فرم ثبت می‌کند، بنابراین اگر فرم از بین برود در حالی که کامپوننت هنوز زنده است، VCL به طور خودکار آن را آزاد می‌کند. پاس دادن nullptr به معنای بدون مالک است: شما به تنهایی مسئولیت اشاره‌گر را بر عهده می‌گیرید، و اگر پیش از delete صریح شما یک استثنا پشته (stack) را باز کند، هیچ چیز آن را برای شما پاک نخواهد کرد

برای یک عملیات خروجی‌گیری یک‌باره (one-shot export) که در یک تابع منفرد تکمیل می‌شود، هر دو انتخاب کار می‌کنند، اما این دو حالت‌های خطای متفاوتی دارند. در حالی که this مالک است، تا زمانی که فرم در نهایت بسته شود، نشت حافظه غیرممکن است؛ با nullptr، اشاره‌گر باید به یک بلوک __finally برسد. در عمل، الگوی nullptr به علاوه __finally برای اشیای با طول عمر کوتاه اندکی تمیزتر است زیرا مرز طول عمر را در یک نگاه قابل مشاهده می‌سازد و از انباشته شدن اشیای متعلق به فرم که قرار بوده موقتی باشند جلوگیری می‌کند

ساختار ایمن در برابر استثنا (Exception-safe structure)

تولید 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 مختصات را در نقاط اندازه‌گیری شده از گوشه پایین سمت چپ صفحه دریافت می‌کند، جایی که Y به سمت بالا افزایش می‌یابد؛ جفت 72، 720 متن را نزدیک گوشه بالا سمت چپ یک صفحه با اندازه نامه (letter-size) و با یک حاشیه یک اینچی در سمت چپ قرار می‌دهد. دستور delete Pdf در بلوک __finally اجرا می‌شود، صرف نظر از اینکه BeginDoc، رسم کردن یا EndDoc یک استثنا ایجاد کرده باشند یا خیر

از فراخوانی هر متدی روی Pdf پس از delete خودداری کنید. اگر اشاره‌گر در یک متغیر عضو (member variable) ذخیره شده است، بلافاصله پس از حذف آن را به nullptr تنظیم کنید تا هرگونه دسترسی تصادفی بعدی منجر به یک کرش تمیز به جای خرابی خاموش و پنهان شود

پیکربندی پروژه

C++Builder THotPDF را از طریق ترکیبی از مسیرهای include، مسیرهای کتابخانه و یک دستور پرگما (pragma directive) پیدا می‌کند. هدر تولید شده در کنار HPDFDoc.pas در دایرکتوری منبع HotPDF قرار دارد؛ آن دایرکتوری را به مسیر Project > Options > C++ Compiler > Include path اضافه کنید. دستور #pragma link "HPDFDoc" به پیوند دهنده (linker) می‌گوید که یونیت کامپایل شده را بدون ذکر نام آن در فایل پروژه به صورت دستی وارد کند. اگر به جای لینک استاتیک (static linking) از بسته‌های زمان اجرا استفاده می‌کنید، ابتدا بسته‌های زمان طراحی و زمان اجرای HotPDF را نصب کنید؛ پرگما همچنان اعمال می‌شود

نام یونیت HPDFDoc را دست‌نخورده نگه دارید. C++Builder نام هدر را از نام یونیت פסکل (Pascal) استخراج می‌کند، بنابراین تغییر نام فایل یا استفاده از نام مستعار مسیر در پرگما، جستجو را در سکوت متوقف می‌کند

حوزه فعالیت و کارهای چندسندی

برای یک عملیات خروجی‌گیری منفرد که توسط کاربر فعال شده است، یک متغیر محلی محدود به کنترل‌کننده دکمه (button handler) پاسخ مناسب است: در یک فریم فراخوانی ایجاد شده، استفاده شده و از بین می‌رود و این قصد برای هر کسی که بعداً کد را می‌خواند آشکار است. جایگزین زمان طراحی زمانی توجیه دارد که همان فرم، یک روند کاری پیوسته را هدایت می‌کند، مانند پنل پیش‌نمایش چاپ که هر زمان کاربر تنظیمی را تغییر داد سند را بازسازی می‌کند؛ در آن حالت، زنده نگه داشتن کامپوننت و فراخوانی مکرر BeginDoc/EndDoc نسبت به تخصیص و آزادسازی مکرر اشیای حافظه heap، اخلال کمتری ایجاد می‌کند

برای کارهای دسته‌ای (batch jobs) که اسناد زیادی را به صورت متوالی تولید می‌کنند، اختصاص یک THotPDF به ازای هر سند به سربار تخصیص می‌ارزد. اگر هیچ شیئی برای حمل وضعیت وجود نداشته باشد، وضعیت (state) بین اسناد منتقل نمی‌شود، و این یکی از انواع باگ‌های متناوب است که هرگز مجبور به دیباگ کردن آن نخواهید شد. تخصیص دهید، تولید کنید، حذف کنید، و تکرار نمایید

یک ویژگی که در بسیاری از دموهای HotPDF وجود دارد AutoLaunch است، که فایل تولید شده را بلافاصله پس از EndDoc در نمایشگر PDF سیستم باز می‌کند. این ویژگی در هنگام نوشتن اولین پیش‌نویس از یک طرح (layout) مفید است. در کد تولیدی (production)، از آن صرف‌نظر کنید: مسیر خروجی را به طور صریح باز کنید، تأیید کنید که فایل وجود دارد و اندازه آن غیرصفر است، نتیجه را ثبت کنید، و اجازه دهید جریان کاری فراخوان‌کننده تصمیم بگیرد که آیا نمایشگر مربوط است یا خیر. در یک کار دسته‌ای، AutoLaunch برای هر سند یک پنجره نمایشگر اجرا می‌کند و روند را در برخی سیستم‌ها در انتظار بسته شدن نمایشگرها مسدود می‌کند

کامپوننت THotPDF و تمامی فراخوانی‌های رسم که در اینجا نشان داده شده‌اند، بخشی از کامپوننت HotPDF برای دلفی و C++Builder هستند