Pular para o conteúdo principal
Esta seção fornece instruções sobre como implantar um aplicativo do FRE 12 para Windows no Azure App Service. Como exemplo, é apresentado um par de projetos WebJob que usam dados de uma conta de Armazenamento do Azure. O processamento dos arquivos é realizado usando um contêiner de blobs.
Com esse cenário, você obterá os melhores resultados de reconhecimento para documentos pequenos de uma página, como faturas, recibos etc.
A implantação do seu aplicativo no App Service inclui várias etapas:
  1. Preparar sua máquina local e a instância do aplicativo usando os pré-requisitos
  2. Executar as etapas preparatórias antes de implantar seu aplicativo
  3. Implantar e executar seu aplicativo no App Service
Use os exemplos de código apresentados na instrução abaixo.

Pré-requisitos

Máquina local

Antes de criar seu App Service, use a especificação a seguir para preparar sua máquina local:
  • Visual Studio 2019 e seus módulos para desenvolvimento de aplicativos para Azure (consulte recurso do Azure no Visual Studio ou use o Visual Studio Installer para baixar esses módulos)
  • SDK do Azure (baixe aqui)
  • .NET Framework 4.7.2
  • Pacote NuGet para trabalhar com o Azure Storage e contêineres de blob:
    • Azure.Storage.Blobs (baixe aqui)
    • Azure.Storage.Queues (baixe aqui)
    • Newtonsoft.Json (baixe aqui)
    • System.IO.Compression.ZipFile (baixe aqui)
  • wrapper do ABBYY FineReader Engine para .NET Framework 4.7 (na pasta C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops após a instalação para desenvolvedor)
  • interface IFileWriter sobrescrita para trabalhar com os contêineres de blob (consulte o exemplo abaixo)
  • Azure Storage Explorer (opcional - baixe aqui)
  • Máquina virtual na sua conta do Azure destinada aos procedimentos de licenciamento. Para a configuração posterior, você precisa de:
    • Endereço IP
    • Porta de conexão aberta (padrão ou definida pelo usuário). Para abri-la, configure o firewall
    • Detalhes da conexão via protocolo de rede Sockets

Etapas preparatórias

As etapas preparatórias devem ser realizadas na sua máquina local. Ao concluir essas etapas, você preparará todas as configurações e arquivos necessários para começar a implantar sua aplicação:
  1. Crie um arquivo compactado com a biblioteca ABBYY FineReader Engine (por exemplo, LibraryPackage.zip). A lista de arquivos está no arquivo FREngineDistribution.csv.
    Importante! Se você tiver espaço de armazenamento limitado (por exemplo, se usar um App Service Plan com 1 GB de espaço), recomendamos usar a opção /extract para criar um pacote ABBYY FineReader Engine personalizado com tamanho mínimo. O restante do espaço de armazenamento será usado para processar os arquivos.
    Ao criar um arquivo compactado, leve em consideração que as configurações de licenciamento do ABBYY FineReader Engine devem ser definidas de acordo com as configurações da máquina virtual:
    • O arquivo LicensingSettings.xml deve ser configurado para a configuração de rede (consulte Trabalhando com o arquivo LicensingSettings.xml).
    • O protocolo de rede Sockets deve ser usado.
    • O arquivo de token da Licença Online deve estar localizado na pasta Bin64.
  2. Crie uma conta de Armazenamento do Azure (frestorage, neste artigo). Você encontra todas as instruções necessárias no site do Azure.
  3. Crie seu App Service conforme desejado (veja as instruções aqui).
  4. Crie dois contêineres de Blob dentro de frestorage:
    • fre-lib - para os arquivos do ABBYY FineReader Engine
    • processing-container - para os resultados do processamento
  5. Faça upload de LibraryPackage.zip para o contêiner fre-lib da forma mais conveniente (usando .NET, Powershell, um script Python ou os aplicativos Azure Storage Explorer/Azure Portal).
  6. Implante e configure a máquina virtual com as configurações de licenciamento na sua conta do Azure:
    • Instale o utilitário License Manager por meio do installLM.exe incluído em LibraryPackage.zip.
    • Configure o protocolo de rede Sockets no LicensingSettings.xml e, em seguida, reinicie o Serviço de Licenciamento.
    • Certifique-se de que o Azure App Service possa acessar a porta de conexão do Serviço de Licenciamento (ajuste as regras do Windows Firewall na máquina virtual).
    • Ative sua licença (somente para proteção por software; a proteção online não requer ativação).
  7. Crie duas filas dentro de frestorage:
    • processing-queue - para definir as tarefas de processamento dos arquivos
    • status-queue - para notificar a conclusão das tarefas
  8. Crie dois projetos do Azure WebJob (.NET Framework) no Visual Studio 2019 para trabalhar com frestorage:
    • FreDeployerJob - para implantar LibraryPackage.zip no App Service (veja a listagem de seus arquivos: Config.cs, Functions.cs, Program.cs abaixo)
    • FreProcessorJob - para processamento de documentos (veja a listagem de seus arquivos: Config.cs, Functions.cs, Program.cs, EngineLoader.cs, IFileWriter.cs, Processor.cs abaixo)

