跳轉到主要內容
本節說明如何將適用於 Windows 的 FRE 12 應用程式部署到 Azure App Service。以下以一組使用 Azure Storage account 資料的 WebJob 專案為例。檔案處理會透過 Blob 容器執行。
在此情境中,對於發票、收據等小型單頁文件,您將可獲得最佳的識別結果。
將您的應用程式部署到 App Service 包含以下幾個步驟:
  1. 依照必要條件準備本機環境與應用程式執行個體
  2. 在部署應用程式之前,執行準備步驟
  3. 在 App Service 中部署並執行您的應用程式
請使用下方說明中的程式碼範例

必要條件

本地電腦

在建立您的 App Service 之前,請依照下列規格準備您的本地電腦:
  • Visual Studio 2019 及其用於開發 Azure 應用程式的模組 (請檢查 Visual Studio 中的 Azure 功能 或使用 Visual Studio Installer 下載此類模組)
  • Azure SDK (下載 此處)
  • .NET Framework 4.7.2
  • 用於處理 Azure Storage 和 Blob 容器的 NuGet 套件:
    • Azure.Storage.Blobs (下載 此處)
    • Azure.Storage.Queues (下載 此處)
    • Newtonsoft.Json (下載 此處)
    • System.IO.Compression.ZipFile (下載 此處)
  • .NET Framework 4.7 的 ABBYY FineReader Engine 包裝器 (開發人員安裝之後,在 C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops 資料夾中)
  • 已覆寫以處理 Blob 容器的 IFileWriter 介面 (請參閱下列 範例)
  • Azure Storage Explorer (選用 - 下載 此處)
  • Azure 帳戶中的虛擬機器,專供授權程序使用。為了進一步的設定,您需要:
    • IP 位址
    • 開放連線埠 (預設或使用者定義) 。要開啟它,請使用防火牆
    • Sockets 網路通訊協定連線詳細資訊

準備步驟

準備步驟需在您的本機上完成。完成這些步驟後,您就會備妥開始部署應用程式所需的所有設定和檔案:
  1. 建立包含 ABBYY FineReader Engine 程式庫的封存檔 (例如 LibraryPackage.zip) 。檔案清單列在 FREngineDistribution.csv 檔案中。
    重要!如果您的儲存空間有限 (例如使用 1GB 空間的 App Service Plan) ,建議使用 /extract 選項建立自訂的最小化 ABBYY FineReader Engine 套件。其餘儲存空間將用於處理檔案。
    建立封存檔時,請注意 ABBYY FineReader Engine 的授權設定必須依照虛擬機器設定進行配置:
    • LicensingSettings.xml 檔案必須設為 Network 組態 (請參閱 Working with the LicensingSettings.xml File) 。
    • 必須使用 Sockets 網路通訊協定。
    • 線上授權 token file 必須位於 Bin64 資料夾中。
  2. 建立 Azure Storage account (本文中為 frestorage) 。所有必要說明皆可在 Azure 網站上找到。
  3. 依需求建立您的 App Service (請參閱此處的說明) 。
  4. 在 frestorage 中建立兩個 Blob 容器:
    • fre-lib - 用於存放 ABBYY FineReader Engine 檔案
    • processing-container - 用於存放處理結果
  5. 以最方便的方式將 LibraryPackage.zip 上傳到 fre-lib container (使用 .NET、Powershell、Python 指令碼或 Azure Storage Explorer/Azure Portal 應用程式) 。
  6. 在您的 Azure 帳戶中部署並設定套用授權設定的虛擬機器:
    • 透過 LibraryPackage.zip 中的 installLM.exe 安裝授權管理工具程式。
    • 在 LicensingSettings.xml 中設定 Sockets 網路通訊協定,然後重新啟動 Licensing Service。
    • 確保 Azure App Service 可以存取 Licensing Service 的連線埠 (調整虛擬機器上的 Windows 防火牆規則) 。
    • 啟用您的授權 (僅適用於 software protection;online protection 不需要 activation) 。
  7. 在 frestorage 中建立兩個 queue:
    • processing-queue - 用於設定檔案處理工作
    • status-queue - 用於通知工作完成
  8. 在 Visual Studio 2019 中建立兩個 Azure WebJob (.NET Framework) 專案,以搭配 frestorage 使用:
    • FreDeployerJob - 用於將 LibraryPackage.zip 部署到 App Service (請參閱其檔案清單:Config.cs、Functions.cs、Program.cs,見下方)
    • FreProcessorJob - 用於文件處理 (請參閱其檔案清單:Config.cs、Functions.cs、Program.cs、EngineLoader.cs、IFileWriter.cs、Processor.cs,見下方)

