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