Technical Article

Crear y liberar un componente HotPDF dinámicamente en C++Builder

Colocar un THotPDF en un formulario en tiempo de diseño está bien para un prototipo rápido, pero vincula el componente a la vida del formulario, que rara vez es lo que quiere el código de producción. Un generador de informes que se ejecuta una vez por pulsación de botón, un hilo de servicio que procesa exportaciones nocturnas por lotes, una clase auxiliar que no tiene formulario en absoluto: en cada una de esas situaciones queréis que el componente exista exactamente durante la duración de un trabajo PDF y luego desaparezca. Eso implica asignación en tiempo de ejecución, y cambia dos cosas que vale la pena entender antes de escribir la primera línea: quién posee el objeto y cómo se ejecuta la limpieza cuando algo va mal.

Semántica de propietario en VCL

Cada constructor de componente VCL toma un parámetro Owner de tipo TComponent*. Pasar this (el formulario) registra el nuevo objeto en la lista de componentes poseídos del formulario, de modo que si el formulario se destruye mientras el componente todavía está vivo, VCL lo libera automáticamente. Pasar nullptr significa que no hay propietario: asumiréis la responsabilidad exclusiva del puntero, y nada lo limpiará por vosotros si una excepción deshace la pila antes de vuestro delete explícito.

Para una exportación de una sola vez que se completa dentro de una única función, cualquiera de las dos opciones funciona, pero las dos tienen diferentes modos de fallo. Con this como propietario, una fuga es imposible siempre que el formulario se cierre eventualmente; con nullptr, el puntero debe llegar a un bloque __finally. En la práctica, el patrón nullptr más __finally es ligeramente más limpio para objetos de vida corta porque hace visible el límite de vida de un vistazo y evita que el formulario acumule objetos poseídos que se pretendían temporales.

Estructura segura ante excepciones

La generación de PDF puede fallar por razones que no tienen nada que ver con la API: el directorio de salida es de solo lectura, falta un fichero de fuente, un flujo se vacía prematuramente o los datos proporcionados por el llamante superan un límite de longitud. Sea cual sea la causa, la ruta de limpieza debe ejecutarse. La forma idiomática en C++Builder de garantizarlo es 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;
    }
}

Hay algunos detalles de ese listado que merece la pena destacar. El propietario es nullptr, lo que hace explícita la vida del objeto. Compression y FontEmbedding se establecen antes de BeginDoc: ambas son opciones a nivel de documento que HotPDF confirma cuando se abre el documento, y asignarlas después no tiene ningún efecto. TextOut toma coordenadas en puntos medidas desde la esquina inferior izquierda de la página, con Y creciendo hacia arriba; el par 72, 720 coloca el texto cerca de la esquina superior izquierda de una página de tamaño carta con un margen izquierdo de una pulgada. El delete Pdf en el bloque __finally se ejecuta independientemente de si BeginDoc, el dibujo o EndDoc lanzaron una excepción.

Evitad llamar a cualquier método sobre Pdf después del delete. Si el puntero se almacena en una variable miembro, establecedlo a nullptr inmediatamente después de la eliminación para que cualquier acceso accidental posterior produzca un fallo limpio en lugar de una corrupción silenciosa.

Configuración del proyecto

C++Builder localiza THotPDF mediante una combinación de rutas de inclusión, rutas de bibliotecas y una directiva pragma. La cabecera generada reside junto a HPDFDoc.pas en el directorio de fuentes de HotPDF; añadid ese directorio a Project > Options > C++ Compiler > Include path. La directiva #pragma link "HPDFDoc" indica al enlazador que incluya la unidad compilada sin tener que listarla manualmente en el fichero de proyecto. Si usáis el paquete en tiempo de ejecución en lugar de la vinculación estática, instalad primero los paquetes de diseño y ejecución de HotPDF; el pragma sigue siendo aplicable.

Mantened el nombre de unidad HPDFDoc sin cambios. C++Builder deriva el nombre de cabecera del nombre de unidad Pascal, por lo que renombrar el fichero o usar un alias de ruta en el pragma rompe la búsqueda silenciosamente.

Ámbito y trabajos de múltiples documentos

Para una exportación única desencadenada por una acción del usuario, una variable local con ámbito en el manejador del botón es la respuesta correcta: se crea, usa y destruye dentro de un único marco de llamada, y la intención es obvia para cualquiera que lea el código después. La alternativa en tiempo de diseño está justificada cuando el mismo formulario impulsa un flujo de trabajo continuo, como un panel de vista previa de impresión que reconstruye el documento cada vez que el usuario cambia una configuración; en ese caso, mantener el componente vivo y llamar a BeginDoc/EndDoc repetidamente es menos perturbador que asignar y liberar objetos del heap repetidamente.

Para trabajos por lotes que producen muchos documentos en secuencia, delimitar un THotPDF por documento vale la pena el coste de asignación. El estado no se transfiere entre documentos si no hay ningún objeto que lo transfiera, y esa es una clase de error intermitente que nunca tendréis que depurar. Asignad, generáis, eliminad, repetid.

Una propiedad que aparece en varias demos de HotPDF es AutoLaunch, que abre el fichero generado en el visor PDF del sistema inmediatamente después de EndDoc. Es útil mientras escribís el primer borrador de un diseño. En producción, prescindid de ella: abrid la ruta de salida explícitamente, verificad que el fichero existe y tiene un tamaño distinto de cero, registrad el resultado y dejad que el flujo de trabajo llamante decida si es relevante un visor. En un trabajo por lotes, AutoLaunch lanza una ventana de visor por documento y bloqueará el proceso en algunos sistemas esperando a que se cierre el visor.

El componente THotPDF y todas las llamadas de dibujo mostradas aquí forman parte del HotPDF Component para Delphi y C++Builder.