Technical Article

Criar e Libertar um Componente HotPDF Dinamicamente em C++Builder

Colocar um THotPDF num formulário em tempo de desenho é aceitável para um protótipo rápido, mas vincula o ciclo de vida do componente ao do formulário, o que raramente é o pretendido em código de produção. Um gerador de relatórios executado a cada clique de botão, uma thread de serviço que processa exportações noturnas ou uma classe auxiliar que não possui formulário: em cada uma destas situações, pretende que o componente exista exatamente durante a execução de uma tarefa de PDF e depois desapareça. Isso significa alocação em tempo de execução e altera duas coisas importantes a compreender antes de escrever a primeira linha: quem é o proprietário do objeto e como a limpeza é efetuada quando algo corre mal.

Semântica do proprietário (Owner) na VCL

Cada construtor de componente VCL aceita um parâmetro Owner do tipo TComponent*. Passar this (o formulário) regista o novo objeto na lista de componentes pertencentes ao formulário, pelo que, se o formulário for destruído enquanto o componente ainda estiver ativo, a VCL liberta-o automaticamente. Passar nullptr significa que não existe proprietário: assume a responsabilidade exclusiva pelo ponteiro e nada o libertará por si se uma exceção desenrolar a pilha antes do seu delete explícito.

Para uma exportação pontual que se conclui dentro de uma única função, qualquer uma das opções funciona, mas as duas apresentam modos de falha diferentes. Com this como proprietário, uma fuga de memória é impossível desde que o formulário acabe por fechar; com nullptr, o ponteiro deve alcançar um bloco __finally. Na prática, o padrão de nullptr mais __finally é ligeiramente mais limpo para objetos de curta duração porque torna o limite do ciclo de vida visível à primeira vista e evita que o formulário acumule objetos pertencentes que deviam ser temporários.

Estrutura segura contra exceções

A geração de PDF pode falhar por razões que nada têm a ver com a API: o diretório de saída é apenas de leitura, falta um ficheiro de tipo de letra, o fluxo falha ao efetuar o fluxo prematuramente ou os dados fornecidos pelo invocador excedem um limite. Qualquer que seja a causa, o caminho de limpeza tem de ser executado. A forma idiomática no C++Builder para garantir isso é a utilização de 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;
    }
}

Há alguns aspetos a destacar nessa listagem. O proprietário é nullptr, tornando o ciclo de vida explícito. As propriedades Compression e FontEmbedding são definidas antes de BeginDoc: ambas são opções de nível de documento que o HotPDF consolida ao abrir o documento, e a sua atribuição posterior não tem efeito. O método TextOut recebe coordenadas em pontos medidos a partir do canto inferior esquerdo da página, com o Y a aumentar para cima; o par 72, 720 coloca o texto perto do topo esquerdo de uma página de tamanho Letter com uma margem esquerda de uma polegada. A instrução delete Pdf no bloco __finally executa quer o BeginDoc, os desenhos ou o EndDoc tenham gerado uma exceção ou não.

Evite chamar qualquer método no objeto Pdf após a execução do delete. Se o ponteiro estiver armazenado numa variável membro, defina-o como nullptr imediatamente após a eliminação, para que qualquer acesso acidental posterior resulte num crash limpo em vez de uma corrupção silenciosa.

Configuração do projeto

O C++Builder localiza o THotPDF através de uma combinação de caminhos de inclusão, caminhos de biblioteca e uma diretiva pragma. O cabeçalho gerado reside ao lado de HPDFDoc.pas no diretório de origem do HotPDF; adicione esse diretório em Project > Options > C++ Compiler > Include path. A diretiva #pragma link "HPDFDoc" indica ao linker para incluir a unidade compilada sem necessidade de a listar manualmente no ficheiro de projeto. Se estiver a utilizar o package de tempo de execução em vez de ligação estática, instale primeiro os pacotes de desenho e de tempo de execução do HotPDF; o pragma continua a aplicar-se.

Mantenha o nome da unidade HPDFDoc inalterado. O C++Builder deriva o nome do cabeçalho a partir do nome da unidade Pascal, pelo que renomear o ficheiro ou utilizar um alias de caminho no pragma interrompe a localização silenciosamente.

Âmbito e tarefas com múltiplos documentos

Para uma exportação única desencadeada pela ação do utilizador, uma variável local com âmbito limitado ao manipulador do botão é a resposta correta: é criada, utilizada e destruída num único frame de chamada e a intenção é evidente para qualquer pessoa que leia o código mais tarde. A alternativa em tempo de desenho é justificada quando o mesmo formulário gere um fluxo contínuo, como um painel de pré-visualização que reconstrói o documento sempre que o utilizador altera uma definição; nesse caso, manter o componente ativo e chamar BeginDoc/EndDoc repetidamente é menos dispendioso do que alocar e libertar repetidamente objetos na heap.

Para tarefas em lote (batch) que produzem muitos documentos em sequência, limitar o âmbito de um THotPDF por documento compensa a sobrecarga de alocação. O estado não transita entre documentos se não existir um objeto para o transportar, o que elimina uma classe de erros intermitentes difíceis de depurar. Aloque, gere, elimine, repita.

Uma propriedade que aparece em várias demonstrações do HotPDF é o AutoLaunch, que abre o ficheiro gerado no visualizador de PDF do sistema imediatamente após o EndDoc. É útil durante a conceção do primeiro rascunho de um layout. Em produção, ignore-o: abra o caminho de saída explicitamente, verifique se o ficheiro existe e tem um tamanho diferente de zero, registe o resultado e deixe que o fluxo de trabalho de chamada decida se o visualizador é relevante. Numa tarefa em lote, o AutoLaunch abre uma janela de visualizador por documento e bloqueará o processo nalguns sistemas enquanto aguarda que o visualizador feche.

O componente THotPDF e todas as chamadas de desenho aqui apresentadas fazem parte do Componente HotPDF para Delphi e C++Builder.