Implantando e executando o ABBYY FineReader Engine no App Service

Para implantar o ABBYY FineReader Engine:
  1. Publique o FreDeployerJob no Azure App Service usando o Visual Studio (defina Triggered em WebJob Type).
  2. Abra o App Service no portal do Azure.
  3. Abra a seção WebJobs do App Service.
  4. Encontre o FreDeployerJob na lista de WebJobs.
  5. Inicie o FreDeployerJob clicando com o botão direito e escolhendo o comando Run na guia WebJobs.
Você pode acessar a guia Logs para verificar o resultado da implantação. Se ela for bem-sucedida, o LibraryPackage.zip será carregado do contêiner fre-lib e implantado na pasta %HOME_EXPANDED%, disponível para todos os componentes no App Service. Para implantar o FreProcessorJob, publique o FreProcessorJob no Azure App Service usando o Visual Studio (defina Continuous em WebJob Type). Como resultado, o FreProcessorJob aparecerá na lista de WebJobs do App Service. Para processar um arquivo:
  1. Faça upload do arquivo que você pretende processar para o processing-container.
  2. Adicione uma mensagem JSON para uma nova tarefa de processamento no formato {“blob-item-name” : “file_name”} à processing-queue. Se você fizer upload de Demo.tif para o processing-container, a mensagem deverá ser:
{"blob-item-name" : "Demo.tif"}
  1. Aguarde a conclusão da tarefa. Assim que a nova tarefa é definida, o FreProcessorJob começa a processar o arquivo especificado na memória. A status-queue conterá entradas sobre a execução dessa tarefa.
  2. Encontre o arquivo de saída no processing-container.
  1. O FreProcessorJob opera como um processo de thread única. Se você pretende processar seus arquivos em paralelo, precisa criar vários FreProcessorJob que ficarão escutando a mesma fila. 2. Cada FreProcessorJob adicional consome memória extra. Leve esse fato em consideração ao adquirir seu Service Plan. Por exemplo, no Azure Free Service Plan, é melhor ter apenas um FreProcessorJob, que consome pouca memória e, assim, garante a estabilidade do processamento de arquivos. 3. Usar um único FreProcessorJob não é adequado para processar documentos grandes com várias páginas. Nesse caso, considere o reconhecimento do seu documento no Azure Cloud Service ou na Azure Virtual Machine em vez do App Service.

Exemplos de código

Esta seção contém exemplos de código usados para implantar e implementar a API do ABBYY FineReader Engine no App Service.

FreDeployerJob:

