Passer au contenu principal
Cette section fournit des instructions pour déployer une application FRE 12 pour Windows dans Azure App Service. À titre d’exemple, deux projets WebJob utilisant des données d’un compte de stockage Azure sont présentés. Le traitement des fichiers est effectué à l’aide d’un conteneur Blob.
Avec ce scénario, vous obtiendrez les meilleurs résultats de reconnaissance pour de petits documents d’une page, tels que des factures, des reçus, etc.
Le déploiement de votre application dans App Service comprend plusieurs étapes :
  1. Préparer votre machine locale et votre instance d’application à l’aide des prérequis
  2. Effectuer les étapes préparatoires avant de déployer votre application
  3. Déployer et exécuter votre application dans App Service
Utilisez les exemples de code fournis dans les instructions ci-dessous.

Prérequis

Ordinateur local

Avant de créer votre App Service, préparez votre ordinateur local avec la configuration suivante :
  • Visual Studio 2019 et ses modules de développement d’applications pour Azure (consultez Fonctionnalités Azure dans Visual Studio ou utilisez Visual Studio Installer pour télécharger ces modules)
  • SDK Azure (téléchargez-le ici)
  • .NET Framework 4.7.2
  • package NuGet pour utiliser Azure Storage et les conteneurs Blob :
    • Azure.Storage.Blobs (téléchargez-le ici)
    • Azure.Storage.Queues (téléchargez-le ici)
    • Newtonsoft.Json (téléchargez-le ici)
    • System.IO.Compression.ZipFile (téléchargez-le ici)
  • wrapper ABBYY FineReader Engine pour .NET Framework 4.7 (dans le dossier C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops après l’installation développeur)
  • interface IFileWriter redéfinie pour fonctionner avec les conteneurs Blob (voir l’exemple ci-dessous)
  • Azure Storage Explorer (facultatif : téléchargez-le ici)
  • Machine virtuelle dans votre compte Azure destinée aux procédures de licence. Pour la configuration ultérieure, vous aurez besoin de :
    • Adresse IP
    • Port de connexion ouvert (par défaut ou défini par l’utilisateur). Pour l’ouvrir, configurez le pare-feu
    • Paramètres de connexion du protocole réseau Sockets

Étapes préparatoires

Les étapes préparatoires doivent être effectuées sur votre machine locale. En les réalisant, vous préparerez tous les paramètres et fichiers nécessaires pour commencer le déploiement de votre application :
  1. Créez une archive contenant ABBYY FineReader Engine Library (par exemple, LibraryPackage.zip). La liste des fichiers figure dans le fichier FREngineDistribution.csv.
    Important ! Si vous disposez d’un espace de stockage limité (par exemple, si vous utilisez un App Service Plan avec 1 Go d’espace), nous vous recommandons d’utiliser l’option /extract pour créer un package ABBYY FineReader Engine personnalisé de taille minimale. Le reste de l’espace de stockage sera utilisé pour le traitement des fichiers.
    Lors de la création de l’archive, gardez à l’esprit que les paramètres de licence d’ABBYY FineReader Engine doivent être configurés en fonction des paramètres de la machine virtuelle :
    • Le fichier LicensingSettings.xml doit être configuré pour la configuration Network (voir Utilisation du fichier LicensingSettings.xml).
    • Le protocole réseau Sockets doit être utilisé.
    • Le fichier de jeton de licence en ligne doit se trouver dans le dossier Bin64.
  2. Créez un compte de stockage Azure (frestorage dans cet article). Vous trouverez toutes les instructions nécessaires sur le site web Azure.
  3. Créez votre App Service selon vos besoins (voir les instructions ici).
  4. Créez deux conteneurs Blob dans frestorage :
    • fre-lib - pour les fichiers ABBYY FineReader Engine
    • processing-container - pour les résultats du traitement
  5. Téléversez LibraryPackage.zip dans le conteneur fre-lib de la manière la plus pratique pour vous (à l’aide de .NET, PowerShell, d’un script Python ou des applications Azure Storage Explorer/Azure Portal).
  6. Déployez et configurez la machine virtuelle avec les paramètres de licence dans votre compte Azure :
    • Installez l’utilitaire License Manager à l’aide de installLM.exe depuis LibraryPackage.zip.
    • Configurez le protocole réseau Sockets dans le fichier LicensingSettings.xml, puis redémarrez le service de licence.
    • Assurez-vous qu’Azure App Service peut accéder au port de connexion du service de licence (ajustez les règles du Pare-feu Windows sur la machine virtuelle).
    • Activez votre licence (pour la protection logicielle uniquement ; la protection en ligne ne nécessite pas d’activation).
  7. Créez deux files d’attente dans frestorage :
    • processing-queue - pour définir les tâches de traitement des fichiers
    • status-queue - pour signaler l’achèvement des tâches
  8. Créez deux projets Azure WebJob (.NET Framework) dans Visual Studio 2019 pour travailler avec frestorage :
    • FreDeployerJob - pour déployer LibraryPackage.zip vers App Service (voir la liste de ses fichiers : Config.cs, Functions.cs, Program.cs ci-dessous)
    • FreProcessorJob - pour le traitement des documents (voir la liste de ses fichiers : Config.cs, Functions.cs, Program.cs, EngineLoader.cs, IFileWriter.cs, Processor.cs ci-dessous)

