> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abbyy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure App Service での実行

> ABBYY FineReader Engine 12 for Windows を Azure App Service にデプロイし、2 つの WebJob プロジェクトと Azure Storage Blob コンテナーを使用してファイルを処理します。

このセクションでは、FRE 12 for Windows アプリケーションを Azure App Service にデプロイする方法について説明します。例として、Azure Storage アカウントのデータを使用する 2 つの WebJob プロジェクトを紹介します。ファイルの処理には Blob コンテナーを使用します。<br />このシナリオを使用すると、請求書や領収書などの小さな 1 ページ文書で最適な認識結果を得ることができます。

アプリケーションを App Service にデプロイするには、いくつかの手順があります。

1. [前提条件](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#prerequisitesapp)に従って、ローカル マシンとアプリ インスタンスを準備する
2. アプリケーションをデプロイする前に、[準備手順](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#preparatorystepsapp)を実行する
3. [App Service でアプリケーションをデプロイして実行する](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#deployingabbyyfinereaderengineinappservice)

以下の手順で示す[コード サンプル](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#scriptsapp)を使用してください。

<div id="prerequisites">
  ## 前提条件
</div>

<div id="local-machine">
  ### ローカル マシン
</div>

App Service を作成する前に、以下の要件に従ってローカル マシンを準備してください。

* Visual Studio 2019 および Azure 向けアプリケーション開発用モジュール ([Visual Studio の Azure 機能](http://visualstudio.microsoft.com/vs/features/azure/)を確認するか、Visual Studio Installer を使用してこれらのモジュールをダウンロードしてください)
* Azure SDK ([こちら](https://azure.microsoft.com/en-us/downloads/)からダウンロード)
* .NET Framework 4.7.2
* Azure Storage および Blob コンテナーを操作するための NuGet パッケージ:
  * Azure.Storage.Blobs ([こちら](https://www.nuget.org/packages/Azure.Storage.Blobs/)からダウンロード)
  * Azure.Storage.Queues ([こちら](https://www.nuget.org/packages/Azure.Storage.Queues/)からダウンロード)
  * Newtonsoft.Json ([こちら](https://www.nuget.org/packages/Newtonsoft.Json/)からダウンロード)
  * System.IO.Compression.ZipFile ([こちら](https://www.nuget.org/packages/System.IO.Compression/)からダウンロード)
* .NET Framework 4.7 用の ABBYY FineReader Engine ラッパー (開発者向けインストール後、C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops フォルダー内)
* Blob コンテナーで動作するようにオーバーライドした [IFileWriter](/ja/fine-reader/engine/api-reference/supplementary-objects-and-methods/ifilewriter) インターフェイス (以下の[サンプル](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#ifilewriter)を参照)
* Azure Storage Explorer (任意。[こちら](http://azure.microsoft.com/en-us/features/storage-explorer/)からダウンロード)
* ライセンス処理用に使用する Azure アカウント内の仮想マシン。以降の設定には、次の情報が必要です:
  * IP アドレス
  * 開放された接続ポート (既定またはユーザー定義) 。開放するにはファイアウォールを使用します
  * Sockets ネットワーク プロトコル接続の詳細

<div id="preparatory-steps">
  ## 準備手順
</div>

準備作業はローカル マシンで行います。これらの手順を完了すると、アプリケーションのデプロイを開始するために必要な設定とファイルをすべて用意できます。

1. ABBYY FineReader Engine ライブラリを含むアーカイブ (たとえば LibraryPackage.zip) を作成します。含めるファイルの一覧は FREngineDistribution.csv ファイルに記載されています。<br />重要: ストレージ容量が限られている場合 (たとえば、容量が 1GB の App Service Plan を使用している場合) は、`/extract` オプションを使用して、最小サイズのカスタム ABBYY FineReader Engine パッケージを作成することをお勧めします。残りのストレージ容量はファイル処理に使用されます。<br />アーカイブを作成する際は、ABBYY FineReader Engine のライセンス設定を仮想マシンの設定に合わせて構成する必要がある点に注意してください。
   * LicensingSettings.xml ファイルは Network 構成用に設定する必要があります ([Working with the LicensingSettings.xml File](/ja/fine-reader/engine/licensing/working-with-the-licensingsettingsxml-file) を参照) 。
   * Sockets ネットワーク プロトコルを使用する必要があります。
   * オンライン ライセンスのトークン ファイルは Bin64 フォルダー内に配置する必要があります。
2. Azure Storage アカウント (この記事では frestorage) を作成します。必要な手順は [Azure](http://docs.microsoft.com/en-us/azure/?product=featured) の Web サイトにあります。
3. 必要に応じて App Service を作成します (手順は [こちら](https://docs.microsoft.com/en-us/azure/app-service/) を参照) 。
4. frestorage 内に 2 つの Blob コンテナーを作成します:
   * fre-lib - ABBYY FineReader Engine ファイル用
   * processing-container - 処理結果用
5. 都合のよい方法 (.NET、Powershell、Python スクリプト、または Azure Storage Explorer/Azure Portal アプリケーション) で、LibraryPackage.zip を fre-lib コンテナーにアップロードします。
6. Azure アカウントで、ライセンス設定を含む仮想マシンをデプロイして構成します:
   * LibraryPackage.zip 内の installLM.exe を使用して License Manager ユーティリティをインストールします。
   * LicensingSettings.xml で Sockets ネットワーク プロトコルを設定し、その後 Licensing Service を再起動します。
   * Azure App Service から Licensing Service の接続ポートにアクセスできることを確認します (仮想マシン上の Windows Firewall ルールを調整します) 。
   * ライセンスをアクティブ化します (ソフトウェア保護のみ。オンライン保護ではアクティベーションは不要です) 。
7. frestorage 内に 2 つのキューを作成します:
   * processing-queue - ファイル処理タスクの設定用
   * status-queue - タスク完了通知用
8. frestorage で使用する 2 つの Azure WebJob (.NET Framework) プロジェクトを Visual Studio 2019 で作成します:
   * FreDeployerJob - LibraryPackage.zip を App Service にデプロイするため (ファイル一覧: Config.cs、Functions.cs、Program.cs。詳細は [以下](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#fredeployerjob) を参照)
   * FreProcessorJob - 文書処理用 (ファイル一覧: Config.cs、Functions.cs、Program.cs、EngineLoader.cs、IFileWriter.cs、Processor.cs。詳細は [以下](/ja/fine-reader/engine/distribution/distribution-windows/running-abbyy-finereader-engine-inside-azure-services/running-in-azure-app-service#freprocessorjob) を参照)

<div id="deploying-and-running-abbyy-finereader-engine-in-app-service">
  ## App Service での ABBYY FineReader Engine のデプロイと実行
</div>

ABBYY FineReader Engine をデプロイするには:

1. Visual Studio を使用して FreDeployerJob を Azure App Service に発行します (WebJob の種類を Triggered に設定)。
2. Azure ポータルで App Service を開きます。
3. App Service の WebJobs を開きます。
4. WebJobs の一覧で FreDeployerJob を探します。
5. WebJobs タブで右クリックメニューから Run を実行して FreDeployerJob を起動します。

デプロイの結果を確認するには、Logs タブにアクセスできます。成功した場合、fre-lib コンテナーから LibraryPackage.zip がアップロードされ、App Service 内のすべてのエンティティから利用可能な %HOME\_EXPANDED% フォルダー内にデプロイされます。

FreProcessorJob をデプロイするには、Visual Studio を使用して FreProcessorJob を Azure App Service に発行します (WebJob の種類を Continuous に設定)。その結果、FreProcessorJob は App Service の WebJobs の一覧に表示されます。

ファイルを処理するには:

1. 処理対象のファイルを processing-コンテナーにアップロードします。
2. 処理の新しいタスク用の JSON メッセージを、形式 \{"blob-item-name" : "file\_name"} で processing-queue に追加します。processing-コンテナーに Demo.tif をアップロードした場合、メッセージは次のようになります:

```json theme={null}
{"blob-item-name" : "Demo.tif"}
```

3. タスクの完了を待ちます。新しいタスクが設定されるとすぐに、FreProcessorJob が指定されたファイルをメモリ内で処理し始めます。status-queue には、このタスクの実行に関するエントリが含まれます。
4. processing-container 内で出力ファイルを見つけます。

<Warning>
  1) FreProcessorJob はシングルスレッドのプロセスとして動作します。ファイルを並列処理する予定がある場合、同じキューを監視する複数の FreProcessorJob を作成する必要があります。 2. 追加の各 FreProcessorJob は余分なメモリを消費します。Service Plan を購入する際は、この点を考慮してください。例えば、Azure Free Service Plan では、少量のメモリを消費し、ファイル処理の安定性を確保する単一の FreProcessorJob を持つのが理想的です。 3. 単一の FreProcessorJob を使用することは、大規模な多ページドキュメントの処理には適していません。この場合、App Service の代わりに Azure Cloud Service または Azure Virtual Machine でのドキュメント認識を検討してください。
</Warning>

<div id="code-samples">
  ## コード サンプル
</div>

このセクションでは、App Service で ABBYY FineReader Engine API をデプロイおよび実装する際に使用するコード サンプルを紹介します。

<div id="fredeployerjob">
  ## FreDeployerJob:
</div>

<Accordion title="Config.cs">
  ```csharp theme={null}
  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";
  ```
</Accordion>

<Accordion title="Functions.cs">
  ```csharp theme={null}
  namespace FreDeployerJob
  {
      public class Functions
      {
          // この関数は自動ではトリガーされないため、手動で実行する必要があります
          [NoAutomaticTrigger]
          [Timeout("01:00:00")]
          public static void DeployFRE()
          {
              Console.WriteLine("Deploying FRE");
              // Storage Account の接続文字列を使用して、既存の入力コンテナー <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.");
                      }
                  }
              }
          }
      }
  }
  ```
</Accordion>

<Accordion title="Program.cs">
  ```csharp theme={null}
  namespace FreDeployerJob
  {
      // Microsoft Azure WebJobs SDK の詳細については、https://go.microsoft.com/fwlink/?LinkID=320976 を参照してください
      class Program
      {
          // これらの WebJobs を実行するには、app.config で次の接続文字列を設定してください
          // AzureWebJobsDashboard および AzureWebJobsStorage
          static void Main()
          {
              // この WebJob がトリガーされると、このメソッドのみが呼び出されます
              Functions.DeployFRE();
          }
      }
  }
  ```
</Accordion>

<div id="freprocessorjob">
  ## FreProcessorJob:
</div>

<Accordion title="Config.cs">
  ```csharp theme={null}
  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;
          }
      }
  }
  ```
</Accordion>

<Accordion title="Functions.cs">
  ```csharp theme={null}
  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 Queue に新しいメッセージが書き込まれると
          // トリガーされて実行されます
          // メッセージは、'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 portal の 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());
                  // ダウンロードした blob を、メモリ処理メソッドを使って ABBYY FineReader Engine で処理します
                  // 出力は、<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 portal のログに記録します
                  Console.WriteLine("Succeeded");
   
                  // status-queue に送信します
                  task["status"] = "succeeded";
                  task["result-blob-name"] = resultBlobName;
                  queueClient.SendMessage(task.ToString());
              }
              catch (Exception error)
              {
                  // エラーが発生した場合は報告します
                  // Azure portal のログへ
                  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());
              }
          }
      }
  }
  ```
</Accordion>

<Accordion title="Program.cs">
  ```csharp theme={null}
  using Microsoft.Azure.WebJobs;
  namespace FreProcessorJob
  {
      // Microsoft Azure WebJobs SDK の詳細については、https://go.microsoft.com/fwlink/?LinkID=320976 を参照してください
      class Program
      {
          // これらの WebJobs を実行するには、app.config で次の接続文字列を設定してください:
          // 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 queue に関連付けられ、新しいタスクを待ち受けるためです
              host.RunAndBlock();
          }
      }
  }
  ```
</Accordion>

<Accordion title="EngineLoader.cs">
  ```csharp theme={null}
  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 へのハンドル
          private IntPtr dllHandle = IntPtr.Zero;
          private InitializeEngine initializeEngine = null;
          private DeinitializeEngine deinitializeEngine = null;
          private DllCanUnloadNow dllCanUnloadNow = null;
      }
  }
  ```
</Accordion>

<Accordion title="IFileWriter.cs">
  ```csharp theme={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;
      }
  }
  ```
</Accordion>

<Accordion title="Processor.cs">
  ```csharp theme={null}
  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("定義済みプロファイルを読み込んでいます...");
              // オプション
              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.Message);
              }
          }
          private void UnloadEngine()
          {
              try
              {
                  if (engineLoader != null)
                  {
                      engineLoader.Dispose();
                      engineLoader = null;
                  }
              }
              catch (Exception error)
              {
                  displayMessage("エラー: " + 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("画像を読み込んでいます...");
                  IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                  Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                  document.AddImageFileFromMemory(handle.ToInt64(), null, null);
                  // ドキュメントを認識
                  displayMessage("認識中...");
                  document.Process(null);
                  // 結果を保存
                  displayMessage("結果を保存しています...");
                  FileWriter fileWriter = new FileWriter(inputBlobName, ".pdf");
                  resultBlobName = inputBlobName + ".pdf";
                  document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
              }
              catch (Exception error)
              {
                  displayMessage("エラー: " + error.Message);
                  throw error;
              }
              finally
              {
                  // ドキュメントを閉じる
                  document.Close();
              }
              return resultBlobName;
          }
          public void Dispose()
          {
              UnloadEngine();
          }
      }
  }
  ```
</Accordion>
