🐧

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

2023/12/30に公開

はじめに

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

SkipSharp は Google の Skia を .NET に移植したものです。SkipSharp も 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