Déploiement et exécution d’ABBYY FineReader Engine dans App Service

Pour déployer ABBYY FineReader Engine :
  1. Publiez FreDeployerJob sur Azure App Service à l’aide de Visual Studio (définissez Triggered comme WebJob Type).
  2. Ouvrez votre App Service dans le portail Azure.
  3. Ouvrez la section WebJobs de votre App Service.
  4. Recherchez FreDeployerJob dans la liste des WebJobs.
  5. Lancez FreDeployerJob par un clic droit, puis la commande Run dans l’onglet WebJobs.
Vous pouvez accéder à l’onglet Logs pour vérifier le résultat du déploiement. Si celui-ci réussit, LibraryPackage.zip est téléversé depuis le conteneur fre-lib et déployé dans le dossier %HOME_EXPANDED% accessible à toutes les entités dans App Service. Pour déployer FreProcessorJob, publiez FreProcessorJob sur Azure App Service à l’aide de Visual Studio (définissez Continuous comme WebJob Type). FreProcessorJob apparaîtra alors dans la liste des onglets WebJobs de votre App Service. Pour traiter un fichier :
  1. Téléversez le fichier que vous souhaitez traiter dans le processing-container.
  2. Ajoutez un message JSON pour une nouvelle tâche de traitement au format {“blob-item-name” : “file_name”} dans le processing-queue. Si vous téléversez Demo.tif dans le processing-container, votre message doit être :
{"blob-item-name" : "Demo.tif"}
  1. Attendez que la tâche soit terminée. Dès que la nouvelle tâche est définie, FreProcessorJob commence à traiter en mémoire le fichier spécifié. La status-queue contiendra des entrées concernant l’exécution de cette tâche.
  2. Recherchez le fichier de sortie dans le processing-container.
  1. FreProcessorJob fonctionne comme un process mono-thread. Si vous souhaitez traiter vos fichiers en parallèle, vous devez créer plusieurs FreProcessorJob qui écouteront la même file d’attente. 2. Chaque FreProcessorJob supplémentaire consomme davantage de mémoire. Tenez-en compte lors de l’achat de votre Service Plan. Par exemple, avec Azure Free Service Plan, il est préférable de n’avoir qu’un seul FreProcessorJob, qui consomme peu de mémoire et garantit ainsi la stabilité du traitement des fichiers. 3. L’utilisation d’un seul FreProcessorJob ne convient pas au traitement de documents volumineux comportant plusieurs pages. Dans ce cas, envisagez plutôt la reconnaissance de votre document dans Azure Cloud Service ou Azure Virtual Machine que dans App Service.

Exemples de code

Cette section présente des exemples de code utilisés pour déployer et mettre en œuvre l’API ABBYY FineReader Engine dans App Service.

FreDeployerJob:

