Artigo Técnico

Criar e Liberar um Componente HotPDF no C++Builder

Soltar um THotPDF em um formulário em tempo de design é bom para um protótipo rápido, mas vincula o componente ao tempo de vida do formulário, o que raramente é o que o código de produção deseja. Um gerador de relatórios que é executado uma vez por clique de botão, uma thread de serviço que agrupa exportações noturnas, uma classe auxiliar que não tem nenhum formulário: em cada uma dessas situações, você deseja que o componente exista exatamente pela duração de um trabalho PDF e depois desapareça. Isso significa alocação em tempo de execução e muda duas coisas que valem a pena entender antes de escrever a primeira linha: quem é o proprietário do objeto e como a limpeza é executada quando algo dá errado

Semântica de proprietário na VCL

Todo construtor de componente VCL recebe um parâmetro Owner do tipo TComponent*. Passar this (o formulário) registra o novo objeto na lista de componentes pertencentes ao formulário, portanto, se o formulário for destruído enquanto o componente ainda estiver vivo, a VCL o liberará automaticamente. Passar nullptr significa nenhum proprietário: você assume a responsabilidade exclusiva pelo ponteiro e nada o limpará para você se uma exceção desenrolar a pilha antes do seu delete explícito

Para uma exportação de disparo único que é concluída em uma única função, qualquer uma das opções funciona, mas as duas têm modos de falha diferentes. Com this como proprietário, um vazamento é impossível, desde que o formulário acabe fechando; com nullptr, o ponteiro deve atingir um bloco __finally. Na prática, o padrão nullptr mais __finally é um pouco mais limpo para objetos de vida curta porque torna o limite de tempo de vida visível rapidamente e evita que o formulário acumule objetos de sua propriedade que deveriam ser temporários

Linha do tempo de um ciclo de vida dinâmico de THotPDF no C++Builder de new THotPDF(nullptr) passando pela definição de opções, BeginDoc e EndDoc, até delete Pdf dentro de um bloco __finally que executa mesmo quando uma exceção é lançada
Um THotPDF de runtime vive por exatamente um trabalho PDF: defina as opções antes de BeginDoc, desenhe, feche, e deixe o bloco __finally emitir o delete independentemente de exceções

Estrutura segura contra exceções

A geração de PDF pode falhar por motivos que não têm nada a ver com a API: o diretório de saída é somente leitura, um arquivo de fonte está ausente, um fluxo é descarregado prematuramente ou os dados fornecidos pelo chamador atingem um limite de comprimento. Seja qual for a causa, o caminho de limpeza tem que ser executado. A maneira idiomática no C++Builder de garantir isso é 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;
    }
}

Alguns pontos dessa listagem merecem destaque. O owner é nullptr, o que torna o tempo de vida explícito. Compression e FontEmbedding são definidos antes do BeginDoc: ambos são opções no nível do documento que o HotPDF confirma quando o documento abre, e atribuí-las depois não tem efeito. O TextOut recebe coordenadas em pontos medidas a partir do canto superior esquerdo da página, com Y crescendo para baixo, então o par 72, 72 posiciona o texto a uma polegada das bordas superior e esquerda. O delete Pdf no bloco __finally executa quer BeginDoc, o desenho ou o EndDoc tenham gerado exceção ou não

Evite chamar qualquer método em Pdf após o delete. Se o ponteiro estiver armazenado em uma variável de membro, defina-o como nullptr imediatamente após a exclusão para que qualquer acesso acidental posterior produza uma falha limpa em vez de corrupção silenciosa

Configuração do projeto

O C++Builder localiza o THotPDF por meio de uma combinação de caminhos de inclusão, caminhos de biblioteca e uma diretiva pragma. O cabeçalho gerado fica junto com HPDFDoc.pas no diretório de origem do HotPDF; adicione esse diretório a Project > Options > C++ Compiler > Include path. A diretiva #pragma link "HPDFDoc" diz ao vinculador para puxar a unidade compilada sem listá-la no arquivo do projeto manualmente. Se você estiver usando o pacote de tempo de execução em vez de vinculação estática, instale primeiro os pacotes de design e tempo de execução do HotPDF; o pragma ainda se aplica

Mantenha o nome da unidade HPDFDoc inalterado. O C++Builder deriva o nome do cabeçalho a partir do nome da unidade Pascal, portanto, renomear o arquivo ou usar um alias de caminho no pragma quebra a pesquisa silenciosamente

Escopo e trabalhos de vários documentos

Para uma única exportação acionada por ação do usuário, uma variável local com escopo definido para o manipulador do botão é a resposta certa: ela é criada, usada e destruída em um quadro de chamada, e a intenção é óbvia para quem ler o código mais tarde. A alternativa de tempo de design é justificada quando o mesmo formulário conduz um fluxo de trabalho contínuo, como um painel de visualização de impressão que reconstrói o documento sempre que o usuário altera uma configuração; nesse caso, manter o componente vivo e chamar BeginDoc/EndDoc repetidamente é menos perturbador do que alocar e liberar objetos de heap repetidamente

Para trabalhos em lote que produzem muitos documentos em sequência, definir o escopo de um THotPDF por documento compensa a sobrecarga de alocação. O estado não é transferido entre documentos se não houver objeto para carregá-lo, e essa é uma classe de bug intermitente que você nunca precisa depurar. Aloque, gere, exclua, repita

Uma propriedade que aparece em várias demonstrações do HotPDF é AutoLaunch, que abre o arquivo gerado no visualizador de PDF do sistema imediatamente após o EndDoc. Ela é útil ao escrever o primeiro rascunho de um layout. Na produção, ignore-a: abra o caminho de saída explicitamente, verifique se o arquivo existe e tem um tamanho diferente de zero, registre o resultado e deixe o fluxo de trabalho de chamada decidir se um visualizador é relevante. Em um trabalho em lote, o AutoLaunch inicia uma janela do visualizador por documento e bloqueará o processo em alguns sistemas aguardando o fechamento do visualizador

Diagrama de loop em lote alocando um THotPDF novo por documento e liberando-o após EndDoc para que nenhum estado vaze entre passadas, com um alerta contra habilitar AutoLaunch em exportações de PDF de produção
Delimite um THotPDF novo por documento do lote para que nenhum estado vaze entre passadas, e deixe AutoLaunch desabilitado para que janelas espúrias de visualizador não travem o trabalho

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