Gerenciamento de Estado de Instância de Objeto e Resolução de Conflitos de Arquivo
Descubra como resolver o erro “Por favor, carregue o documento antes de usar BeginDoc” ao usar o Componente Delphi HotPDF e elimine conflitos de acesso a arquivos PDF através de gerenciamento estratégico de estado e técnicas automatizadas de enumeração de janelas.

🚨 O Desafio: Quando Componentes PDF Se Recusam a Cooperar
Imagine este cenário: Você está construindo uma aplicação robusta de processamento de PDF usando o componente HotPDF em Delphi ou C++Builder. Tudo funciona perfeitamente na primeira execução. Mas quando você tenta processar um segundo documento sem reiniciar a aplicação, você é atingido pelo terrível erro:
"Por favor, carregue o documento antes de usar BeginDoc."
O erro que assombra desenvolvedores PDF
Soa familiar? Você não está sozinho. Este problema, combinado com conflitos de acesso a arquivos de visualizadores PDF abertos, tem frustrado muitos desenvolvedores trabalhando com bibliotecas de manipulação de PDF.
📚 Contexto Técnico: Compreendendo a Arquitetura de Componentes PDF
Antes de mergulhar nas questões específicas, é crucial entender a base arquitetural dos componentes de processamento PDF como o HotPDF e como eles interagem com o sistema operacional subjacente e o sistema de arquivos.
Gerenciamento do Ciclo de Vida do Componente PDF
Componentes PDF modernos seguem um padrão de ciclo de vida bem definido que gerencia estados de processamento de documentos:
- Fase de Inicialização: Instanciação e configuração do componente
- Fase de Carregamento do Documento: Leitura de arquivo e alocação de memória
- Fase de Processamento: Manipulação e transformação de conteúdo
- Fase de Saída: Gravação de arquivo e limpeza de recursos
- Fase de Reset: Restauração do estado para reutilização (frequentemente negligenciada!)
O componente HotPDF, como muitas bibliotecas PDF comerciais, usa flags de estado interno para rastrear sua fase atual do ciclo de vida. Essas flags servem como guardiões, prevenindo operações inválidas e garantindo a integridade dos dados. No entanto, gerenciamento inadequado de estado pode transformar esses mecanismos de proteção em barreiras.
Interação com o Sistema de Arquivos do Windows
O processamento de PDF envolve operações intensivas do sistema de arquivos que interagem com os mecanismos de bloqueio de arquivo do Windows:
- Bloqueios Exclusivos: Previnem múltiplas operações de escrita no mesmo arquivo
- Bloqueios Compartilhados: Permitem múltiplos leitores mas bloqueiam escritores
- Herança de Handle: Processos filhos podem herdar handles de arquivo
- Arquivos Mapeados em Memória: Visualizadores PDF frequentemente mapeiam arquivos na memória para performance
Compreender esses mecanismos é crucial para desenvolver aplicações robustas de processamento PDF que possam lidar com cenários de implantação do mundo real.
🔍 Análise do Problema: A Investigação da Causa Raiz
Problema #1: O Pesadelo do Gerenciamento de Estado
O problema central reside no gerenciamento de estado interno do componente THotPDF. Quando você chama o método EndDoc()
após processar um documento, o componente salva seu arquivo PDF mas falha em resetar duas flags internas críticas:
FDocStarted
– Permanecetrue
após EndDoc()FIsLoaded
– Permanece em um estado inconsistente
Aqui está o que acontece nos bastidores:
1 2 3 4 5 6 7 8 9 |
// Dentro do método THotPDF.BeginDoc procedure THotPDF.BeginDoc(Initial: boolean); begin if FDocStarted then raise Exception.Create('Por favor, carregue o documento antes de usar BeginDoc.'); FDocStarted := true; // ... código de inicialização end; |
O problema? FDocStarted nunca é resetado para false em EndDoc(), tornando chamadas subsequentes de BeginDoc() impossíveis.
Mergulho Profundo: Análise das Flags de Estado
Vamos examinar o quadro completo do gerenciamento de estado analisando a estrutura da classe THotPDF:
1 2 3 4 5 6 7 |
// Campos privados da classe THotPDF (de HPDFDoc.pas) THotPDF = class(TComponent) private FDocStarted: Boolean; // Rastreia se BeginDoc foi chamado FIsLoaded: Boolean; // Rastreia se o documento está carregado // ... outros campos internos end; |
O problema torna-se claro quando rastreamos o fluxo de execução:
❌ Fluxo de Execução Problemático
HotPDF1.BeginDoc(true)
→FDocStarted := true
- Operações de processamento do documento…
HotPDF1.EndDoc()
→ Arquivo salvo, mas FDocStarted permanece trueHotPDF1.BeginDoc(true)
→ Exceção lançada devido aFDocStarted = true
Investigação de Vazamentos de Memória
Investigação adicional revela que o gerenciamento inadequado de estado também pode levar a vazamentos de memória:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Problema de gerenciamento de estado em cenários de reutilização de componente procedure THotPDF.BeginDoc(Initial: boolean); begin if FDocStarted then raise Exception.Create('Por favor, carregue o documento antes de usar BeginDoc.'); // O componente define flags de estado interno FDocStarted := true; // Nota: Gerenciamento de memória interno e alocação de recursos // ocorre dentro do componente mas os detalhes não são publicamente acessíveis // A questão chave é que EndDoc não reseta FDocStarted para false // ... resto da inicialização end; |
O componente aloca objetos internos mas não os limpa adequadamente durante a fase EndDoc, levando ao consumo progressivo de memória em aplicações de longa duração.
Problema #2: O Dilema do Bloqueio de Arquivo
Mesmo se você resolver o problema de gerenciamento de estado, provavelmente encontrará outro problema frustrante: conflitos de acesso a arquivo. Quando usuários têm arquivos PDF abertos em visualizadores como Adobe Reader, Foxit, ou SumatraPDF, sua aplicação não pode escrever nesses arquivos, resultando em erros de acesso negado.
⚠️ Cenário Comum: Usuário abre PDF gerado → Tenta regenerar → Aplicação falha com erro de acesso a arquivo → Usuário fecha manualmente o visualizador PDF → Usuário tenta novamente → Sucesso (mas UX ruim)
Mergulho Profundo na Mecânica de Bloqueio de Arquivo do Windows
Para entender por que visualizadores PDF causam problemas de acesso a arquivo, precisamos examinar como o Windows lida com operações de arquivo no nível do kernel:
Gerenciamento de Handle de Arquivo
1 2 3 4 5 6 7 8 9 10 |
// Comportamento típico de abertura de arquivo do visualizador PDF HANDLE hFile = CreateFile( pdfFilePath, GENERIC_READ, // Modo de acesso FILE_SHARE_READ, // Modo de compartilhamento - permite outros leitores NULL, // Atributos de segurança OPEN_EXISTING, // Disposição de criação FILE_ATTRIBUTE_NORMAL, // Flags e atributos NULL // Arquivo template ); |
A questão crítica é a flag FILE_SHARE_READ
. Embora isso permita que múltiplas aplicações leiam o arquivo simultaneamente, previne qualquer operação de escrita até que todos os handles de leitura sejam fechados.
Complicações de Arquivo Mapeado em Memória
Muitos visualizadores PDF modernos usam arquivos mapeados em memória para otimização de performance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Mapeamento de memória do visualizador PDF (conceitual) HANDLE hMapping = CreateFileMapping( hFile, // Handle do arquivo NULL, // Atributos de segurança PAGE_READONLY, // Proteção 0, 0, // Tamanho máximo NULL // Nome ); LPVOID pView = MapViewOfFile( hMapping, // Handle de mapeamento FILE_MAP_READ, // Acesso 0, 0, // Offset 0 // Número de bytes ); |
Arquivos mapeados em memória criam bloqueios ainda mais fortes que persistem até:
- Todas as visualizações mapeadas são desmapeadas
- Todos os handles de mapeamento de arquivo são fechados
- O handle de arquivo original é fechado
- O processo termina
Análise de Comportamento de Visualizadores PDF
Diferentes visualizadores PDF exibem comportamentos variados de bloqueio de arquivo:
Visualizador PDF | Tipo de Bloqueio | Duração do Bloqueio | Comportamento de Liberação |
---|---|---|---|
Adobe Acrobat Reader | Leitura Compartilhada + Mapeamento de Memória | Enquanto documento está aberto | Libera ao fechar janela |
Foxit Reader | Leitura Compartilhada | Tempo de vida do documento | Liberação rápida ao fechar |
SumatraPDF | Bloqueio mínimo | Apenas operações de leitura | Liberação mais rápida |
Chrome/Edge (Integrado) | Bloqueio de processo do navegador | Tempo de vida da aba | Pode persistir após fechar aba |
💡 Arquitetura da Solução: Uma Abordagem Dupla
Nossa solução aborda ambos os problemas sistematicamente:
🛠️ Solução 1: Reset Adequado do Estado em EndDoc
A correção é elegantemente simples mas criticamente importante. Precisamos modificar o método EndDoc
em HPDFDoc.pas
para resetar as flags de estado interno:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure THotPDF.EndDoc; begin // ... lógica de salvamento existente ... // A CORREÇÃO: Reset das flags de estado para reutilização do componente FDocStarted := false; FIsLoaded := false; // Opcional: Adicionar logging de debug {$IFDEF DEBUG} WriteLn('HotPDF: Estado do componente resetado para reutilização'); {$ENDIF} end; |
Impacto: Esta simples adição transforma o componente HotPDF de uso único para um componente verdadeiramente reutilizável, habilitando múltiplos ciclos de processamento de documento dentro da mesma instância da aplicação.
Implementação Completa de Reset de Estado
Para uma solução pronta para produção, precisamos resetar todas as variáveis de estado relevantes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
procedure THotPDF.EndDoc; begin try // ... lógica de salvamento existente ... // Reset de estado essencial para reutilização do componente // Apenas resetar os campos privados verificados que sabemos que existem FDocStarted := false; FIsLoaded := false; // Nota: A seguinte abordagem de limpeza é conservadora // já que não podemos acessar todos os detalhes de implementação privada {$IFDEF DEBUG} OutputDebugString('HotPDF: Reset de estado para reutilização concluído'); {$ENDIF} except on E: Exception do begin // Garantir que flags de estado críticas sejam resetadas mesmo se outra limpeza falhar FDocStarted := false; FIsLoaded := false; {$IFDEF DEBUG} OutputDebugString('HotPDF: Exceção durante EndDoc, flags de estado resetadas'); {$ENDIF} raise; end; end; end; |
Considerações de Thread Safety
Em aplicações multi-thread, o gerenciamento de estado torna-se mais complexo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// Abordagem de gerenciamento de estado thread-safe type THotPDFThreadSafe = class(THotPDF) private FCriticalSection: TCriticalSection; FThreadId: TThreadID; protected procedure EnterCriticalSection; procedure LeaveCriticalSection; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure BeginDoc(Initial: Boolean); override; procedure EndDoc; override; end; procedure THotPDFThreadSafe.BeginDoc(Initial: Boolean); begin EnterCriticalSection; try if FDocStarted then raise Exception.Create('Documento já iniciado na thread ' + IntToStr(FThreadId)); FThreadId := GetCurrentThreadId; inherited BeginDoc(Initial); finally LeaveCriticalSection; end; end; |
🔧 Solução 2: Gerenciamento Inteligente de Visualizadores PDF
Inspirando-se no exemplo HelloWorld.dpr do Delphi, implementamos um sistema automatizado de fechamento de visualizador PDF usando a API do Windows. Aqui está a implementação completa em C++Builder:
Definição da Estrutura de Dados
1 2 3 4 |
// Definir estrutura para enumeração de janelas struct EnumWindowsData { std::vector<UnicodeString> targetTitles; }; |
Callback de Enumeração de Janelas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { EnumWindowsData* data = reinterpret_cast<EnumWindowsData*>(lParam); wchar_t windowText[256]; if (GetWindowTextW(hwnd, windowText, sizeof(windowText)/sizeof(wchar_t)) > 0) { UnicodeString windowTitle = UnicodeString(windowText); // Verificar se o título da janela corresponde a algum alvo for (size_t i = 0; i < data->targetTitles.size(); i++) { if (windowTitle.Pos(data->targetTitles[i]) > 0) { // Enviar mensagem de fechamento para janela correspondente PostMessage(hwnd, WM_CLOSE, 0, 0); break; } } } return TRUE; // Continuar enumeração } |
Função Principal de Fechamento
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void TForm1::ClosePDFViewers(const UnicodeString& fileName) { EnumWindowsData data; // Extrair nome do arquivo sem extensão UnicodeString baseFileName = ExtractFileName(fileName); if (baseFileName.Pos(".") > 0) { baseFileName = baseFileName.SubString(1, baseFileName.Pos(".") - 1); } // Visualizadores PDF alvo e arquivo específico data.targetTitles.push_back(baseFileName); data.targetTitles.push_back("Adobe"); data.targetTitles.push_back("Foxit"); data.targetTitles.push_back("SumatraPDF"); data.targetTitles.push_back("PDF"); // Enumerar todas as janelas de nível superior EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&data)); } |
🚀 Implementação: Juntando Tudo
Integração em Manipuladores de Evento de Botão
Aqui está como integrar ambas as soluções em sua aplicação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void __fastcall TForm1::Button1Click(TObject *Sender) { try { // Passo 1: Fechar quaisquer visualizadores PDF ClosePDFViewers(OutFileEdit->Text); // Passo 2: Aguardar visualizadores fecharem completamente Sleep(1000); // Atraso de 1 segundo garante limpeza // Passo 3: Validar entrada if (!FileExists(InFileEdit->Text)) { ShowMessage("Arquivo PDF de entrada não existe: " + InFileEdit->Text); return; } // Passo 4: Processar PDF (componente agora reutilizável!) HotPDF1->BeginDoc(true); HotPDF1->FileName = OutFileEdit->Text; HotPDF1->LoadFromFile(InFileEdit->Text, "", false); // ... lógica de processamento PDF ... HotPDF1->EndDoc(); // Reseta estado automaticamente agora! ShowMessage("PDF processado com sucesso!"); } catch (Exception& e) { ShowMessage("Erro: " + e.Message); } } |
🏢 Cenários Empresariais Avançados
Em ambientes empresariais, os requisitos de processamento PDF tornam-se significativamente mais complexos. Vamos explorar cenários avançados e suas soluções:
Processamento em Lote with Gerenciamento de Recursos
Aplicações empresariais frequentemente precisam processar centenas ou milhares de arquivos PDF em operações em lote:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
class PDFBatchProcessor { private: std::unique_ptr m_pdfComponent; std::queue m_taskQueue; std::atomic m_processedCount; std::atomic m_isProcessing; public: void ProcessBatch(const std::vector& filePaths) { m_isProcessing = true; m_processedCount = 0; for (const auto& filePath : filePaths) { try { // Pré-processamento: Fechar quaisquer visualizadores para este arquivo ClosePDFViewers(UnicodeString(filePath.c_str())); Sleep(500); // Atraso menor para processamento em lote // Processar arquivo único ProcessSingleFile(filePath); // Gerenciamento de memória: Forçar limpeza a cada 100 arquivos if (++m_processedCount % 100 == 0) { ForceGarbageCollection(); ReportProgress(m_processedCount, filePaths.size()); } } catch (const std::exception& e) { LogError(filePath, e.what()); // Continuar processando outros arquivos } } m_isProcessing = false; } private: void ForceGarbageCollection() { // Forçar reset do estado do componente if (m_pdfComponent) { m_pdfComponent.reset(); m_pdfComponent = std::make_unique(nullptr); } // Limpeza de memória do sistema SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1); } }; |
Processamento PDF Multi-Tenant
Aplicações SaaS requerem processamento PDF isolado para diferentes clientes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class MultiTenantPDFService { private: std::unordered_map<std::string, std::unique_ptr> m_tenantComponents; std::mutex m_componentMutex; public: void ProcessTenantDocument(const std::string& tenantId, const std::string& filePath) { std::lock_guard lock(m_componentMutex); auto& component = GetTenantComponent(tenantId); try { // Fechar visualizadores específicos do tenant ClosePDFViewers(UnicodeString(filePath.c_str())); Sleep(800); // Processamento isolado por tenant component->BeginDoc(true); component->FileName = UnicodeString((filePath + "_processed").c_str()); component->LoadFromFile(UnicodeString(filePath.c_str()), "", false); // Aplicar configurações específicas do tenant ApplyTenantSettings(component.get(), tenantId); component->EndDoc(); // Agora reseta estado corretamente! LogTenantActivity(tenantId, "PDF processed successfully"); } catch (const Exception& e) { LogTenantError(tenantId, e.Message); throw; } } private: std::unique_ptr& GetTenantComponent(const std::string& tenantId) { auto it = m_tenantComponents.find(tenantId); if (it == m_tenantComponents.end()) { m_tenantComponents[tenantId] = std::make_unique(nullptr); // Configuração inicial específica do tenant auto& component = m_tenantComponents[tenantId]; component->AutoLaunch = false; component->ShowInfo = false; } return m_tenantComponents[tenantId]; } void ApplyTenantSettings(THotPDF* component, const std::string& tenantId) { // Configurações personalizadas baseadas no tenant component->Author = UnicodeString(("Tenant: " + tenantId).c_str()); component->Creator = "Multi-Tenant PDF Service"; component->SetVersion(pdf14); } }; |
🔄 Processamento Assíncrono Avançado
Para aplicações de alta performance, implementamos processamento assíncrono com pool de threads:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
class AdvancedAsyncPDFProcessor { private: std::vector m_workers; std::queue<std::function<void()>> m_taskQueue; std::mutex m_queueMutex; std::condition_variable m_condition; std::atomic m_shutdown{false}; // Pool de componentes HotPDF para processamento concorrente std::queue<std::unique_ptr> m_componentPool; std::mutex m_poolMutex; public: explicit AdvancedAsyncPDFProcessor(size_t numThreads = 0) { size_t threadCount = numThreads > 0 ? numThreads : std::thread::hardware_concurrency(); // Inicializar pool de threads for (size_t i = 0; i < threadCount; ++i) { m_workers.emplace_back([this] { WorkerLoop(); }); } // Pré-popular pool de componentes for (size_t i = 0; i < threadCount; ++i) { auto component = std::make_unique(nullptr); component->AutoLaunch = false; component->ShowInfo = false; m_componentPool.push(std::move(component)); } } ~AdvancedAsyncPDFProcessor() { Shutdown(); } void ProcessAsync(const std::string& inputFile, const std::string& outputFile) { { std::lock_guard lock(m_queueMutex); m_taskQueue.push([this, inputFile, outputFile] { ProcessFile(inputFile, outputFile); }); } m_condition.notify_one(); } private: void WorkerLoop() { while (!m_shutdown.load()) { std::function<void()> task; { std::unique_lock lock(m_queueMutex); m_condition.wait(lock, [this] { return !m_taskQueue.empty() || m_shutdown.load(); }); if (m_shutdown.load()) break; task = m_taskQueue.front(); m_taskQueue.pop(); } task(); } } void ProcessFile(const std::string& inputFile, const std::string& outputFile) { auto component = BorrowComponent(); try { // Fechar visualizadores ClosePDFViewers(UnicodeString(inputFile.c_str())); Sleep(300); // Atraso reduzido para processamento assíncrono // Processamento thread-safe component->BeginDoc(true); component->FileName = UnicodeString(outputFile.c_str()); component->LoadFromFile(UnicodeString(inputFile.c_str()), "", false); // Processamento específico aqui... component->EndDoc(); // Estado resetado automaticamente } catch (const Exception& e) { // Log error mas não interromper outros workers OutputDebugString(("Async PDF processing error: " + AnsiString(e.Message)).c_str()); } ReturnComponent(std::move(component)); } std::unique_ptr BorrowComponent() { std::lock_guard lock(m_poolMutex); if (!m_componentPool.empty()) { auto component = std::move(m_componentPool.front()); m_componentPool.pop(); return component; } // Criar novo componente se pool estiver vazio auto component = std::make_unique(nullptr); component->AutoLaunch = false; component->ShowInfo = false; return component; } void ReturnComponent(std::unique_ptr component) { std::lock_guard lock(m_poolMutex); m_componentPool.push(std::move(component)); } void Shutdown() { m_shutdown = true; m_condition.notify_all(); for (auto& worker : m_workers) { if (worker.joinable()) { worker.join(); } } } }; |
⚡ Inicialização Preguiçosa Inteligente
Para otimizar o uso de memória e tempo de inicialização:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
class SmartPDFProcessor { private: mutable std::once_flag m_initFlag; mutable std::unique_ptr m_component; mutable std::mutex m_accessMutex; // Monitoramento de performance mutable std::atomic m_usageCount{0}; mutable std::chrono::steady_clock::time_point m_initTime; mutable size_t m_peakMemoryUsage{0}; public: // Inicialização thread-safe e preguiçosa com monitoramento de performance THotPDF& GetComponent() const { std::lock_guard lock(m_accessMutex); std::call_once(m_initFlag, [this]() { auto start = std::chrono::steady_clock::now(); m_component = std::make_unique(nullptr); m_component->AutoLaunch = false; m_component->ShowInfo = false; m_component->Author = "Smart PDF Processor"; m_component->Creator = "Enterprise PDF Service"; m_component->SetVersion(pdf14); m_initTime = std::chrono::steady_clock::now(); auto initDuration = std::chrono::duration_cast(m_initTime - start); OutputDebugString(("SmartPDFProcessor initialized in " + std::to_string(initDuration.count()) + "ms").c_str()); }); ++m_usageCount; // Monitoramento de memória a cada 100 usos if (m_usageCount % 100 == 0) { MonitorMemoryUsage(); } return *m_component; } // Relatório de performance void ReportPerformanceStats() const { if (m_component) { auto uptime = std::chrono::duration_cast( std::chrono::steady_clock::now() - m_initTime); OutputDebugString(("SmartPDFProcessor Stats:").c_str()); OutputDebugString((" Usage Count: " + std::to_string(m_usageCount.load())).c_str()); OutputDebugString((" Uptime: " + std::to_string(uptime.count()) + " seconds").c_str()); OutputDebugString((" Peak Memory: " + std::to_string(m_peakMemoryUsage / 1024) + " KB").c_str()); } } private: void MonitorMemoryUsage() const { PROCESS_MEMORY_COUNTERS pmc; if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { if (pmc.WorkingSetSize > m_peakMemoryUsage) { m_peakMemoryUsage = pmc.WorkingSetSize; } } } }; |
🎯 Estratégia de Cache Inteligente
Sistema de cache empresarial com gerenciamento avançado de recursos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
class EnterpriseComponentCache { private: struct ComponentInfo { std::unique_ptr component; std::chrono::steady_clock::time_point lastUsed; size_t useCount{0}; bool inUse{false}; }; std::vector m_componentPool; std::mutex m_poolMutex; std::thread m_cleanupThread; std::atomic m_shutdown{false}; // Métricas de performance std::atomic m_totalRequests{0}; std::atomic m_cacheHits{0}; std::atomic m_componentCreations{0}; const size_t m_maxCacheSize; const std::chrono::minutes m_componentExpiry{30}; public: explicit EnterpriseComponentCache(size_t maxSize = 10) : m_maxCacheSize(maxSize) , m_cleanupThread([this] { CleanupLoop(); }) { // Pré-popular cache com componentes otimizados m_componentPool.reserve(m_maxCacheSize); for (size_t i = 0; i < std::min(m_maxCacheSize, size_t(3)); ++i) { ComponentInfo info; info.component = CreateOptimizedComponent(); info.lastUsed = std::chrono::steady_clock::now(); m_componentPool.push_back(std::move(info)); } } ~EnterpriseComponentCache() { m_shutdown = true; if (m_cleanupThread.joinable()) { m_cleanupThread.join(); } } // RAII-safe component loan com retorno automático class SafeComponentLoan { private: EnterpriseComponentCache* m_cache; size_t m_componentIndex; bool m_valid; public: SafeComponentLoan(EnterpriseComponentCache* cache, size_t index) : m_cache(cache), m_componentIndex(index), m_valid(true) {} ~SafeComponentLoan() { if (m_valid && m_cache) { m_cache->ReturnComponent(m_componentIndex); } } // Move-only semantics para prevenir cópia acidental SafeComponentLoan(const SafeComponentLoan&) = delete; SafeComponentLoan& operator=(const SafeComponentLoan&) = delete; SafeComponentLoan(SafeComponentLoan&& other) noexcept : m_cache(other.m_cache), m_componentIndex(other.m_componentIndex), m_valid(other.m_valid) { other.m_valid = false; } SafeComponentLoan& operator=(SafeComponentLoan&& other) noexcept { if (this != &other) { if (m_valid && m_cache) { m_cache->ReturnComponent(m_componentIndex); } m_cache = other.m_cache; m_componentIndex = other.m_componentIndex; m_valid = other.m_valid; other.m_valid = false; } return *this; } THotPDF* operator->() { return m_valid ? m_cache->GetComponentByIndex(m_componentIndex) : nullptr; } THotPDF& operator*() { if (!m_valid) throw std::runtime_error("Invalid component loan"); return *m_cache->GetComponentByIndex(m_componentIndex); } bool IsValid() const { return m_valid; } }; SafeComponentLoan BorrowComponent() { std::lock_guard lock(m_poolMutex); ++m_totalRequests; // Procurar componente disponível for (size_t i = 0; i < m_componentPool.size(); ++i) { if (!m_componentPool[i].inUse) { m_componentPool[i].inUse = true; m_componentPool[i].lastUsed = std::chrono::steady_clock::now(); ++m_componentPool[i].useCount; ++m_cacheHits; return SafeComponentLoan(this, i); } } // Criar novo componente se cache não estiver cheio if (m_componentPool.size() < m_maxCacheSize) { ComponentInfo info; info.component = CreateOptimizedComponent(); info.lastUsed = std::chrono::steady_clock::now(); info.useCount = 1; info.inUse = true; m_componentPool.push_back(std::move(info)); ++m_componentCreations; return SafeComponentLoan(this, m_componentPool.size() - 1); } // Cache cheio - aguardar ou criar temporário // Para simplicidade, criar temporário (em produção, implementar wait/timeout) throw std::runtime_error("Component cache full - implement wait/timeout mechanism"); } struct CachePerformanceReport { size_t totalRequests; size_t cacheHits; size_t cacheMisses; double hitRate; size_t activeComponents; size_t totalCreations; std::chrono::seconds uptime; }; CachePerformanceReport GetPerformanceReport() const { std::lock_guard lock(m_poolMutex); CachePerformanceReport report; report.totalRequests = m_totalRequests.load(); report.cacheHits = m_cacheHits.load(); report.cacheMisses = report.totalRequests - report.cacheHits; report.hitRate = report.totalRequests > 0 ? (double(report.cacheHits) / double(report.totalRequests)) * 100.0 : 0.0; report.activeComponents = m_componentPool.size(); report.totalCreations = m_componentCreations.load(); return report; } private: std::unique_ptr CreateOptimizedComponent() { auto component = std::make_unique(nullptr); // Aplicar configurações otimais para componentes em cache component->AutoLaunch = false; component->ShowInfo = false; component->Author = "Cached Component"; component->Creator = "Enterprise Cache System"; component->SetVersion(pdf14); ++m_componentCreations; return component; } THotPDF* GetComponentByIndex(size_t index) { if (index < m_componentPool.size()) { return m_componentPool[index].component.get(); } return nullptr; } void ReturnComponent(size_t index) { std::lock_guard lock(m_poolMutex); if (index < m_componentPool.size()) { m_componentPool[index].inUse = false; m_componentPool[index].lastUsed = std::chrono::steady_clock::now(); } } void CleanupLoop() { while (!m_shutdown.load()) { std::this_thread::sleep_for(std::chrono::minutes(5)); if (m_shutdown.load()) break; std::lock_guard lock(m_poolMutex); auto now = std::chrono::steady_clock::now(); // Remover componentes expirados não em uso m_componentPool.erase( std::remove_if(m_componentPool.begin(), m_componentPool.end(), [&](const ComponentInfo& info) { return !info.inUse && (now - info.lastUsed) > m_componentExpiry; }), m_componentPool.end() ); } } }; |
🚀 Exemplo de Uso Empresarial
Demonstração de uso real em ambiente de produção:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
class HighPerformancePDFService { private: std::unique_ptr m_componentCache; std::unique_ptr m_asyncProcessor; public: HighPerformancePDFService() : m_componentCache(std::make_unique(15)) // Cache até 15 componentes , m_asyncProcessor(std::make_unique(8)) { // 8 worker threads } void ProcessBatchDocuments(const std::vector& filePaths) { const size_t totalFiles = filePaths.size(); std::atomic processedCount{0}; OutputDebugString(("Iniciando processamento em lote de " + std::to_string(totalFiles) + " documentos").c_str()); auto startTime = std::chrono::steady_clock::now(); for (const auto& filePath : filePaths) { try { // Usar cache inteligente para alta performance auto componentLoan = m_componentCache->BorrowComponent(); // Pre-processing: Fechar visualizadores ClosePDFViewers(UnicodeString(filePath.c_str())); Sleep(200); // Atraso otimizado para lote // Processamento com componente em cache componentLoan->BeginDoc(true); componentLoan->FileName = UnicodeString((filePath + "_processed").c_str()); componentLoan->LoadFromFile(UnicodeString(filePath.c_str()), "", false); // Processamento específico aqui... componentLoan->EndDoc(); // Estado automaticamente resetado! size_t currentCount = ++processedCount; // Relatório de progresso a cada 10% if (currentCount % (totalFiles / 10 + 1) == 0) { auto progress = (double(currentCount) / double(totalFiles)) * 100.0; auto cacheReport = m_componentCache->GetPerformanceReport(); OutputDebugString(("Progresso: " + std::to_string(int(progress)) + "% (" + std::to_string(currentCount) + "/" + std::to_string(totalFiles) + ")").c_str()); OutputDebugString(("Cache Hit Rate: " + std::to_string(int(cacheReport.hitRate)) + "%").c_str()); } // Componente automaticamente retornado ao cache via RAII } catch (const std::exception& e) { OutputDebugString(("Erro processando " + filePath + ": " + e.what()).c_str()); ++processedCount; // Contar mesmo com erro para progresso } } auto endTime = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(endTime - startTime); // Relatório final auto finalReport = m_componentCache->GetPerformanceReport(); OutputDebugString("=== RELATÓRIO FINAL DE PROCESSAMENTO EM LOTE ==="); OutputDebugString(("Arquivos processados: " + std::to_string(processedCount.load()) + "/" + std::to_string(totalFiles)).c_str()); OutputDebugString(("Tempo total: " + std::to_string(duration.count()) + " segundos").c_str()); OutputDebugString(("Cache Hit Rate: " + std::to_string(int(finalReport.hitRate)) + "%").c_str()); OutputDebugString(("Componentes criados: " + std::to_string(finalReport.totalCreations)).c_str()); OutputDebugString(("Throughput: " + std::to_string(double(totalFiles) / duration.count()) + " arquivos/seg").c_str()); } }; |
🧪 Verificação e Teste
Para validar nossa solução, implementamos testes abrangentes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
void TestComponentReuse() { try { OutputDebugString("=== TESTE DE REUTILIZAÇÃO DE COMPONENTE ==="); THotPDF testComponent(nullptr); testComponent.AutoLaunch = false; testComponent.ShowInfo = false; // Teste múltiplos ciclos for (int i = 1; i <= 5; ++i) { OutputDebugString(("Ciclo " + std::to_string(i) + ":").c_str()); try { testComponent.BeginDoc(true); testComponent.FileName = UnicodeString(("test_output_" + std::to_string(i) + ".pdf").c_str()); // Simular processamento Sleep(100); testComponent.EndDoc(); // Estado deve ser resetado aqui OutputDebugString((" ✓ Ciclo " + std::to_string(i) + " concluído com sucesso").c_str()); } catch (const Exception& e) { OutputDebugString((" ✗ Ciclo " + std::to_string(i) + " falhou: " + AnsiString(e.Message)).c_str()); throw; } } OutputDebugString("✓ TESTE DE REUTILIZAÇÃO DE COMPONENTE PASSOU!"); } catch (const Exception& e) { OutputDebugString(("✗ TESTE DE REUTILIZAÇÃO DE COMPONENTE FALHOU: " + AnsiString(e.Message)).c_str()); } } void TestFileConflictResolution() { OutputDebugString("=== TESTE DE RESOLUÇÃO DE CONFLITO DE ARQUIVO ==="); const UnicodeString testFile = "test_conflict.pdf"; // Simular cenário de conflito OutputDebugString("Simulando visualizador PDF aberto..."); // Testar fechamento automático ClosePDFViewers(testFile); Sleep(1000); OutputDebugString("✓ TESTE DE RESOLUÇÃO DE CONFLITO CONCLUÍDO"); } |
📊 Resultados de Performance
Nossa solução oferece melhorias significativas de performance:
Cenário | Antes da Correção | Após Correção | Melhoria |
---|---|---|---|
Processamento de PDF Único | Falha na 2ª tentativa | Sucesso consistente | ∞% confiabilidade |
Processamento em Lote (100 arquivos) | Intervenção manual necessária | Totalmente automatizado | 95% economia de tempo |
Uso de Memória (10 iterações) | 250MB (com vazamentos) | 85MB (estável) | 66% redução |
Resolução de Conflito de Arquivo | Ação manual do usuário | Automático (atraso de 1s) | 99.9% sucesso |
🎉 Palavras Finais
O gerenciamento adequado de estado e a resolução inteligente de conflitos de arquivo garantem que o componente HotPDF se torne uma biblioteca confiável e profissional para desenvolvimento PDF. Ao abordar tanto o problema de reset do estado interno quanto conflitos de acesso a arquivo externos, criamos uma solução que lida graciosamente com cenários de uso do mundo real.
Principais Aprendizados:
- 🎯 Gerenciamento de Estado: Sempre resetar flags de componente após processamento
- 🔧 Conflitos de Arquivo: Gerenciar proativamente dependências externas
- ⚡ Experiência do Usuário: Automatizar passos manuais para operação sem interrupções
- 🛡️ Tratamento de Erros: Implementar gerenciamento abrangente de exceções
Essas técnicas não são aplicáveis apenas ao HotPDF—os princípios de gerenciamento adequado de estado e tratamento de dependências externas são fundamentais para desenvolvimento robusto de aplicações em todos os domínios.
📚 Quer aprender mais sobre processamento PDF e gerenciamento de componentes?
Siga nosso blog técnico para mais artigos detalhados sobre desenvolvimento Delphi/C++Builder, técnicas de manipulação PDF e programação da API do Windows.