macOS/Linux でネイティブ ライブラリを含む Azure Function をローカル実行するとエラーになる
はじめに
Azure Functions で画像の加工をしようと思ったのですが、System.Drawing.Common
名前空間は Windows 以外ではサポートされていません。
記事にもあるように 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 もあるのですが解決には至っていないようです。
解決方法
この事象はローカル実行でのみ発生するので、デプロイして動作確認は Azure でするというのが考えられます。また Windows を使うという方法によっても回避できます。どうしても macOS/Linux でのローカル実行を実現したければ /usr/local/lib
などのフォルダーにライブラリをコピーするといいでしょう。.csproj
ファイルのビルド アクションでコピーするという方法もありそうです。
おわりに
Azure Functions Core Tools のビルドで「.dll
ファイルのみ bin
フォルダーにコピーする」というロジックになっているような気がします。
Discussion