using System.IO;
class Config
{
    // String de conexão com o contêiner de blobs
    public static readonly string ConnectionString = "your_connection_string";
    // O diretório HOME_EXPANDED é comum a todas as pastas do WebJobs
    public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
    // Nomes dos contêineres de entrada e saída no seu armazenamento
    public static readonly string LibraryContainerName = "fre-lib";
namespace FreDeployerJob
{
    public class Functions
    {
        // Esta função não será acionada automaticamente; você deve fazer isso manualmente
        [NoAutomaticTrigger]
        [Timeout("01:00:00")]
        public static void DeployFRE()
        {
            Console.WriteLine("Deploying FRE");
            // Conecta ao contêiner de entrada existente <InputContainerName> por meio da string de conexão da conta de armazenamento
            BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.LibraryContainerName);
            // Cria o diretório da biblioteca, bem como as pastas AppData e Temp para a inicialização do ABBYY FineReader Engine
            if (Directory.Exists(Config.LibraryFolder) == true)
            {
                Directory.Delete(Config.LibraryFolder, true);
            }
            Directory.CreateDirectory(Config.LibraryFolder);
            Directory.CreateDirectory(Path.Combine(Config.LibraryFolder, "Temp"));
            Directory.CreateDirectory(Path.Combine(Config.LibraryFolder, "AppData"));
            // Itera pelos blobs no contêiner. O blob em <InputContainerName> corresponde a um arquivo de imagem
            foreach (BlobItem blobItem in inputContainerClient.GetBlobs())
            {
                Console.WriteLine("\t" + blobItem.Name);
                // Procura a versão leve da biblioteca ABBYY FineReader Engine
                if (blobItem.Name == "LibraryPackage.zip")
                {
                    Console.WriteLine("LibraryPackage.zip was found.");
                    // Conecta ao blob para acessar seu conteúdo
                    BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.LibraryContainerName, blobItem.Name);
                    Console.WriteLine("Downloading to memory...");
                    // Baixa o arquivo ZIP para a memória
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        blobClient.DownloadTo(memoryStream);
                        Console.WriteLine("LibraryPackage.zip was downloaded.");
                        Console.WriteLine("Unzipping...");
                        // Descompacta sem usar a pasta temporária (somente processamento em memória)
                        using (ZipArchive archive = new ZipArchive(memoryStream))
                        {
                            foreach (ZipArchiveEntry entry in archive.Entries)
                            {
                                string subDirectory = Path.GetDirectoryName(Path.Combine(Config.LibraryFolder, entry.FullName));
                                if (Directory.Exists(subDirectory) == false)
                                {
                                    Directory.CreateDirectory(subDirectory);
                                }
                                if (entry.Name.Length != 0)
                                {
                                    entry.ExtractToFile(Path.Combine(subDirectory, entry.Name));
                                }
                            }
                        }
                        Console.WriteLine("LibraryPackage.zip was unzipped to HOME_EXPANDED.");
                    }
                }
            }
        }
    }
}
namespace FreDeployerJob
{
    // Para saber mais sobre o Microsoft Azure WebJobs SDK, consulte https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Defina as seguintes strings de conexão em app.config para que esses WebJobs sejam executados:
        // AzureWebJobsDashboard and AzureWebJobsStorage
        static void Main()
        {
            // Quando acionado, este WebJob chamará apenas este método
            Functions.DeployFRE();
        }
    }
}

FreProcessorJob:

using System;
using System.IO;
namespace FreProcessorJob
{
    class Config
    {
        // String de conexão com o contêiner de blobs
        public static readonly string ConnectionString = "your_connection_string";
        // O diretório HOME_EXPANDED é comum a todas as pastas do WebJobs
        // É o mesmo do projeto FreDeployerJob
        public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
        // Nome do processing-container no seu armazenamento
        public static readonly string ProcessingContainerName = "processing-container";
        // Nome da fila de processamento
        public static readonly string ProcessingQueueName = "processing-queue";
        public static readonly string StatusQueueName = "status-queue";
        // Retorna o ID do projeto do cliente para o ABBYY FineReader Engine
        public static String GetCustomerProjectId()
        {
            return "your_cpid";
        }
 
        // Retorna o nome do token da Licença Online
        // Se você não usar Licença Online, deixe uma string vazia
        // O token deve estar localizado na pasta Bin64 do pacote da biblioteca
        public static String GetLicenseTokenName()
        {
            return "your_online_license_token_if_you_have_it";
        }
 
        // Retorna a senha da Licença Online
        // Se você não usar Licença Online, deixe uma string vazia
        public static String GetLicensePassword()
        {
            return "online_license_password_if_you have_it";
        }
 
