O erro diz Please load the document before using BeginDoc, e quase sempre aparece na segunda vez. O primeiro documento é escrito corretamente. Então a mesma instância THotPDF é solicitada a iniciar um segundo, o BeginDoc gera exceção e a mensagem aponta para o carregamento de um documento, que é o oposto do que o código está tentando fazer. É a incompatibilidade entre o sintoma e a mensagem que torna esse erro persistente. O assunto real é o ciclo de vida do componente, e assim que isso faz sentido, o erro deixa de ser um mistério

Uma instância THotPDF é um documento, não uma fábrica de documentos
O modelo mental tentador é que THotPDF é um objeto de serviço que você inicializa uma vez e alimenta com documentos, da mesma forma que você poderia manter uma ligação de banco de dados aberta e executar consulta após consulta por meio dela. Não é isso. Uma instância modela a construção de um único documento, e sua máquina de estado interna carrega a premissa de que ela percorre o caminho uma vez só: do vazio, passando por um documento aberto, até um ficheiro guardado. O BeginDoc abre esse caminho e marca a instância como tendo um documento em andamento. O EndDoc serializa tudo em FileName e o encerra. Chamar BeginDoc novamente na mesma instância já finalizada pede a ela que reentre em um estado que ela nunca deixou de forma limpa, e o guarda que dispara é aquele cuja mensagem por acaso menciona carregamento, porque internamente as condições 'pronto para começar' e 'tem um documento carregado' são verificadas juntas
Portanto a mensagem é enganosa, mas o guarda está fazendo o seu trabalho. Ele está se recusando a deixar você iniciar um documento novo sobre um componente que ainda acredita estar no meio de um documento. A correção não é derrotar o guarda. É parar de reutilizar uma instância já gasta
O ciclo de vida, na ordem em que precisa acontecer
Todo documento que o HotPDF escreve do zero segue as mesmas quatro etapas, e a ordem não é negociável. O Create aloca o componente. O BeginDoc abre o documento e fixa as escolhas estruturais, então qualquer coisa que afete o ficheiro inteiro (tamanho da página, compactação, criptografia, nome do ficheiro de saída) tem de ser definida entre o Create e o BeginDoc. Depois você desenha. Depois o EndDoc grava os bytes no disco. O Free libera a instância. Chamadas de desenho colocadas antes do BeginDoc não têm página onde aterrissar; propriedades de documento inteiro atribuídas depois dele são ignoradas sem reclamação
Leia isso como a unidade de trabalho. Um Create, um BeginDoc, um EndDoc, um Free, um ficheiro no disco. No instante em que você quer um segundo ficheiro, você está iniciando uma nova unidade de trabalho, o que significa uma nova instância
O que "reutilizar" deveria significar: uma instância nova por ficheiro
A versão que quebra tenta ser econômica com alocação: construa o componente uma vez, faça um loop sobre um lote, chame BeginDoc e EndDoc dentro do loop. A segunda iteração gera exceção. A versão que funciona trata cada saída como seu próprio objeto de vida curta, e o custo de alocação de criar um componente é trivial perto do trabalho de compor e serializar um PDF, então não há nada a economizar acumulando a instância
O try/finally posicionado dentro do loop é a parte que vale a pena defender na revisão. Se o BeginDoc ou qualquer chamada de desenho gerar exceção no meio de um documento, a instância daquela iteração ainda é liberada antes de a próxima começar, então um registo defeituoso não deixa encalhado um componente montado pela metade nem envenena o resto da execução. Puxe o Create para fora, acima do loop, para 'otimizar', e você volta ao bug original, agora vestido de loop de lote
Modificar um ficheiro existente é um ponto de entrada diferente
Há uma segunda leitura de "reutilizar" que é inteiramente legítima: você não quer um documento em branco, você quer abrir um PDF que já existe e alterá-lo. Esse caminho não passa pelo BeginDoc de forma alguma, que é exatamente por que a mensagem de erro menciona carregamento. Você carrega o ficheiro, edita-o e salva com o nome que escolher
O LoadFromFile retorna a contagem de páginas, e um valor de zero ou menos significa que o carregamento falhou, então vale a pena verificar antes de tocar em CurrentPage. O pareamento importa: um documento que você abriu com LoadFromFile é guardado com SaveLoadedDocument, não com o par BeginDoc/EndDoc, que pertence aos documentos que você cria a partir do nada. Misturar os dois é a maneira mais comum de confundir a mesma máquina de estado que produziu o erro original. Mantenha os dois fluxos separados mentalmente: BeginDoc ... EndDoc cria, LoadFromFile ... SaveLoadedDocument edita
O problema do bloqueio de ficheiro é real, e a resposta não é matar janelas do visualizador
O erro de reutilização costuma vir acompanhado de uma segunda reclamação, e as duas se enredam porque afloram no mesmo fluxo de regenerar o ficheiro. Um utilizador abre o PDF que você acabou de produzir, deixa-o aberto no Acrobat ou no Foxit e então dispara uma reconstrução. O EndDoc tenta escrever no mesmo caminho, o sistema operacional recusa porque o visualizador mantém um compartilhamento de leitura que bloqueia quem escreve, e você recebe uma falha de acesso negado. Essa é genuinamente uma questão de bloqueio de ficheiro do Windows, e não uma questão de estado do componente, e merece uma resposta de verdade em vez de uma solução de contorno
A solução de contorno que circula, enumerar as janelas de nível superior e postar WM_CLOSE para qualquer uma cujo título pareça um visualizador de PDF, é o instinto errado. Ela cruza os limites de processo para fechar janelas que o seu programa não possui, ela adivinha visualizadores pelo texto do título e pode descartar as anotações não salvas de um utilizador sem perguntar. Trate toda essa abordagem como um cheiro ruim. A correção confiável é nunca escrever em um caminho que outro processo possa estar segurando. Serialize para um ficheiro temporário no mesmo diretoria e depois troque-o de lugar com uma renomeação atômica assim que o EndDoc for bem-sucedido. Se um visualizador ainda tiver o ficheiro antigo aberto, a renomeação ou sucede de forma limpa ou falha ruidosamente, e você exibe uma mensagem clara em vez de brigar com o bloqueio
Duas ressalvas honestas sobre esse código. O TFile.Move e o clássico RenameFile mapeiam ambos para a mesma renomeação do Windows, que é atômica somente quando origem e destino ficam no mesmo volume, e é exatamente por isso que o ficheiro temporário vai para o diretoria de destino em vez de TPath.GetTempPath. E o par apagar-depois-mover não é em si um único passo atômico: há uma breve janela na qual nenhum dos ficheiros existe. Para um aplicação desktop regenerando um relatório essa janela é irrelevante; leitores que precisem de um contrato mais forte no mesmo volume podem chamar diretamente o ReplaceFile do Win32 ou o MoveFileEx com MOVEFILE_REPLACE_EXISTING, que colapsa a troca em uma única chamada
Para um servidor de alto volume que regenera documentos constantemente, a disciplina mais limpa é escrever cada saída sob um nome exclusivo (um carimbo de data/hora ou um id de tarefa) para que duas execuções nunca disputem um caminho, e deixar uma política de retenção separada limpar os ficheiros antigos. O padrão é uma linha de disciplina de nomenclatura por requisição
__CODE_BLOCK_Um id de requisição ou id de tarefa funciona tão bem quanto o GUID quando o framework ao redor já lhe entrega um, e isso torna o nome do ficheiro rastreável de volta a uma linha de log de graça. De qualquer forma o princípio é o mesmo: projete de modo que o ficheiro que você está escrevendo seja só seu no momento em que você o escreve. O bloqueio desaparece não porque você forçou o fechamento de uma janela, mas porque nada mais está tocando nos bytes
O formato da correção
Reduza os dois problemas às suas raízes e ambos têm a ver com respeitar limites. O erro da máquina de estado quer que você honre o limite da instância: um THotPDF, um documento, depois deixe-o ir e faça outro. O erro de bloqueio de ficheiro quer que você honre o limite do ficheiro: escreva onde nada mais esteja lendo e depois mova o resultado para o lugar. Nenhum dos dois pede corrigir a biblioteca com patch nem programar a área de trabalho por script. Ambos resultam de tratar cada documento como uma unidade de trabalho autocontida, criada do zero, escrita de forma limpa e liberada, que é o mesmo padrão que torna o resto do componente previsível
As chamadas BeginDoc, EndDoc, LoadFromFile e SaveLoadedDocument mostradas aqui fazem parte do HotPDF Component para Delphi e C++Builder