using System.IO;
class Config
{
    // Chaîne de connexion au conteneur Blob
    public static readonly string ConnectionString = "your_connection_string";
    // Le répertoire HOME_EXPANDED est commun à tous les dossiers WebJobs
    public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
    // Nom des conteneurs d’entrée et de sortie dans votre compte de stockage
    public static readonly string LibraryContainerName = "fre-lib";
namespace FreDeployerJob
{
    public class Functions
    {
        // Cette fonction ne sera pas déclenchée automatiquement ; vous devez la lancer manuellement
        [NoAutomaticTrigger]
        [Timeout("01:00:00")]
        public static void DeployFRE()
        {
            Console.WriteLine("Déploiement de FRE");
            // Connexion au conteneur d’entrée existant <InputContainerName> à l’aide de la chaîne de connexion du compte de stockage
            BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.LibraryContainerName);
            // Création du répertoire de bibliothèque ainsi que des dossiers AppData et Temp pour l’initialisation d’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"));
            // Parcours des blobs dans le conteneur. Le blob dans <InputContainerName> correspond à un fichier image
            foreach (BlobItem blobItem in inputContainerClient.GetBlobs())
            {
                Console.WriteLine("\t" + blobItem.Name);
                // Recherche de la version allégée de la bibliothèque ABBYY FineReader Engine
                if (blobItem.Name == "LibraryPackage.zip")
                {
                    Console.WriteLine("LibraryPackage.zip a été trouvé.");
                    // Connexion au blob pour accéder à son contenu
                    BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.LibraryContainerName, blobItem.Name);
                    Console.WriteLine("Téléchargement en mémoire...");
                    // Télécharger le fichier zip en mémoire
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        blobClient.DownloadTo(memoryStream);
                        Console.WriteLine("LibraryPackage.zip a été téléchargé.");
                        Console.WriteLine("Décompression...");
                        // Décompression sans utiliser de dossier temporaire (traitement uniquement en mémoire)
                        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 a été décompressé dans HOME_EXPANDED.");
                    }
                }
            }
        }
    }
}
namespace FreDeployerJob
{
    // Pour en savoir plus sur le SDK Microsoft Azure WebJobs, consultez https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Veuillez définir les chaînes de connexion suivantes dans app.config pour que ces WebJobs puissent s’exécuter :
        // AzureWebJobsDashboard et AzureWebJobsStorage
        static void Main()
        {
            // Une fois déclenché, ce WebJob appellera uniquement cette méthode
            Functions.DeployFRE();
        }
    }
}

FreProcessorJob:

using System;
using System.IO;
namespace FreProcessorJob
{
    class Config
    {
        // Chaîne de connexion au conteneur Blob
        public static readonly string ConnectionString = "your_connection_string";
        // Le répertoire HOME_EXPANDED est commun à tous les dossiers WebJobs
        // Il est identique à celui du projet FreDeployerJob
        public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
        // Nom du conteneur de traitement dans votre stockage
        public static readonly string ProcessingContainerName = "processing-container";
        // Nom de la file d’attente de traitement
        public static readonly string ProcessingQueueName = "processing-queue";
        public static readonly string StatusQueueName = "status-queue";
        // Retourne le Customer Project ID pour ABBYY FineReader Engine
        public static String GetCustomerProjectId()
        {
            return "your_cpid";
        }
 
        // Retourne le nom du jeton de licence en ligne
        // Si vous n’utilisez pas de licence en ligne, laissez une chaîne vide
        // Le jeton doit se trouver dans le dossier Bin64 du package de bibliothèque
        public static String GetLicenseTokenName()
        {
            return "your_online_license_token_if_you_have_it";
        }
 
        // Retourne le mot de passe de la licence en ligne
        // Si vous n’utilisez pas de licence en ligne, laissez une chaîne vide
        public static String GetLicensePassword()
        {
            return "online_license_password_if_you have_it";
        }
 
        // Retourne le nom du jeton de licence
        // La licence se trouve dans le dossier Bin64 du package de bibliothèque
        public static String GetLicenseTokenName()
        {
            return "your_licence_for_ABBYY FineReader Engine";
        }
 
        // Retourne le mot de passe de la licence
        public static String GetLicensePassword()
        {
            return "license_password";
        }
 