在 App Service 中部署及執行 ABBYY FineReader Engine

若要部署 ABBYY FineReader Engine:
  1. 使用 Visual Studio 將 FreDeployerJob 發佈到 Azure App Service (將 WebJob Type 設為 Triggered) 。
  2. 在 Azure 入口網站中開啟您的 App Service。
  3. 開啟 App Service 的 WebJobs。
  4. 在 WebJobs 清單中找出 FreDeployerJob。
  5. 在 WebJobs 索引標籤中,以滑鼠右鍵按一下 FreDeployerJob,然後執行 Run 命令來啟動它。
您可以開啟 Logs 索引標籤來檢查部署結果。若部署成功,LibraryPackage.zip 會從 fre-lib 容器上傳,並部署到 App Service 中所有實體皆可使用的 %HOME_EXPANDED% 資料夾內。 若要部署 FreProcessorJob,請使用 Visual Studio 將 FreProcessorJob 發佈到 Azure App Service (將 WebJob Type 設為 Continuous) 。如此一來,FreProcessorJob 就會出現在您 App Service 的 WebJobs 索引標籤清單中。 若要處理檔案:
  1. 將您要處理的檔案上傳至 processing-container。
  2. 以 {“blob-item-name” : “file_name”} 格式,將新的處理工作 JSON 訊息新增至 processing-queue。若您將 Demo.tif 上傳至 processing-container,訊息應為:
{"blob-item-name" : "Demo.tif"}
  1. 等待任務完成。新任務一設定好,FreProcessorJob 就會開始在記憶體中處理指定的檔案。status-queue 會包含此任務執行情況的相關項目。
  2. 在 processing-container 中尋找輸出檔案。
  1. FreProcessorJob 是以單執行緒處理序的方式運作。若您打算平行處理檔案,則需要建立多個 FreProcessorJob 來接聽相同的佇列。2. 每增加一個 FreProcessorJob,都會額外耗用記憶體。購買 Service Plan 時,請將這點納入考量。例如,在 Azure Free Service Plan 中,最好只使用一個記憶體耗用較低的 FreProcessorJob,藉此確保檔案處理的穩定性。3. 單一 FreProcessorJob 不適合處理大型多頁文件。在這種情況下,請考慮在 Azure Cloud Service 或 Azure Virtual Machine 中識別您的文件,而非使用 App Service。

程式碼範例

本節包含在 App Service 中部署及使用 ABBYY FineReader Engine API 的程式碼範例。

FreDeployerJob:

using System.IO;
class Config
{
    // Blob 容器的連線字串
    public static readonly string ConnectionString = "your_connection_string";
    // HOME_EXPANDED 目錄是所有 WebJobs 資料夾共用的
    public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
    // 儲存體中輸入和輸出容器的名稱
    public static readonly string LibraryContainerName = "fre-lib";
namespace FreDeployerJob
{
    public class Functions
    {
        // 此函式不會自動觸發,您必須手動執行
        [NoAutomaticTrigger]
        [Timeout("01:00:00")]
        public static void DeployFRE()
        {
            Console.WriteLine("Deploying FRE");
            // 透過儲存體帳戶連接字串,連線至現有的輸入容器 <InputContainerName>
            BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.LibraryContainerName);
            // 建立程式庫目錄,以及供 ABBYY FineReader Engine 初始化使用的 AppData 和 Temp 資料夾
            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"));
            // 逐一查看容器中的 Blob。<InputContainerName> 中的每個 Blob 都對應到某個影像檔案
            foreach (BlobItem blobItem in inputContainerClient.GetBlobs())
            {
                Console.WriteLine("\t" + blobItem.Name);
                // 尋找 ABBYY FineReader Engine 程式庫的精簡版本
                if (blobItem.Name == "LibraryPackage.zip")
                {
                    Console.WriteLine("LibraryPackage.zip was found.");
                    // 連線到 Blob 以存取其內容
                    BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.LibraryContainerName, blobItem.Name);
                    Console.WriteLine("Downloading to memory...");
                    // 將 zip 下載到記憶體
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        blobClient.DownloadTo(memoryStream);
                        Console.WriteLine("LibraryPackage.zip was downloaded.");
                        Console.WriteLine("Unzipping...");
                        // 不使用暫存資料夾進行解壓縮(僅在記憶體中處理)
                        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
{
    // 若要深入瞭解 Microsoft Azure WebJobs SDK,請參閱 https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // 請在 app.config 中為這些 WebJobs 設定下列連接字串,才能執行:
        // AzureWebJobsDashboard 和 AzureWebJobsStorage
        static void Main()
        {
            // 觸發後,此 WebJob 只會呼叫這個方法
            Functions.DeployFRE();
        }
    }
}

FreProcessorJob:

using System;
using System.IO;
namespace FreProcessorJob
{
    class Config
    {
        // Blob 容器 的連線字串
        public static readonly string ConnectionString = "your_connection_string";
        // HOME_EXPANDED 目錄是所有 WebJobs 資料夾共用的
        // 與 FreDeployerJob 專案中的設定相同
        public static readonly string LibraryFolder = Path.Combine(System.Environment.GetEnvironmentVariable("HOME_EXPANDED"), "FRE");
        // 儲存體中的處理容器名稱
        public static readonly string ProcessingContainerName = "processing-container";
        // 處理佇列名稱
        public static readonly string ProcessingQueueName = "processing-queue";
        public static readonly string StatusQueueName = "status-queue";
        // 傳回 ABBYY FineReader Engine 的 Customer Project ID
        public static String GetCustomerProjectId()
        {
            return "your_cpid";
        }
 
        // 傳回線上授權權杖名稱
        // 如果您未使用線上授權,請保留空字串
        // 權杖應位於程式庫套件的 Bin64 資料夾中
        public static String GetLicenseTokenName()
        {
            return "your_online_license_token_if_you_have_it";
        }
 
        // 傳回線上授權密碼
        // 如果您未使用線上授權,請保留空字串
        public static String GetLicensePassword()
        {
            return "online_license_password_if_you have_it";
        }
 
        // 傳回授權權杖名稱
        // 授權相關元件位於程式庫套件的 Bin64 資料夾中
        public static String GetLicenseTokenName()
        {
            return "your_licence_for_ABBYY FineReader Engine";
        }
 
        // 傳回授權密碼
        public static String GetLicensePassword()
        {
            return "license_password";
        }
 
