Esecuzione di ABBYY FineReader Engine nei servizi di Azure
Esecuzione in Azure Cloud Service
Questa sezione fornisce istruzioni su come distribuire un’applicazione FRE 12 per Windows in Azure Cloud Service. Come esempio, viene mostrato un Worker che elabora file tramite un contenitore in un account di archiviazione di Azure.Per implementare questo scenario, viene utilizzata una licenza online che si connette al server di licenze *.abbyy.com.
ABBYY Licensing Service può funzionare solo con una licenza online alla volta.
La distribuzione dell’applicazione in Cloud Service comprende diversi passaggi:
Configurare la licenza online, il computer locale e l’istanza cloud usando i prerequisiti
Per usare una licenza online su qualsiasi computer in cui sia installato ABBYY Licensing Service, devono essere soddisfatte le seguenti condizioni:
Connessione Internet attiva
Connessioni consentite a *.abbyy.com sulla porta 443 (HTTPS)
Certificato radice GoDaddy per l’autorità di certificazione (deve essere installato nell’archivio certificati Autorità di certificazione radice attendibili del computer locale. Per informazioni dettagliate sul certificato, vedi il sito web di GoDaddy).
Prima di creare il tuo Cloud Service, usa le specifiche seguenti per predisporre il computer locale:
Visual Studio 2019 e i relativi moduli per sviluppare applicazioni per Azure (consulta la funzionalità Azure in Visual Studio oppure usa Visual Studio Installer per scaricare questi moduli)
Emulatori e spazio di archiviazione di Cloud Service per semplificare il debug (vedi le informazioni qui e scarica tramite Visual Studio Installer)
Soluzione modello per Azure Cloud Service (extended preview) con un singolo progetto Worker role (vedi le informazioni qui)
Pacchetto NuGet per lavorare con Azure Storage e i blob contenitori - Azure.Storage.Blobs (scarica qui)
Wrapper di ABBYY FineReader Engine per .NET Framework 4.7 (nella cartella C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops dopo l’installazione per sviluppatori)
Interfaccia IFileWriter con override per lavorare con i blob contenitori (vedi l’esempio sotto)
Azure Storage Explorer (facoltativo - scarica qui)
I passaggi preparatori devono essere eseguiti sul computer locale. Completandoli, predisporrai tutte le impostazioni e i file necessari per iniziare la distribuzione dell’applicazione:
Crea due archivi contenenti ABBYY FineReader Engine Library e Licensing Service (ad esempio, LibraryPackage.zip e LSPackage.zip). Puoi generare automaticamente l’elenco dei file con l’aiuto del file FREngineDistribution.csv. Usa ABBYY FineReader Engine e License Server dello stesso pacchetto; in caso contrario, la compatibilità non è garantita.
Crea l’account di archiviazione di Azure (frestorage in questo articolo). Tutte le istruzioni necessarie sono disponibili sul sito web di Azure.
Crea tre contenitori Blob all’interno di frestorage:
fre-lib - per i file di ABBYY FineReader Engine
fre-input - per i file in ingresso
fre-output - per i risultati dell’elaborazione
Carica LibraryPackage.zip e LSPackage.zip nel contenitore fre-lib nel modo che ritieni più pratico (usando .NET, Powershell, uno script Python oppure le applicazioni Azure Storage Explorer/Azure Portal).
Come esempio, viene usato un progetto WorkerRole per operare in un ambiente configurato. Tutti i file di configurazione necessari (.csdef e Cloud.cscfg) vengono generati automaticamente dopo la creazione del progetto.
Distribuire ABBYY FineReader Engine in Cloud Service
Per distribuire ABBYY FineReader Engine nel nuovo progetto WorkerRole:
Specifica i parametri di Cloud.cscfg in base alle esigenze.
Specifica i parametri del file .csdef:
(facoltativo) le impostazioni di WorkerRole
(facoltativo) le dimensioni della macchina virtuale
(obbligatorio) l’archiviazione locale del ruolo (sezione LocalStorage; in questo articolo, l’archiviazione denominata LocalStorage1). Impostala ad almeno 3 GB per garantire la corretta distribuzione del pacchetto ABBYY FineReader Engine.
(obbligatorio) l’ordine di avvio del ruolo (usa gli esempi di codice e le impostazioni della sezione code samples)
Implementa il progetto WorkerRole per elaborare i file dal contenitore fre-input e pubblicare i risultati dell’elaborazione nel contenitore fre-output:
il metodo OnStart per preparare Cloud Service all’esecuzione. Questo metodo viene usato per inizializzare l’Engine e configurare il protocollo TLS.
il metodo RunAsync per elaborare ciclicamente i file prelevati dal contenitore fre-input. Questo metodo rileva i file nel contenitore fre-input, li elabora in memoria e li colloca nel contenitore fre-output (vedi Processor.cs, IFileWriter.cs e Config.cs).
il metodo OnStop per terminare l’esecuzione di Cloud Service. Questo metodo viene usato per deinizializzare l’Engine.
Questa sezione include esempi di codice usati per configurare l’ordine di avvio dei ruoli e le impostazioni di ABBYY FineReader Engine:
CleanUpOnStart - elimina la versione precedente di ABBYY FineReader Engine e ne semplifica la procedura di aggiornamento (vedi il relativo contenuto nei file CleanUpOnStart.cmd e CleanUpOnStart.ps1 riportati di seguito).
PreparePoShModules - scarica l’SDK necessario affinché PowerShell possa funzionare con Azure Storage (vedi il relativo contenuto nei file PreparePoShModules.cmd e PreparePoShModules.ps1 riportati di seguito).
PrepareLibrary - carica ed estrae ABBYY FineReader Engine Library dal contenitore fre-lib nell’archiviazione locale (vedi il relativo contenuto nei file PrepareLibrary.cmd e PrepareLibrary.ps1 riportati di seguito).
PrepareLS - carica, estrae e avvia il Licensing Service dal contenitore fre-lib (vedi il relativo contenuto nei file PrepareLS.cmd e PrepareLS.ps1 riportati di seguito).
È necessario che gli script elencati sopra vengano eseguiti esattamente nell’ordine proposto, prima di avviare l’applicazione. Per definire l’ordine di esecuzione degli script, specifica i seguenti attributi:
taskType=“simple” - le attività vengono eseguite in modo sincrono, una alla volta.
executionContext=“elevated” - esegue uno script di avvio con diritti di amministratore (necessario per poter installare qualsiasi applicazione ed eseguire LicensingService.exe)
In questo modo, verrà creata una cartella che Cloud Service può gestire. Questa cartella viene usata per caricare ABBYY FineReader Engine tramite gli script elencati sopra e per caricare FREngine.dll in vista del suo successivo utilizzo nell’esempio.
CleanUpOnStart.cmd
rem Script to clean up previous ABBYY FineReader Engine and Licensing Service packagesecho Cleaning up before launching service >> ".\CleanUpOnStart.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\CleanUpOnStart.ps1 >> ".\CleanUpOnStart.log" 2>&1echo Cleaning up beafore launching service. >> ".\CleanUpOnStart.log" 2>&1exit /B %errorlevel%
CleanUpOnStart.ps1
Write-Host "CleanUpOnStart.ps1 started...";# percorso dell'archiviazione locale (vedere ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');# arresta il processo Licensing Service se è in esecuzione$processes = Get-Process;foreach( $process in $processes ) { if( $process.Name -eq "LicesingService" ) { Stop-Process $process; }}# pulisce e ricrea la cartella fre_packages$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# pulisce e ricrea la cartella Input$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Input';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;# pulisce e ricrea la cartella Results$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Results';if( Test-Path -Path $destination_path ) { Remove-Item $destination_path -Recurse -Force;}New-Item -ItemType Directory -Path $destination_path;Write-Host ("Resource folder was cleaned up.");
PreparePoShModules.cmd
rem Script per preparare i moduli, in modo da potersi connettere all'account di archiviazione di Azure e scaricare i pacchetti di ABBYY FineReader Engine e Licensing Serviceecho Preparing PoSh Modules >> ".\PreparePoShModules.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PreparePoShModules.ps1 >> ".\PreparePoShModules.log" 2>&1echo PoSh Modules wer prepared. >> ".\PreparePoShModules.log" 2>&1exit /B %errorlevel%
PreparePoShModules.ps1
# installazione dei moduliWrite-Host "PreparePoShModules.ps1 started...";# installazione del provider di pacchetti NuGet per scaricare i moduli AzureWrite-Host "Installing NuGet package provider...";Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force;# installazione del modulo Azure Storage per usare il blobWrite-Host "Installing Az.Storage module...";Install-Module -Name Az.Storage -Scope CurrentUser -Repository PSGallery -Force -AllowClobber;Write-Host ("PoSh modules were prepared.");
PrepareLibrary.cmd
rem Script per preparare il pacchetto ABBYY FineReader Engineecho Preparing Library >> ".\PrepareLibrary.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLibrary.ps1 >> ".\PrepareLibrary.log" 2>&1echo Library was prepared. >> ".\PrepareLibrary.log" 2>&1exit /B %errorlevel%
PrepareLibrary.ps1
Write-Host "PrepareLibrary.ps1 avviato...";# contenitore con i pacchetti di ABBYY FineReader Engine e Licensing Service$container_name = 'fre-lib';# stringa di connessione dell'account di archiviazione di Azure $connection_string = "<connection_string_to_frestorage>";Write-Host "Configurazione";Write-Host ("Nome del contenitore: " + $container_name);Write-Host ("Stringa di connessione: " + $storage_account);# connessione all'oggetto di archiviazioneWrite-Host ("Connessione all'account di archiviazione di Azure...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# recupero dei blob (in pratica, file)Write-Host ("Recupero dei blob dal contenitore...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# percorso dell'archiviazione locale (vedere ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';# download dell'unico blob necessarioWrite-Host ("Download del pacchetto FRE dal contenitore...");foreach( $blob in $fre_blobs ) { Write-Host $blob.Name; if( $blob.Name -eq "LibraryPackage.zip" ) { Write-Host ("Download del blob: " + $blob.Name + " in " + $destination_path); Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name ` -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account; } else { Write-Host ("Download del blob " + $blob.Name + " ignorato."); }}# decompressione del pacchetto scaricato (la libreria precedente verrà sovrascritta forzatamente)Write-Host ("Decompressione del pacchetto della libreria...");Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LibraryPackage.zip") ` -DestinationPath (Join-Path -Path $destination_path -ChildPath "LibraryPackage") -Force;Write-Host ("Pacchetto della libreria preparato.");
PrepareLS.cmd
rem Script per preparare il pacchetto Licensing Service e avviare LicensingService.exeecho Preparazione di LS >> ".\PrepareLS.log" 2>&1PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLS.ps1 >> ".\PrepareLS.log" 2>&1echo LS è stato preparato. >> ".\PrepareLS.log" 2>&1exit /B %errorlevel%
PrepareLS.ps1
Write-Host "PrepareLS.ps1 avviato...";# contenitore con i pacchetti di ABBYY FineReader Engine e Licensing Service$container_name = 'fre-lib';# stringa di connessione dell'account di archiviazione di Azure$connection_string = "<connection_string_to_frestorage>";Write-Host "Configurazione";Write-Host ("Nome del contenitore: " + $container_name);Write-Host ("Stringa di connessione: " + $storage_account);# connessione all'oggetto di archiviazioneWrite-Host ("Connessione all'account di archiviazione di Azure in corso...");$storage_account = New-AzStorageContext -ConnectionString $connection_string;# recupero dei blob (in pratica, file)Write-Host ("Recupero dei blob dal contenitore in corso...");$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;# percorso dell'archiviazione locale (vedere ServiceDefinition.csdef)$local_storage_name = "LocalStorage1";[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages'; # download del pacchetto Licensing ServiceWrite-Host ("Download del pacchetto Licensing Service dal contenitore in corso...");foreach( $blob in $fre_blobs ) { Write-Host $blob.Name; if( $blob.Name -eq "LSPackage.zip" ) { Write-Host ("Download del blob: " + $blob.Name + " in " + $destination_path); Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name ` -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account; } else { Write-Host ("Download del blob " + $blob.Name + " ignorato."); }}# decompressione del pacchetto scaricatoWrite-Host ("Decompressione del pacchetto della libreria in corso...");Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LSPackage.zip") ` -DestinationPath (Join-Path -Path $destination_path -ChildPath "LSPackage") -Force;# creazione delle cartelle per le licenze$program_data_path = [System.Environment]::ExpandEnvironmentVariables("%programdata%");$path = Join-Path -Path $program_data_path -ChildPath "ABBYY\SDK\12\Licenses";New-Item -ItemType Directory -Path $path -Force;# avvio di Licensing Service in modalità standaloneWrite-Host ("Avvio di Licensing Service in corso...");$licensing_service_app = Join-Path -Path $destination_path -ChildPath "LSPackage\LicensingService.exe";Start-Process -FilePath $licesing_service_app -ArgumentList "/standalone";Write-Host ("Il pacchetto LS è stato preparato e LS è stato avviato.");
Implementazione dell'interfaccia IFileWriter
namespace WorkerRole1.Engine{ 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() { // Crea una connessione a un nuovo blob in <OutputContainerName> // Il risultato dell'elaborazione verrà archiviato qui BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, Config.OutputContainerName, resultBlobName + fileExtension); // Sovrascrive il file esistente resultBlobClient.DeleteIfExists(); // Imposta la posizione su 0 per scrivere il file dall'inizio stream.Position = 0; resultBlobClient.Upload(stream); stream.Close(); } public void Dispose() { // Chiude lo stream di memoria durante il rilascio per renderlo accessibile dopo la scrittura dei dati stream.Close(); } private string resultBlobName; private string fileExtension; private MemoryStream stream; }}
Esempio di elaborazione dei file tramite ABBYY FineReader Engine
namespace WorkerRole1{ public class WorkerRole : RoleEntryPoint { private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); Processor processor = new Processor(); public override void Run() { Trace.TraceInformation("WorkerRole1 è in esecuzione"); try { this.RunAsync(this.cancellationTokenSource.Token).Wait(); } finally { this.runCompleteEvent.Set(); } } public override bool OnStart() { // Imposta il numero massimo di connessioni simultanee ServicePointManager.DefaultConnectionLimit = 12; // Imposta TLS 1.2 per potersi connettere all'account di archiviazione di Azure System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; // Per informazioni sulla gestione delle modifiche di configurazione // vedere l'argomento MSDN all'indirizzo https://go.microsoft.com/fwlink/?LinkId=166357 bool result = base.OnStart(); Trace.TraceInformation("WorkerRole1 è stato avviato"); processor.LoadEngine(); return result; } public override void OnStop() { processor.UnloadEngine(); Trace.TraceInformation("WorkerRole1 è in arresto"); this.cancellationTokenSource.Cancel(); this.runCompleteEvent.WaitOne(); base.OnStop(); Trace.TraceInformation("WorkerRole1 è stato arrestato"); } private async Task RunAsync(CancellationToken cancellationToken) { // TODO: sostituisci quanto segue con la tua logica while (!cancellationToken.IsCancellationRequested) { Trace.TraceInformation("Elaborazione in corso"); try { // Crea il contenitore e restituisce un oggetto client del contenitore BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.InputContainerName); foreach (BlobItem blobItem in inputContainerClient.GetBlobs()) { Trace.TraceInformation("\t" + blobItem.Name); BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.InputContainerName, blobItem.Name); Trace.TraceInformation("\t Download del blob in memoria: " + blobItem.Name); MemoryStream memoryStream = new MemoryStream(); blobClient.DownloadTo(memoryStream); Trace.TraceInformation("\t Elaborazione in FRE: " + blobItem.Name); string resultBlobName = processor.ProcessImage(memoryStream, blobItem.Name); Trace.TraceInformation("\t Nome del blob risultante nel contenitore di output: " + resultBlobName); Trace.TraceInformation("\t Eliminazione dal contenitore di input: " + blobItem.Name); blobClient.Delete(); } } catch (Exception error) { Trace.TraceInformation("errore: " + error.Message); } await Task.Delay(1000); } } }}
Processor.cs
namespace WorkerRole1{ class Processor : IDisposable { EngineLoader engineLoader = null; private void displayMessage(string text) { File.AppendAllText(".\\FRE.log", text + "\n"); Trace.TraceInformation("\t" + text ); } private void loadProfile() { engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy"); } private void setupFREngine() { displayMessage("Caricamento del profilo predefinito..."); loadProfile(); } public void LoadEngine() { try { if (engineLoader == null) { // Lo stesso EngineLoader dell'esempio Hello engineLoader = new EngineLoader(); } setupFREngine(); } catch (Exception error) { displayMessage("errore: " + error.Message); } } public void UnloadEngine() { try { if (engineLoader != null) { engineLoader.Dispose(); engineLoader = null; } } catch (Exception error) { displayMessage("errore: " + error.Message); } } public string ProcessImage( MemoryStream inputMemoryStream, string inputBlobName ) { FRDocument document = engineLoader.Engine.CreateFRDocument(); string resultBlobName = ""; try { document.PageFlushingPolicy = FREngine.PageFlushingPolicyEnum.PFP_KeepInMemory; // Aggiunge il file immagine al documento displayMessage("Caricamento dell'immagine..."); IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length); Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length); document.AddImageFileFromMemory(handle.ToInt64(), null, null); // Riconosce il documento displayMessage("Riconoscimento in corso..."); document.Process(null); // Salva i risultati displayMessage("Salvataggio dei risultati..."); // Salva i risultati in PDF usando lo scenario 'balanced' // FREngine.PDFExportParams pdfParams = engineLoader.Engine.CreatePDFExportParams(); // pdfParams.Scenario = FREngine.PDFExportScenarioEnum.PES_Balanced; WorkerRole1.Engine.FileWriter fileWriter = new WorkerRole1.Engine.FileWriter(inputBlobName, ".pdf"); resultBlobName = inputBlobName + ".pdf"; document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null); } catch (Exception error) { displayMessage("errore: " + error.Message); } finally { // Chiude il documento document.Close(); } return resultBlobName; } public void Dispose() { UnloadEngine(); } }}
Config.cs
class Config{ // Archiviazione locale come definita in ServiceDefinition.csdef private static string LocalStorageName = "LocalStorage1"; // Stringa di connessione al contenitore BLOB public static readonly string ConnectionString = "<connection_string_to_frestorage>"; // Nomi dei contenitori di input e output nello spazio di archiviazione public static readonly string InputContainerName = "fre-input"; public static readonly string OutputContainerName = "fre-output"; // Restituisce il Customer Project ID per ABBYY FineReader Engine public static String GetCustomerProjectId() { return "<Your_Customer_Project_ID>"; } // Restituisce il nome del token di licenza public static String GetLicenseTokenName() { return "<Token_number>.ABBYY.ActivationToken"; } // Restituisce la password della licenza public static String GetLicensePassword() { return "<Your_Online_License_token_password>"; } // Restituisce il percorso del motore public static String GetEngineFolder() { string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64"; string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder); return engineDllFolder; }}