        // Retourne le chemin d’accès de l’Engine
        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
    {
        // Cette fonction est déclenchée/exécutée lorsqu'un nouveau message est écrit
        // dans une file d’attente Azure nommée processing-queue
        // Le message attendu est un message JSON avec la clé 'blob-item-name'
        // les résultats du traitement seront enregistrés dans le conteneur de traitement
        // l'état du traitement sera envoyé à status-queue au format JSON
        public static void ProcessQueueMessage([QueueTrigger("processing-queue")] string message)
        {
            // Tout d'abord, connexion à status-queue
            QueueClient queueClient = new QueueClient(Config.ConnectionString, Config.StatusQueueName);
            try
            {
                // Ceci sera consigné dans les journaux WebJob du portail Azure
                Console.WriteLine("Accepted task: " + message);
                JObject task = JObject.Parse(message);
                task["processor_id"] = Environment.GetEnvironmentVariable("WEBJOBS_NAME");
 
                // Ceci sera envoyé à status-queue
                task["status"] = "accepted";
                queueClient.SendMessage(task.ToString());
 
                // Récupération de blob-item-name : le nom du fichier dans le conteneur de traitement
                string blobFileName = task["blob-item-name"].ToString();
                BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.ProcessingContainerName, blobFileName);
                // Chargement du blob en mémoire
                Console.WriteLine("\t Downloading blob to memory: " + blobFileName);
                MemoryStream memoryStream = new MemoryStream();
                blobClient.DownloadTo(memoryStream);
                Console.WriteLine("\t Downloaded.");
                // Mise à jour de l'état à processing
                Console.WriteLine("\t Processing in FRE: " + blobFileName);
                task["status"] = "processing";
                queueClient.SendMessage(task.ToString());
                // Traitement du blob téléchargé dans ABBYY FineReader Engine à l'aide des méthodes de traitement en mémoire
                // La sortie correspond au nom du résultat du traitement enregistré en tant que blob dans <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);
                }
                // Suppression de l'image d'entrée
                Console.WriteLine("\t Deleting from input container: " + blobFileName);
                blobClient.Delete();
                // Mise à jour de l'état à succeeded
                // dans les journaux du portail Azure
                Console.WriteLine("Succeeded");
 
                // dans status-queue
                task["status"] = "succeeded";
                task["result-blob-name"] = resultBlobName;
                queueClient.SendMessage(task.ToString());
            }
            catch (Exception error)
            {
                // En cas d'erreur, signalement
                // dans les journaux du portail Azure
                Console.WriteLine("Failed: " + error.Message);
                // dans 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
{
    // Pour en savoir plus sur le SDK Microsoft Azure WebJobs, consultez https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Veuillez définir les chaînes de connexion suivantes dans app.config pour que ces WebJobs puissent s'exécuter :
        // AzureWebJobsDashboard and AzureWebJobsStorage
        static void Main()
        {
            var config = new JobHostConfiguration();
            if (config.IsDevelopment)
            {
                config.UseDevelopmentSettings();
            }
            // ABBYY FineReader Engine n'est pas thread-safe ; nous ne pouvons donc pas traiter plus d'un message à la fois
            config.Queues.BatchSize = 1;
            var host = new JobHost(config);
            // Le code suivant garantit que le WebJob s'exécutera en continu
            // car l'une des fonctions est liée à une file d’attente Azure et attend les nouvelles tâches
            host.RunAndBlock();
        }
    }
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using FREngine;
namespace FreProcessorJob.FreProcessor
{
    // Classe pour le chargement/déchargement de FREngine.dll et l'initialisation/désinitialisation du Engine
    // Le chargement est effectué dans le constructeur, le déchargement dans Dispose()
    // Lève des exceptions en cas d'échec du chargement
    public class EngineLoader : IDisposable
    {
        // Charger ABBYY FineReader Engine avec les paramètres stockés dans 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
            {
                // Charger la bibliothèque FREngine.dll
                dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
                if (dllHandle == IntPtr.Zero)
                {
                    int error = Marshal.GetLastWin32Error();
                    Console.WriteLine("La dernière erreur Win32 était : " + error);
                    throw new Exception("Impossible de charger " + enginePath);
                }
 
                IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
                if (initializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Impossible de trouver la fonction InitializeEngine");
                }
                IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
                if (deinitializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Impossible de trouver la fonction DeinitializeEngine");
                }
                IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
                if (dllCanUnloadNowPtr == IntPtr.Zero)
                {
                    throw new Exception("Impossible de trouver la fonction DllCanUnloadNow");
                }
                // Convertir les pointeurs en délégués
                initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    initializeEnginePtr, typeof(InitializeEngine));
                deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    deinitializeEnginePtr, typeof(DeinitializeEngine));
                dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                    dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
                // Appeler la fonction 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)
            {
                // Libérer la bibliothèque FREngine.dll
                engine = null;
                // Suppression de tous les objets avant l'appel à FreeLibrary
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                FreeLibrary(dllHandle);
                dllHandle = IntPtr.Zero;
                initializeEngine = null;
                deinitializeEngine = null;
                dllCanUnloadNow = null;
                throw;
            }
        }
        // Décharger ABBYY FineReader Engine
        public void Dispose()
        {
            if (engine == null)
            {
                // Le Engine n'a pas été chargé
                return;
            }
            engine = null;
            int hresult = deinitializeEngine();
            // Suppression de tous les objets avant l'appel à FreeLibrary
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            hresult = dllCanUnloadNow();
            if (hresult == 0)
            {
                FreeLibrary(dllHandle);
            }
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            // Levée d'exception après le nettoyage
            Marshal.ThrowExceptionForHR(hresult);
        }
        // Retourne un pointeur vers l'objet principal de ABBYY FineReader Engine
        public IEngine Engine
        {
            get
            {
                return engine;
            }
        }
        // Fonctions de 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);
        // Fonctions de 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();
        // Variables privées
        private FREngine.IEngine engine = null;
        // Handle vers 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()
        {
            // Création d'une connexion à un nouveau blob dans <ProcessingContainerName>. Le résultat du traitement y sera stocké
            BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, 
                Config.ProcessingContainerName,
                resultBlobName + fileExtension);
            // Écraser le fichier existant
            resultBlobClient.DeleteIfExists();
            // Définir la position sur 0 pour écrire le fichier depuis le début
            stream.Position = 0;
            resultBlobClient.Upload(stream);
            stream.Close();
        }
        public void Dispose()
        {
            // Fermer le flux mémoire lors de la libération afin de pouvoir y accéder une fois les données écrites
            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("Chargement du profil prédéfini...");
            // ceci est facultatif
            engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
            // ceci est obligatoire sur les App Service Plan peu performants, car des erreurs se produiront lors du traitement parallèle
            engineLoader.Engine.MultiProcessingParams.MultiProcessingMode = MultiProcessingModeEnum.MPM_Sequential;
        }
        private void LoadEngine()
        {
            try
            {
                if (engineLoader == null)
                {
                    engineLoader = new EngineLoader();
                }
                setupFREngine();
            }
            catch (Exception error)
            {
                displayMessage("erreur : " + error.Message);
            }
        }
        private void UnloadEngine()
        {
            try
            {
                if (engineLoader != null)
                {
                    engineLoader.Dispose();
                    engineLoader = null;
                }
            }
            catch (Exception error)
            {
                displayMessage("erreur : " + 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;
 
                // Ajouter le fichier image au document
                displayMessage("Chargement de l'image...");
                IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                document.AddImageFileFromMemory(handle.ToInt64(), null, null);
                // Reconnaître le document
                displayMessage("Reconnaissance...");
                document.Process(null);
                // Enregistrer les résultats
                displayMessage("Enregistrement des résultats...");
                FileWriter fileWriter = new FileWriter(inputBlobName, ".pdf");
                resultBlobName = inputBlobName + ".pdf";
                document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
            }
            catch (Exception error)
            {
                displayMessage("erreur : " + error.Message);
                throw error;
            }
            finally
            {
                // Fermer le document
                document.Close();
            }
            return resultBlobName;
        }
        public void Dispose()
        {
            UnloadEngine();
        }
    }
}