Zum Hauptinhalt springen
In diesem Abschnitt wird beschrieben, wie Sie eine FRE 12 for Windows-Anwendung in Azure App Service bereitstellen. Als Beispiel dient ein Paar von WebJob-Projekten, die Daten aus einem Azure Storage-Konto verwenden. Die Dateiverarbeitung erfolgt mithilfe eines Blob-Containers.
Mit diesem Szenario erzielen Sie die besten Erkennungsergebnisse für kleine einseitige Dokumente wie Rechnungen, Kassenbelege usw.
Das Bereitstellen Ihrer Anwendung in App Service umfasst mehrere Schritte:
  1. Einrichten Ihres lokalen Computers und Ihrer App-Instanz anhand der Voraussetzungen
  2. Durchführen der vorbereitenden Schritte vor dem Bereitstellen Ihrer Anwendung
  3. Bereitstellen und Ausführen Ihrer Anwendung in App Service
Verwenden Sie die Codebeispiele in der folgenden Anleitung.

Voraussetzungen

Lokaler Rechner

Bevor Sie Ihren App Service erstellen, verwenden Sie die folgende Spezifikation, um Ihren lokalen Rechner entsprechend einzurichten:
  • Visual Studio 2019 und die Module für die Entwicklung von Anwendungen für Azure (siehe Azure feature in Visual Studio oder verwenden Sie den Visual Studio Installer, um diese Module herunterzuladen)
  • Azure SDK (hier herunterladen)
  • .NET Framework 4.7.2
  • NuGet-Pakete für die Arbeit mit Azure Storage und Blob-Containern:
    • Azure.Storage.Blobs (hier herunterladen)
    • Azure.Storage.Queues (hier herunterladen)
    • Newtonsoft.Json (hier herunterladen)
    • System.IO.Compression.ZipFile (hier herunterladen)
  • ABBYY FineReader Engine-Wrapper für .NET Framework 4.7 (im Ordner C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops nach der Installation der Entwicklerversion)
  • Überschriebene IFileWriter-Schnittstelle für die Arbeit mit Blob-Containern (siehe das Beispiel unten)
  • Azure Storage Explorer (optional – hier herunterladen)
  • Virtuelle Maschine in Ihrem Azure-Konto für die Lizenzierung. Für die weitere Konfiguration benötigen Sie:
    • IP-Adresse
    • Offenen Verbindungsport (Standard oder benutzerdefiniert). Zum Öffnen verwenden Sie eine Firewall
    • Verbindungsdetails für das Sockets-Netzwerkprotokoll

Vorbereitende Schritte

Die vorbereitenden Schritte sind auf Ihrem lokalen Rechner durchzuführen. Nach Abschluss dieser Schritte haben Sie alle erforderlichen Einstellungen und Dateien vorbereitet, um die Bereitstellung Ihrer Anwendung zu starten:
  1. Erstellen Sie ein Archiv mit der ABBYY FineReader Engine-Bibliothek (z. B. LibraryPackage.zip). Die Liste der Dateien ist in der Datei FREngineDistribution.csv aufgeführt.
    Wichtig! Wenn Sie über begrenzten Speicherplatz verfügen (z. B. bei Verwendung eines App Service Plans mit 1 GB Speicherplatz), empfehlen wir die Verwendung der Option /extract, um ein benutzerdefiniertes ABBYY FineReader Engine-Paket mit minimaler Größe zu erstellen. Der verbleibende Speicherplatz wird für die Verarbeitung der Dateien genutzt.
    Beachten Sie beim Erstellen des Archivs, dass die ABBYY FineReader Engine-Lizenzierungseinstellungen entsprechend den Einstellungen der virtuellen Maschine konfiguriert sein müssen:
    • Die Datei LicensingSettings.xml muss für die Netzwerkkonfiguration eingerichtet sein (siehe Working with the LicensingSettings.xml File).
    • Das Netzwerkprotokoll Sockets muss verwendet werden.
    • Die Online License-Token-Datei muss sich im Ordner Bin64 befinden.
  2. Erstellen Sie ein Azure Storage-Konto (in diesem Artikel als frestorage bezeichnet). Alle erforderlichen Anweisungen finden Sie auf der Azure-Website.
  3. Erstellen Sie Ihren App Service nach Bedarf (Anweisungen finden Sie hier).
  4. Erstellen Sie zwei Blob-Container in frestorage:
    • fre-lib – für die ABBYY FineReader Engine-Dateien
    • processing-container – für Verarbeitungsergebnisse
  5. Laden Sie LibraryPackage.zip auf dem für Sie bequemsten Weg in den Container fre-lib hoch (mithilfe von .NET, PowerShell, Python-Skript oder den Anwendungen Azure Storage Explorer/Azure Portal).
  6. Stellen Sie die virtuelle Maschine mit den Lizenzierungseinstellungen in Ihrem Azure-Konto bereit und konfigurieren Sie sie:
    • Installieren Sie das License Manager Utility über installLM.exe aus der LibraryPackage.zip.
    • Richten Sie das Netzwerkprotokoll Sockets in der LicensingSettings.xml ein und starten Sie anschließend den Licensing Service neu.
    • Stellen Sie sicher, dass Azure App Service auf den Verbindungsport des Licensing Service zugreifen kann (passen Sie dazu die Windows-Firewallregeln auf der virtuellen Maschine an).
    • Aktivieren Sie Ihre Lizenz (nur bei Software-Schutz erforderlich; Online-Schutz erfordert keine Aktivierung).
  7. Erstellen Sie zwei Queues in frestorage:
    • processing-queue – zum Festlegen der Dateiverarbeitungsaufgaben
    • status-queue – zur Benachrichtigung über den Abschluss von Aufgaben
  8. Erstellen Sie zwei Azure WebJob-Projekte (.NET Framework) in Visual Studio 2019 für die Arbeit mit frestorage:
    • FreDeployerJob – zum Bereitstellen von LibraryPackage.zip in App Service (siehe Auflistung der zugehörigen Dateien: Config.cs, Functions.cs, Program.cs unten)
    • FreProcessorJob – für die Dokumentverarbeitung (siehe Auflistung der zugehörigen Dateien: Config.cs, Functions.cs, Program.cs, EngineLoader.cs, IFileWriter.cs, Processor.cs unten)