        // 傳回 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
    {
        // 當有新訊息寫入名為 processing-queue 的 Azure 佇列時,
        // 系統會觸發/執行此函式
        // 訊息應為包含 'blob-item-name' 索引鍵的 JSON 訊息
        // 處理結果會儲存至 processing 容器
        // 處理狀態會以 JSON 格式傳送至 status-queue
        public static void ProcessQueueMessage([QueueTrigger("processing-queue")] string message)
        {
            // 首先,連線到 status-queue
            QueueClient queueClient = new QueueClient(Config.ConnectionString, Config.StatusQueueName);
            try
            {
                // 這會記錄到 Azure 入口網站中的 WebJob 日誌
                Console.WriteLine("Accepted task: " + message);
                JObject task = JObject.Parse(message);
                task["processor_id"] = Environment.GetEnvironmentVariable("WEBJOBS_NAME");
 
                // 這會傳送到 status-queue
                task["status"] = "accepted";
                queueClient.SendMessage(task.ToString());
 
                // 取得 blob-item-name,也就是 Processing 容器中的檔案名稱
                string blobFileName = task["blob-item-name"].ToString();
                BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.ProcessingContainerName, blobFileName);
                // 將 Blob 載入記憶體
                Console.WriteLine("\t Downloading blob to memory: " + blobFileName);
                MemoryStream memoryStream = new MemoryStream();
                blobClient.DownloadTo(memoryStream);
                Console.WriteLine("\t Downloaded.");
                // 將狀態更新為 processing
                Console.WriteLine("\t Processing in FRE: " + blobFileName);
                task["status"] = "processing";
                queueClient.SendMessage(task.ToString());
                // 使用記憶體處理方法,在 ABBYY FineReader Engine 中處理已下載的 Blob
                // 輸出為儲存於 <ProcessingContainerName> 中、作為 Blob 的處理結果名稱
                string resultBlobName = "";
                using (FreProcessor.Processor freProcessor = new FreProcessor.Processor())
                {
                    resultBlobName = freProcessor.ProcessBlobFromMemory(memoryStream, blobFileName);
                    Console.WriteLine("\t Result blob name in output container: " + resultBlobName);
                }
                // 刪除輸入影像
                Console.WriteLine("\t Deleting from input container: " + blobFileName);
                blobClient.Delete();
                // 將狀態更新為 succeeded
                // 在 Azure 入口網站日誌中
                Console.WriteLine("Succeeded");
 
                // 在 status-queue 中
                task["status"] = "succeeded";
                task["result-blob-name"] = resultBlobName;
                queueClient.SendMessage(task.ToString());
            }
            catch (Exception error)
            {
                // 發生任何錯誤時,回報
                // 至 Azure 入口網站日誌
                Console.WriteLine("Failed: " + error.Message);
                // 至 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
{
    // 若要進一步了解 Microsoft Azure WebJobs SDK,請參閱 https://go.microsoft.com/fwlink/?LinkID=320976
    class Program
    {
        // 請在 app.config 中為這些 WebJobs 設定下列連接字串,才能執行:
        // AzureWebJobsDashboard 和 AzureWebJobsStorage
        static void Main()
        {
            var config = new JobHostConfiguration();
            if (config.IsDevelopment)
            {
                config.UseDevelopmentSettings();
            }
            // ABBYY FineReader Engine 並非執行緒安全,因此無法同時處理超過一則訊息
            config.Queues.BatchSize = 1;
            var host = new JobHost(config);
            // 下列程式碼可確保 WebJob 持續執行
            // 因為其中一個函式已連接至 Azure 佇列,並會接聽新任務
            host.RunAndBlock();
        }
    }
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using FREngine;
namespace FreProcessorJob.FreProcessor
{
    // 用於載入/卸載 FREngine.dll 及初始化/反初始化 Engine 的類別
    // 載入在建構函式中執行,卸載在 Dispose() 中執行
    // 載入失敗時擲回例外
    public class EngineLoader : IDisposable
    {
        // 使用儲存於 SamplesConfig.cs 中的設定載入 ABBYY FineReader Engine
        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 程式庫
                dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
                if (dllHandle == IntPtr.Zero)
                {
                    int error = Marshal.GetLastWin32Error();
                    Console.WriteLine("最後一個 Win32 錯誤為:" + error);
                    throw new Exception("無法載入 " + enginePath);
                }
 
                IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
                if (initializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("找不到 InitializeEngine 函式");
                }
                IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
                if (deinitializeEnginePtr == IntPtr.Zero)
                {
                    throw new Exception("找不到 DeinitializeEngine 函式");
                }
                IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
                if (dllCanUnloadNowPtr == IntPtr.Zero)
                {
                    throw new Exception("找不到 DllCanUnloadNow 函式");
                }
                // 將指標轉換為委派
                initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    initializeEnginePtr, typeof(InitializeEngine));
                deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                    deinitializeEnginePtr, typeof(DeinitializeEngine));
                dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                    dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
                // 呼叫 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)
            {
                // 釋放 FREngine.dll 程式庫
                engine = null;
                // 在呼叫 FreeLibrary 之前釋放所有物件
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                FreeLibrary(dllHandle);
                dllHandle = IntPtr.Zero;
                initializeEngine = null;
                deinitializeEngine = null;
                dllCanUnloadNow = null;
                throw;
            }
        }
        // 卸載 ABBYY FineReader Engine
        public void Dispose()
        {
            if (engine == null)
            {
                // Engine 尚未載入
                return;
            }
            engine = null;
            int hresult = deinitializeEngine();
            // 在呼叫 FreeLibrary 之前釋放所有物件
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            hresult = dllCanUnloadNow();
            if (hresult == 0)
            {
                FreeLibrary(dllHandle);
            }
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            // 清理完成後擲回例外
            Marshal.ThrowExceptionForHR(hresult);
        }
        // 回傳指向 ABBYY FineReader Engine 主要物件的指標
        public IEngine Engine
        {
            get
            {
                return engine;
            }
        }
        // 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);
        // 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();
        // 私有變數
        private FREngine.IEngine engine = null;
        // FREngine.dll 的 Handle
        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()
        {
            // 在 <ProcessingContainerName> 中建立與新 Blob 的連線。處理結果將儲存於其中
            BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, 
                Config.ProcessingContainerName,
                resultBlobName + fileExtension);
            // 重寫現有檔案
            resultBlobClient.DeleteIfExists();
            // 將位置設為 0 以從開頭寫入檔案
            stream.Position = 0;
            resultBlobClient.Upload(stream);
            stream.Close();
        }
        public void Dispose()
        {
            // 在處置時關閉記憶體資料流,以便在資料寫入後仍能存取它
            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("Loading predefined profile...");
            // 這是選用的
            engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
            // 在效能較低的 App Service Plan 上這是強制的,因為我們會在並行處理時遇到錯誤
            engineLoader.Engine.MultiProcessingParams.MultiProcessingMode = MultiProcessingModeEnum.MPM_Sequential;
        }
        private void LoadEngine()
        {
            try
            {
                if (engineLoader == null)
                {
                    engineLoader = new EngineLoader();
                }
                setupFREngine();
            }
            catch (Exception error)
            {
                displayMessage("error: " + error.Message);
            }
        }
        private void UnloadEngine()
        {
            try
            {
                if (engineLoader != null)
                {
                    engineLoader.Dispose();
                    engineLoader = null;
                }
            }
            catch (Exception error)
            {
                displayMessage("error: " + 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;
 
                // 將影像檔案新增至文件
                displayMessage("Loading image...");
                IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                document.AddImageFileFromMemory(handle.ToInt64(), null, null);
                // 辨識文件
                displayMessage("Recognizing...");
                document.Process(null);
                // 儲存結果
                displayMessage("Saving results...");
                FileWriter fileWriter = new FileWriter(inputBlobName, ".pdf");
                resultBlobName = inputBlobName + ".pdf";
                document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
            }
            catch (Exception error)
            {
                displayMessage("error: " + error.Message);
                throw error;
            }
            finally
            {
                // 關閉文件
                document.Close();
            }
            return resultBlobName;
        }
        public void Dispose()
        {
            UnloadEngine();
        }
    }
}