        // Retorna o nome do token de licença
        // Os arquivos de licenciamento ficam localizados na pasta Bin64 do pacote da biblioteca
        public static String GetLicenseTokenName()
        {
            return "your_licence_for_ABBYY FineReader Engine";
        }
 
        // Retorna a senha da licença
        public static String GetLicensePassword()
        {
            return "license_password";
        }
 
        // Retorna o caminho do mecanismo
        public static String GetEngineFolder()
        {
            string engineSubfolder = "Bin64";
            string engineDllFolder = Path.Combine(LibraryFolder, engineSubfolder);
            return engineDllFolder;
        }
    }
}
using Microsoft.Azure.WebJobs;
using System;
using System.IO;
using Azure.Storage.Blobs;
using Azure.Storage.Queues;
using Newtonsoft.Json.Linq;
namespace FreProcessorJob
{
    public class Functions
    {
        // Esta função é acionada/executada quando uma nova mensagem é gravada
        // em uma Azure Queue chamada processing-queue
        // Espera-se que a mensagem seja uma mensagem JSON com a chave 'blob-item-name'
        // os resultados do processamento serão salvos no contêiner de processamento
        // o status do processamento será enviado para status-queue no formato JSON
        public static void ProcessQueueMessage([QueueTrigger("processing-queue")] string message)
        {
            // Primeiro, conecte-se à status-queue
            QueueClient queueClient = new QueueClient(Config.ConnectionString, Config.StatusQueueName);
            try
            {
                // Isso será registrado nos logs do WebJob no portal do Azure
                Console.WriteLine("Accepted task: " + message);
                JObject task = JObject.Parse(message);
                task["processor_id"] = Environment.GetEnvironmentVariable("WEBJOBS_NAME");
 
                // Isso será enviado para status-queue
                task["status"] = "accepted";
                queueClient.SendMessage(task.ToString());
 
                // Obtém blob-item-name, o nome do arquivo no contêiner Processing
                string blobFileName = task["blob-item-name"].ToString();
                BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.ProcessingContainerName, blobFileName);
                // Carrega o blob na memória
                Console.WriteLine("\t Downloading blob to memory: " + blobFileName);
                MemoryStream memoryStream = new MemoryStream();
                blobClient.DownloadTo(memoryStream);
                Console.WriteLine("\t Downloaded.");
                // Atualiza o status para processing
                Console.WriteLine("\t Processing in FRE: " + blobFileName);
                task["status"] = "processing";
                queueClient.SendMessage(task.ToString());
                // Processa o blob baixado no ABBYY FineReader Engine usando métodos de processamento em memória
                // A saída é o nome do resultado do processamento salvo como blob em <ProcessingContainerName>
                string resultBlobName = "";
                using (FreProcessor.Processor freProcessor = new FreProcessor.Processor())
                {
                    resultBlobName = freProcessor.ProcessBlobFromMemory(memoryStream, blobFileName);
                    Console.WriteLine("\t Result blob name in output container: " + resultBlobName);
                }
                // Exclui a imagem de entrada
                Console.WriteLine("\t Deleting from input container: " + blobFileName);
                blobClient.Delete();
                // Atualiza o status para succeeded
                // nos logs do portal do Azure
                Console.WriteLine("Succeeded");
 
                // na status-queue
                task["status"] = "succeeded";
                task["result-blob-name"] = resultBlobName;
                queueClient.SendMessage(task.ToString());
            }
            catch (Exception error)
            {
                // Em caso de erro, faz o reporte
                // nos logs do portal do Azure
                Console.WriteLine("Failed: " + error.Message);
                // para status-queue
                JObject task = new JObject(); 
                task["processor_id"] = Environment.GetEnvironmentVariable("WEBJOBS_NAME");
                task["status"] = "failed";
                task["error"] = error.Message;
                task["task"] = message;
                queueClient.SendMessage(task.ToString());
            }
        }
    }
}
using Microsoft.Azure.WebJobs;
namespace FreProcessorJob
{
    // Para saber mais sobre o Microsoft Azure WebJobs SDK, consulte https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Defina as seguintes connection strings no app.config para que estes WebJobs sejam executados:
        // AzureWebJobsDashboard and AzureWebJobsStorage
        static void Main()
        {
            var config = new JobHostConfiguration();
            if (config.IsDevelopment)
            {
                config.UseDevelopmentSettings();
            }
            // O ABBYY FineReader Engine não é thread-safe, portanto não podemos processar mais de uma mensagem ao mesmo tempo
            config.Queues.BatchSize = 1;
            var host = new JobHost(config);
            // O código a seguir garante que o WebJob será executado continuamente
            // já que uma das funções está vinculada à Azure Queue e escuta novas tarefas
            host.RunAndBlock();
        }
    }
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using FREngine;
namespace FreProcessorJob.FreProcessor
{
    // Classe para carregar/descarregar FREngine.dll e inicializar/desinicializar o Engine
    // O carregamento é feito no construtor, o descarregamento em Dispose()
    // Lança exceções quando o carregamento falha
    public class EngineLoader : IDisposable
    {
        // Carrega o ABBYY FineReader Engine com as configurações armazenadas em SamplesConfig.cs
        public EngineLoader()
        {
            string enginePath = Path.Combine(Config.GetEngineFolder(), "FREngine.dll");
            string customerProjectId = Config.GetCustomerProjectId();
            string licensePath = Path.Combine(Config.GetEngineFolder(), Config.GetLicenseTokenName());
            string licensePassword = Config.GetLicensePassword();
            try
            {
                // Carrega a biblioteca FREngine.dll
                dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
                if (dllHandle == IntPtr.Zero)
                {
                    int error = Marshal.GetLastWin32Error();
                    Console.WriteLine("O último erro Win32 foi: " + error);
                    throw new Exception("Não foi possível carregar " + enginePath);
                }
 
                IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
                if (initializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Não foi possível encontrar a função InitializeEngine");
                }
                IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
                if (deinitializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Não foi possível encontrar a função DeinitializeEngine");
                }
                IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
                if (dllCanUnloadNowPtr == IntPtr.Zero)
                {
                    throw new Exception("Não foi possível encontrar a função DllCanUnloadNow");
                }
                // Converte ponteiros em delegates
                initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    initializeEnginePtr, typeof(InitializeEngine));
                deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    deinitializeEnginePtr, typeof(DeinitializeEngine));
                dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                    dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
                // Chama a função InitializeEngine
                string dataFolder = Path.Combine(Config.LibraryFolder, "AppData");
                string tempFolder = Path.Combine(Config.LibraryFolder, "Temp");
                int hresult = initializeEngine(customerProjectId, licensePath, licensePassword,
                    dataFolder, tempFolder, false, ref engine);
                Marshal.ThrowExceptionForHR(hresult);
            }
            catch (Exception)
            {
                // Libera a biblioteca FREngine.dll
                engine = null;
                // Removendo todos os objetos antes da chamada de FreeLibrary
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                FreeLibrary(dllHandle);
                dllHandle = IntPtr.Zero;
                initializeEngine = null;
                deinitializeEngine = null;
                dllCanUnloadNow = null;
                throw;
            }
        }
        // Descarrega o ABBYY FineReader Engine
        public void Dispose()
        {
            if (engine == null)
            {
                // O Engine não foi carregado
                return;
            }
            engine = null;
            int hresult = deinitializeEngine();
            // Removendo todos os objetos antes da chamada de FreeLibrary
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            hresult = dllCanUnloadNow();
            if (hresult == 0)
            {
                FreeLibrary(dllHandle);
            }
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            // Lançando exceção após a limpeza
            Marshal.ThrowExceptionForHR(hresult);
        }
        // Retorna o ponteiro para o objeto principal do ABBYY FineReader Engine
        public IEngine Engine
        {
            get
            {
                return engine;
            }
        }
        // Funções do Kernel32.dll
        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
        private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
        [DllImport("kernel32.dll")]
        private static extern bool FreeLibrary(IntPtr hModule);
        // Funções do FREngine.dll
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
        private delegate int InitializeEngine(string customerProjectId, string licensePath, string licensePassword,
            string dataFolder, string tempFolder, bool isSharedCPUCoresMode, ref FREngine.IEngine engine);
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate int DeinitializeEngine();
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate int DllCanUnloadNow();
        // variáveis privadas
        private FREngine.IEngine engine = null;
        // Handle para FREngine.dll
        private IntPtr dllHandle = IntPtr.Zero;
        private InitializeEngine initializeEngine = null;
        private DeinitializeEngine deinitializeEngine = null;
        private DllCanUnloadNow dllCanUnloadNow = null;
    }
}
using System;
using System.IO;
using Azure.Storage.Blobs;
namespace FreProcessorJob.FreProcessor
{
    public class FileWriter : FREngine.IFileWriter, IDisposable
    {
        public FileWriter(string _resultBlobName, string _fileExtension)
        {
            resultBlobName = _resultBlobName;
            fileExtension = _fileExtension;
        }
        public void Open(string fileName, ref int bufferSize)
        {
            stream = new MemoryStream();
        }
        public void Write(byte[] data)
        {
            stream.Write(data, 0, data.Length);
        }
        public void Close()
        {
            // Criando uma conexão com um novo blob em <ProcessingContainerName>. O resultado do processamento será armazenado lá
            BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, 
                Config.ProcessingContainerName,
                resultBlobName + fileExtension);
            // Sobrescreve o arquivo existente
            resultBlobClient.DeleteIfExists();
            // Define a posição como 0 para gravar o arquivo desde o início
            stream.Position = 0;
            resultBlobClient.Upload(stream);
            stream.Close();
        }
        public void Dispose()
        {
            // Fecha o stream de memória ao descartar o objeto para que ele possa ser acessado após a gravação dos dados
            stream.Close();
        }
        private string resultBlobName;
        private string fileExtension;
        private MemoryStream stream;
    }
}
using System;
using System.Runtime.InteropServices;
using System.IO;
using FREngine;
namespace FreProcessorJob.FreProcessor
{
    class Processor : IDisposable
    {
        private EngineLoader engineLoader = null;
        private void displayMessage(string text)
        {
            Console.WriteLine("\t" + text);
        }
        private void setupFREngine()
        {
            displayMessage("Carregando perfil predefinido...");
            // isto é opcional
            engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
            // isto é obrigatório em App Service Plans de baixo desempenho, pois teremos erros no processamento paralelo
            engineLoader.Engine.MultiProcessingParams.MultiProcessingMode = MultiProcessingModeEnum.MPM_Sequential;
        }
        private void LoadEngine()
        {
            try
            {
                if (engineLoader == null)
                {
                    engineLoader = new EngineLoader();
                }
                setupFREngine();
            }
            catch (Exception error)
            {
                displayMessage("erro: " + error.Message);
            }
        }
        private void UnloadEngine()
        {
            try
            {
                if (engineLoader != null)
                {
                    engineLoader.Dispose();
                    engineLoader = null;
                }
            }
            catch (Exception error)
            {
                displayMessage("erro: " + error.Message);
            }
        }
        public Processor()
        {
            LoadEngine();
        }
        public string ProcessBlobFromMemory(MemoryStream inputMemoryStream, string inputBlobName)
        {
            FRDocument document = engineLoader.Engine.CreateFRDocument();
            string resultBlobName = "";
            try
            {
                document.PageFlushingPolicy = FREngine.PageFlushingPolicyEnum.PFP_KeepInMemory;
 
                // Adiciona o arquivo de imagem ao documento
                displayMessage("Carregando imagem...");
                IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                document.AddImageFileFromMemory(handle.ToInt64(), null, null);
                // Reconhece o documento
                displayMessage("Reconhecendo...");
                document.Process(null);
                // Salva os resultados
                displayMessage("Salvando resultados...");
                FileWriter fileWriter = new FileWriter(inputBlobName, ".pdf");
                resultBlobName = inputBlobName + ".pdf";
                document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
            }
            catch (Exception error)
            {
                displayMessage("erro: " + error.Message);
                throw error;
            }
            finally
            {
                // Fecha o documento
                document.Close();
            }
            return resultBlobName;
        }
        public void Dispose()
        {
            UnloadEngine();
        }
    }
}