Bereitstellen und Ausführen von ABBYY FineReader Engine in App Service

So stellen Sie ABBYY FineReader Engine bereit:
  1. Veröffentlichen Sie FreDeployerJob mit Visual Studio in Azure App Service (legen Sie als WebJob Type Triggered fest).
  2. Öffnen Sie Ihren App Service im Azure-Portal.
  3. Öffnen Sie die WebJobs Ihres App Service.
  4. Suchen Sie FreDeployerJob in der Liste der WebJobs.
  5. Starten Sie FreDeployerJob auf der Registerkarte WebJobs mit dem Befehl „Run“ im Kontextmenü (Rechtsklick).
Sie können die Registerkarte Logs öffnen, um das Ergebnis der Bereitstellung zu überprüfen. Wenn die Bereitstellung erfolgreich ist, wird LibraryPackage.zip aus dem Container fre-lib hochgeladen und im Ordner %HOME_EXPANDED% bereitgestellt, der in App Service für alle Komponenten verfügbar ist. Um FreProcessorJob bereitzustellen, veröffentlichen Sie FreProcessorJob mit Visual Studio in Azure App Service (legen Sie als WebJob Type Continuous fest). Danach wird FreProcessorJob in der WebJobs-Liste Ihres App Service angezeigt. So verarbeiten Sie eine Datei:
  1. Laden Sie die Datei, die Sie verarbeiten möchten, in den processing-container hoch.
  2. Fügen Sie der processing-queue eine JSON-Nachricht für eine neue Verarbeitungsaufgabe im Format {“blob-item-name” : “file_name”} hinzu. Wenn Sie Demo.tif in den processing-container hochladen, sollte Ihre Nachricht wie folgt lauten:
{"blob-item-name" : "Demo.tif"}
  1. Warten Sie, bis die Aufgabe abgeschlossen ist. Sobald die neue Aufgabe eingerichtet ist, beginnt FreProcessorJob, die angegebene Datei im Speicher zu verarbeiten. Die Statuswarteschlange enthält Einträge zur Ausführung dieser Aufgabe.
  2. Suchen Sie die Ausgabedatei im Verarbeitungscontainer.
  1. FreProcessorJob arbeitet als Single-Thread-Prozess. Wenn Sie Ihre Dateien parallel verarbeiten möchten, müssen Sie mehrere FreProcessorJob erstellen, die dieselbe Warteschlange überwachen. 2. Jeder zusätzliche FreProcessorJob benötigt zusätzlichen Speicher. Berücksichtigen Sie dies bei der Auswahl Ihres Service Plans. Im Azure Free Service Plan ist es beispielsweise sinnvoll, nur einen FreProcessorJob zu verwenden, der wenig Speicher benötigt und dadurch eine stabile Dateiverarbeitung gewährleistet. 3. Ein einzelner FreProcessorJob eignet sich nicht für die Verarbeitung großer mehrseitiger Dokumente. Ziehen Sie in diesem Fall stattdessen die Erkennung Ihres Dokuments in Azure Cloud Service oder auf einer Azure Virtual Machine in Betracht.

