🐧

macOS/Linux でネイティブ ライブラリを含む Azure Function をローカル実行するとエラーになる

に公開

はじめに

Azure Functions で画像の加工をしようとしましたが、System.Drawing.Common 名前空間は Windows 以外ではサポートされていません。

https://learn.microsoft.com/ja-jp/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only?WT.mc_id=M365-MVP-5002941

記事にもあるように SkiaSharp を使うのがよさそうです。

https://github.com/mono/SkiaSharp

SkiaSharp は Google の Skia を .NET に移植したものです。SkiaSharp も mono プロジェクトで公開されているため、安心して利用できます。Linux の場合は SkiaSharp.NativeAssets.Linux または SkiaSharp.NativeAssets.Linux.NoDependencies の追加が必要です。

実際に以下のようなコードを書きました。画像を指定したサイズに変更する関数です。

public static class ResizeFunction
{
    [FunctionName("ResizeFunction")]
    public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "POST", Route = null)] HttpRequest req)
    {
        if (!int.TryParse(req.Query["width"], out var width))
        {
            return new BadRequestResult();
        }
        if (!int.TryParse(req.Query["height"], out var height))
        {
            return new BadRequestResult();
        }
        var sourceStream = req.Body;
        var destStream = new MemoryStream();
        var sourceImage = SKBitmap.Decode(sourceStream);
        var destImage = sourceImage.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
        _ = destImage.Encode(destStream, SKEncodedImageFormat.Png, 100);
        destStream.Position = 0;
        return new FileStreamResult(destStream, "image/png");
    }
}

このコードをローカルでデバッグしようとすると、Windows では成功しますが、macOS/Linux では失敗します。以下は macOS での例です。

System.TypeInitializationException: The type initializer for 'SkiaSharp.SKAbstractManagedStream' threw an exception.
System.DllNotFoundException: Unable to load shared library 'libSkiaSharp' or one of its dependencies. In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment variable: dlopen(liblibSkiaSharp, 0x0001): tried: 'liblibSkiaSharp' (no such file), '/System/Volumes/Preboot/Cryptexes/OSliblibSkiaSharp' (no such file), '/usr/lib/liblibSkiaSharp' (no such file, not in dyld cache), 'liblibSkiaSharp' (no such file), '/usr/local/lib/liblibSkiaSharp' (no such file), '/usr/lib/liblibSkiaSharp' (no such file, not in dyld cache)

原因

この現象は Azure Functions でのみ発生します。つまり Azure Functions Core Tools に原因があると考えられます。

通常の Web アプリケーションでは、サイトのルートである wwwroot フォルダーにライブラリが配置されます。一方、Azure Functions のフォルダー構成は以下のようになります。ルートには host.json があり、ライブラリは bin フォルダーに配置されます。

wwwroot
  ├─host.json
  └─bin
    ├─XXX.dll
    ├─SkiaSharp.dll
    └─runtimes
      ├─linux-XXX
      │ └─native
      │   └─libSkiaSharp.so
      ├─osx
      │ └─native
      │   └─libSkiaSharp.dylib
      └─win-XXX
        └─native
          └─libSkiaSharp.dll

ローカル実行の場合、アプリケーションのルートはビルド先のフォルダー (bin/Debug/net6.0 など) です。このとき、ネイティブ ライブラリが含まれる runtime フォルダーはビルド先のフォルダーと bin フォルダーの両方に配置されます。しかし bin フォルダーには Windows しか配置されません。そのためライブラリが見つからず、エラーが発生します。

bin/Debug/net6.0
  ├─host.json
  ├─XXX.dll
  ├─SkiaSharp.dll
  ├─bin
  │ ├─XXX.dll
  │ ├─SkiaSharp.dll
  │ └─runtimes
  │   └─win-XXX
  │     └─native
  │       └─libSkiaSharp.dll
  └─runtimes
    ├─linux-XXX
    │ └─native
    │   └─libSkiaSharp.so
    ├─osx
    │ └─native
    │   └─libSkiaSharp.dylib
    └─win-XXX
      └─native
        └─libSkiaSharp.dll

すでに Issue もありますが、解決には至っていません。

https://github.com/mono/monodevelop/issues/6673
https://github.com/Azure/azure-functions-host/issues/4481

解決方法

この事象はローカル実行でのみ発生します。デプロイして動作確認は Azure で行うことができます。また Windows を利用することで回避できます。どうしても macOS/Linux でローカル実行したい場合は、/usr/local/lib などのフォルダーにライブラリをコピーしてください。csproj ファイルのビルド アクションでコピーする方法もあります。

おわりに

Azure Functions Core Tools のビルドでは dll ファイルのみ bin フォルダーにコピーする というロジックになっているようです。

Discussion