🎥

C#でもaviutl2のプラグインが作れる!Native AOTを添えて

に公開

突如として現れたAviUtl ExEdit2(通称?aviutl2)、とても懐かしい気分でいっぱいですね!
そんなaviutl2はaviutlと同様、Plugin機構が備わっています。

このPlugin機構いつか触ってみたいと思っていたのですが、ちょうど良い機会なので挑戦してみようと思います。
しかしサンプルとして提供されているのはC++!避け続けてきたので急にハードルが上がってしまいました。
それでも作りたい、なにか方法はないかと頭を悩ませた結果ひとつの結論にたどり着きました。

C# で書けば良いのだと。

Native AOTという救世主

「でもC#で書いたDLLをCのプラグインとして使えるの?」という疑問が浮かぶと思います。

実は.NET 7(?)以降で提供されている Native AOT という機能を使うことで、C#コードを直接ネイティブコードにコンパイルできるようになりました。
これにより、.NET Runtimeが不要で、Cで書いたプラグインと同じように動作するネイティブライブラリを作成できます。

https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/libraries?wt.mc_id=DT-MVP-5002987

AviUtlプラグインの基本構造

AviUtl ExEdit2のプラグインは、特定の関数をエクスポートするDLLとして実装されているようです。
SDKの入力プラグインのサンプル実装である AviReader.cpp を確認してみた所、EXTERN_C している GetInputPluginTable 関数をエクスポートし、プラグインテーブルのポインタを返す必要があります。

このプラグインテーブル構造体をC#で表現し、GetInputPluginTable関数を表現できれば良さそうです。

C#でのNative AOTを使用したSharedライブラリ実装

C#でNative AOTを使用してプラグインを実装するには、以下の手順が必要です。

  1. プロジェクト設定

まず、.csprojファイルで Native AOT を有効にします。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <PublishAot>true</PublishAot> <!-- ここ -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>
  1. UnmanagedCallersOnly属性の使用

Native AOTでは、UnmanagedCallersOnly 属性を使用してC言語風の関数をエクスポートします。

[UnmanagedCallersOnly(EntryPoint = "GetInputPluginTable", 
                      CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
public static IntPtr GetInputPluginTable()
{
    return ~; // TODO: Impl
}
  1. Native AOTを使用したビルド

以下のようなコマンドを使用して、Sharedライブラリをpublishします。

dotnet publish /p:NativeLib=Shared --use-current-runtime

実際の動作例

前述したNative AOTを使用してサンプルプラグインを実装しました。

https://github.com/yamachu/play-aviutl-plugin-dotnet/

今回実装したサンプルプラグインは、画像を読み込んで1秒間で360度回転させる単純なものです。
SkiaSharpを使用して画像処理を行い、AviUtlが期待するBGR24フォーマットで出力します。

https://x.com/y_chu5/status/1942942142982389828

今後もPluginを作っていきたいなということで、今回のプロジェクトは2点工夫をしてみました。

  1. AviUtlPluginNet.Abstractions というパッケージと、PluginのInterfaceを作成し、最低限の実装で量産できるように
  2. CsWin32 パッケージを使用し、頑張らないWin32APIの型定義

突貫で作ったためinterfaceはまだまだ改善の余地ありですが、まずは動くものが出来て安心です。

まとめ

Native AOTを使用することで、C#でもネイティブレベルのパフォーマンスを持つAviUtlプラグインを開発できることが分かりました。
従来のC++開発よりも生産性が高く(個人の感想です)、豊富な.NETエコシステムを活用できるのが大きな利点です(macOSでもWindowsのHeader不要で開発できるのは最高でした)。

今回のサンプル実装では、画像処理にSkiaSharpを使用しましたが、同様にして他のNuGetパッケージも活用できます。
ただし、Native AOTでは一部制限があるため、事前に互換性を確認することが重要です。

AviUtl ExEdit2のみならず、より多くの開発者がC#でNative Library開発を始めるきっかけになれば幸いです。

Discussion