Codebeispiele

Dieser Abschnitt enthält Codebeispiele für die Bereitstellung und Implementierung der ABBYY FineReader Engine API in App Service.

FreDeployerJob:

using System.IO;
class Config
{
    // Verbindungszeichenfolge zum Blob-Container
    public static readonly string ConnectionString = "your_connection_string";
    // Das Verzeichnis HOME_EXPANDED ist für alle WebJobs-Ordner gemeinsam
    public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
    // Namen der Eingabe- und Ausgabe-Container in Ihrem Storage
    public static readonly string LibraryContainerName = "fre-lib";
namespace FreDeployerJob
{
    public class Functions
    {
        // Diese Funktion wird nicht automatisch ausgelöst – Sie müssen sie manuell starten
        [NoAutomaticTrigger]
        [Timeout("01:00:00")]
        public static void DeployFRE()
        {
            Console.WriteLine("Deploying FRE");
            // Verbindung mit dem vorhandenen Eingabe-Container <InputContainerName> über die Verbindungszeichenfolge des Storage-Kontos herstellen
            BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.LibraryContainerName);
            // Bibliotheksverzeichnis sowie AppData- und Temp-Ordner für die Initialisierung von ABBYY FineReader Engine erstellen
            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"));
            // Die Blobs im Container durchlaufen. Ein Blob in <InputContainerName> entspricht einer Bilddatei
            foreach (BlobItem blobItem in inputContainerClient.GetBlobs())
            {
                Console.WriteLine("\t" + blobItem.Name);
                // Nach der Light-Version der ABBYY FineReader Engine-Bibliothek suchen
                if (blobItem.Name == "LibraryPackage.zip")
                {
                    Console.WriteLine("LibraryPackage.zip was found.");
                    // Verbindung mit dem Blob herstellen, um auf dessen Inhalt zuzugreifen
                    BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.LibraryContainerName, blobItem.Name);
                    Console.WriteLine("Downloading to memory...");
                    // ZIP-Datei in den Speicher herunterladen
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        blobClient.DownloadTo(memoryStream);
                        Console.WriteLine("LibraryPackage.zip was downloaded.");
                        Console.WriteLine("Unzipping...");
                        // Ohne Verwendung eines temporären Ordners entpacken (nur Verarbeitung im Speicher)
                        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
{
    // Weitere Informationen zum Microsoft Azure WebJobs SDK finden Sie unter https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Bitte legen Sie die folgenden Verbindungszeichenfolgen in app.config fest, damit diese WebJobs ausgeführt werden können:
        // AzureWebJobsDashboard und AzureWebJobsStorage
        static void Main()
        {
            // Beim Auslösen ruft dieser WebJob nur diese Methode auf
            Functions.DeployFRE();
        }
    }
}

FreProcessorJob:

using System;
using System.IO;
namespace FreProcessorJob
{
    class Config
    {
        // Verbindungszeichenfolge für den Blob-Container
        public static readonly string ConnectionString = "your_connection_string";
        // Das Verzeichnis HOME_EXPANDED wird von allen WebJobs-Ordnern gemeinsam verwendet
        // Es ist dasselbe wie im Projekt FreDeployerJob
        public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
        // Name des Verarbeitungs-Containers in Ihrem Speicher
        public static readonly string ProcessingContainerName = "processing-container";
        // Name der Verarbeitungswarteschlange
        public static readonly string ProcessingQueueName = "processing-queue";
        public static readonly string StatusQueueName = "status-queue";
        // Gibt die Customer Project ID für ABBYY FineReader Engine zurück
        public static String GetCustomerProjectId()
        {
            return "your_cpid";
        }
 
        // Gibt den Namen des Online-Lizenz-Tokens zurück
        // Wenn Sie keine Online-Lizenz verwenden, lassen Sie den String leer
        // Das Token sollte sich im Ordner Bin64 des Bibliothekspakets befinden
        public static String GetLicenseTokenName()
        {
            return "your_online_license_token_if_you_have_it";
        }
 
        // Gibt das Kennwort der Online-Lizenz zurück
        // Wenn Sie keine Online-Lizenz verwenden, lassen Sie den String leer
        public static String GetLicensePassword()
        {
            return "online_license_password_if_you have_it";
        }
 
        // Gibt den Namen des Lizenz-Tokens zurück
        // Die Lizenzierung befindet sich im Ordner Bin64 des Bibliothekspakets
        public static String GetLicenseTokenName()
        {
            return "your_licence_for_ABBYY FineReader Engine";
        }
 
        // Gibt das Lizenzkennwort zurück
        public static String GetLicensePassword()
        {
            return "license_password";
        }
 
        // Gibt den Engine-Pfad zurück
        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
    {
        // Diese Funktion wird ausgelöst/ausgeführt, wenn eine neue Nachricht
        // in eine Azure Queue mit dem Namen processing-queue geschrieben wird
        // Die Nachricht muss eine JSON-Nachricht mit dem Schlüssel 'blob-item-name' sein
        // Verarbeitungsergebnisse werden im Container processing gespeichert
        // der Verarbeitungsstatus wird im JSON-Format an status-queue gesendet
        public static void ProcessQueueMessage([QueueTrigger("processing-queue")] string message)
        {
            // Zuerst Verbindung mit status-queue herstellen
            QueueClient queueClient = new QueueClient(Config.ConnectionString, Config.StatusQueueName);
            try
            {
                // Dies wird in den WebJob-Protokollen im Azure-Portal protokolliert
                Console.WriteLine("Accepted task: " + message);
                JObject task = JObject.Parse(message);
                task["processor_id"] = Environment.GetEnvironmentVariable("WEBJOBS_NAME");
 
                // Dies wird an status-queue gesendet
                task["status"] = "accepted";
                queueClient.SendMessage(task.ToString());
 
                // blob-item-name abrufen – den Namen der Datei im Processing-Container
                string blobFileName = task["blob-item-name"].ToString();
                BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.ProcessingContainerName, blobFileName);
                // Blob in den Arbeitsspeicher laden
                Console.WriteLine("\t Downloading blob to memory: " + blobFileName);
                MemoryStream memoryStream = new MemoryStream();
                blobClient.DownloadTo(memoryStream);
                Console.WriteLine("\t Downloaded.");
                // Status auf processing setzen
                Console.WriteLine("\t Processing in FRE: " + blobFileName);
                task["status"] = "processing";
                queueClient.SendMessage(task.ToString());
                // Den heruntergeladenen Blob in ABBYY FineReader Engine mit In-Memory-Verarbeitungsmethoden verarbeiten
                // Die Ausgabe ist der Name des Verarbeitungsergebnisses, das als Blob in <ProcessingContainerName> gespeichert wird
                string resultBlobName = "";
                using (FreProcessor.Processor freProcessor = new FreProcessor.Processor())
                {
                    resultBlobName = freProcessor.ProcessBlobFromMemory(memoryStream, blobFileName);
                    Console.WriteLine("\t Result blob name in output container: " + resultBlobName);
                }
                // Eingabebild löschen
                Console.WriteLine("\t Deleting from input container: " + blobFileName);
                blobClient.Delete();
                // Status auf succeeded setzen
                // in den Azure-Portal-Protokollen
                Console.WriteLine("Succeeded");
 
                // in status-queue
                task["status"] = "succeeded";
                task["result-blob-name"] = resultBlobName;
                queueClient.SendMessage(task.ToString());
            }
            catch (Exception error)
            {
                // Im Fehlerfall Meldung
                // an die Azure-Portal-Protokolle
                Console.WriteLine("Failed: " + error.Message);
                // an 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
{
    // Weitere Informationen zum Microsoft Azure WebJobs SDK finden Sie unter https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // Legen Sie die folgenden Verbindungszeichenfolgen in app.config fest, damit diese WebJobs ausgeführt werden können:
        // AzureWebJobsDashboard und AzureWebJobsStorage
        static void Main()
        {
            var config = new JobHostConfiguration();
            if (config.IsDevelopment)
            {
                config.UseDevelopmentSettings();
            }
            // ABBYY FineReader Engine ist nicht threadsicher, daher können wir nicht mehr als eine Nachricht gleichzeitig verarbeiten
            config.Queues.BatchSize = 1;
            var host = new JobHost(config);
            // Der folgende Code stellt sicher, dass der WebJob kontinuierlich ausgeführt wird,
            // da eine der Funktionen an die Azure Queue gebunden ist und auf neue Aufgaben wartet
            host.RunAndBlock();
        }
    }
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using FREngine;
namespace FreProcessorJob.FreProcessor
{
    // Klasse zum Laden/Entladen von FREngine.dll und zum Initialisieren/Deinitialisieren der Engine
    // Das Laden erfolgt im Konstruktor, das Entladen in Dispose()
    // Löst Ausnahmen aus, wenn das Laden fehlschlägt
    public class EngineLoader : IDisposable
    {
        // ABBYY FineReader Engine mit den in SamplesConfig.cs gespeicherten Einstellungen laden
        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
            {
                // FREngine.dll-Bibliothek laden
                dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
                if (dllHandle == IntPtr.Zero)
                {
                    int error = Marshal.GetLastWin32Error();
                    Console.WriteLine("Der letzte Win32-Fehler war: " + error);
                    throw new Exception("Kann nicht geladen werden: " + enginePath);
                }
 
                IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
                if (initializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Funktion InitializeEngine nicht gefunden");
                }
                IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
                if (deinitializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("Funktion DeinitializeEngine nicht gefunden");
                }
                IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
                if (dllCanUnloadNowPtr == IntPtr.Zero)
                {
                    throw new Exception("Funktion DllCanUnloadNow nicht gefunden");
                }
                // Zeiger in Delegates umwandeln
                initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    initializeEnginePtr, typeof(InitializeEngine));
                deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    deinitializeEnginePtr, typeof(DeinitializeEngine));
                dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                    dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
                // Funktion InitializeEngine aufrufen
                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)
            {
                // FREngine.dll-Bibliothek freigeben
                engine = null;
                // Alle Objekte vor dem FreeLibrary-Aufruf löschen
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                FreeLibrary(dllHandle);
                dllHandle = IntPtr.Zero;
                initializeEngine = null;
                deinitializeEngine = null;
                dllCanUnloadNow = null;
                throw;
            }
        }
        // ABBYY FineReader Engine entladen
        public void Dispose()
        {
            if (engine == null)
            {
                // Engine wurde nicht geladen
                return;
            }
            engine = null;
            int hresult = deinitializeEngine();
            // Alle Objekte vor dem FreeLibrary-Aufruf löschen
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            hresult = dllCanUnloadNow();
            if (hresult == 0)
            {
                FreeLibrary(dllHandle);
            }
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            // Ausnahme nach der Bereinigung auslösen
            Marshal.ThrowExceptionForHR(hresult);
        }
        // Gibt einen Zeiger auf das Hauptobjekt der ABBYY FineReader Engine zurück
        public IEngine Engine
        {
            get
            {
                return engine;
            }
        }
        // Kernel32.dll-Funktionen
        [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);
        // FREngine.dll-Funktionen
        [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();
        // Private Variablen
        private FREngine.IEngine engine = null;
        // Handle für 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()
        {
            // Verbindung mit einem neuen Blob in <ProcessingContainerName> herstellen. Dort wird das Verarbeitungsergebnis gespeichert
            BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, 
                Config.ProcessingContainerName,
                resultBlobName + fileExtension);
            // Vorhandene Datei überschreiben
            resultBlobClient.DeleteIfExists();
            // Position auf 0 setzen, um die Datei vom Anfang an zu schreiben
            stream.Position = 0;
            resultBlobClient.Upload(stream);
            stream.Close();
        }
        public void Dispose()
        {
            // Speicherstream bei der Freigabe schließen, damit nach dem Schreiben der Daten darauf zugegriffen werden kann
            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("Vordefiniertes Profil wird geladen...");
            // dies ist optional
            engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
            // dies ist bei leistungsschwachen App Service-Plänen erforderlich, da es sonst bei der parallelen Verarbeitung zu Fehlern kommt
            engineLoader.Engine.MultiProcessingParams.MultiProcessingMode = MultiProcessingModeEnum.MPM_Sequential;
        }
        private void LoadEngine()
        {
            try
            {
                if (engineLoader == null)
                {
                    engineLoader = new EngineLoader();
                }
                setupFREngine();
            }
            catch (Exception error)
            {
                displayMessage("Fehler: " + error.Message);
            }
        }
        private void UnloadEngine()
        {
            try
            {
                if (engineLoader != null)
                {
                    engineLoader.Dispose();
                    engineLoader = null;
                }
            }
            catch (Exception error)
            {
                displayMessage("Fehler: " + 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;
 
                // Bilddatei zum Dokument hinzufügen
                displayMessage("Bilddatei wird geladen...");
                IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                document.AddImageFileFromMemory(handle.ToInt64(), null, null);
                // Dokument erkennen
                displayMessage("Erkennung läuft...");
                document.Process(null);
                // Ergebnisse speichern
                displayMessage("Ergebnisse werden gespeichert...");
                FileWriter fileWriter = new FileWriter(inputBlobName, ".pdf");
                resultBlobName = inputBlobName + ".pdf";
                document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
            }
            catch (Exception error)
            {
                displayMessage("Fehler: " + error.Message);
                throw error;
            }
            finally
            {
                // Dokument schließen
                document.Close();
            }
            return resultBlobName;
        }
        public void Dispose()
        {
            UnloadEngine();
        }
    }
}