始めに
dotnet coreでAOTでネイティブシングルバイナリを実現するための一つの仕組みとして、.NET 7よりNativeAOT (元corert)というものが追加された
この中でリフレクション関連の処理を行うための補助設定ファイルとしてrd.xmlというファイルがあるが、それについて現時点でどうすれば良いかという具体的な文書が見当たらなかったので、ここに書いておく。
なお、この記事では.NET 7のものに関して記述していくので、将来的に何か変更があった場合は注意。
NativeAOTにおけるリフレクション
NativeAOTはコンパイル時にネイティブバイナリを作成する関係上、リフレクション関連の処理に大きな制限がかかる。
しかし、昨今のC#の状況をみると、リフレクション関連の処理を全てNGとするのは非現実的(enum関連とか)なため、API呼び出しやフロー解析を用いて、コンパイル時にメタ情報を静的に作成して使用することで、一定の範囲でリフレクションAPIを利用できるようにしている。
しかし、自動的な解析だけではどうしても限界が出てくるため、予めメタ情報を保持しておくものを指定しておくファイルとして、rd(Runtime Directive).xmlが使用される。
この辺りの、API呼び出し+フロー解析+補助設定ファイルというのはGraalVMでも似たようなことをしており、AOTでは結局同じような手法に行きつくという事だと思う。
rd.xmlの出自
rd.xml自体はNativeAOTのオリジナルではなく、.NET Nativeからのもの。
しかし、NativeAOTで使えるのはそのサブセットであり、全ての記法をサポートしているわけではない。
いつ書くべきか
Type.GetType()
直呼び出し程度ならば、コンパイルの段階で自動検出してくれるので、記述の必要は多くの場合は無い。
しかし、自動検出されなかった場合、ネイティブバイナリ実行時にMissingMetadataException
やTypeLoadException
が発生する。
例えば、McMaster.Extensions.CommandLineUtilsでは属性ベースでサブコマンドクラスを指定できるが、NativeAOTするとどこからも参照されてないクラスとしてサブコマンドクラスが刈り取られ、実行時に例外が発生する。
この時にrd.xmlを書くことを検討する。
意図通りに動作しない例
下記コードを実行すると、通常は"MyClass"という出力が得られるが、NativeAOTしたものを実行すると何も表示されなくなる。
foreach(var x in CollectMy()){
Console.WriteLine(x);
}
IEnumerable<string> CollectMy()
{
foreach(var item in typeof(Program).Assembly.GetTypes())
{
var attr = item.GetCustomAttributes(typeof(MyAttribute), true);
if(attr != null && attr.Length != 0)
{
yield return item.Name;
}
}
}
class MyAttribute: Attribute
{
}
[My]
class MyClass
{
}
書き方
-
Directives
を頂点として、その下にApplication
かLibrary
要素を追加する- 現時点ではどちらでも入れてOK
- その配下に
Assembly
要素を追加し、属性としてそのアセンブリ名(System.Reflection.AssemblyName.Name
)を指定 - 配下に
Type
要素を追加し、属性Name
に名前空間を含めたタイプ名と、属性Dynamic
に`Required All"を指定する- corertでは今の所
Dynamic
にはRequired All
以外の記述は認められていない - メソッド指定のみ必要な場合は、
Dynamic
属性は付けなくてもOK- どのような効用があるかは今の所不明なので、とりあえずつけるという感じで良いかもしれない
- ジェネリッククラスの場合、
TypeName`1[[Type1,AssemblyName]]
のような記述になる - インナークラスの場合、
OuterClass+InnerClass
のような記述になる
- corertでは今の所
- メソッドの指定をしたい場合は、
Type
配下にMethod
要素を追加する- ジェネリックメソッドの場合は、更に配下に
GenericArgument
を追加する
- ジェネリックメソッドの場合は、更に配下に
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<Assembly Name="My.Assembly.Name">
<Type Name="Full.Typename" Dynamic="Required All" />
<Type Name="Full.Typename2">
<Method Name="Method1"/>
<Method Name="Method2>
<GenericArgument Name="System.Int32"/>
</Method>
</Type>
</Assembly>
</Application>
</Directives>
より実践的な書き方を見たい場合は、よくあるパッケージのrd.xmlの記述を予め書いておこうという kant2002/RdXmlLibrary というプロジェクトがあるので、それを参考にしてもいいと思う。
プロジェクト設定
rd.xmlはItemGroupにRdXmlFile
を追加することで読み込んでくれる。
rd.xmlが不正なフォーマットだったり、クラス指定等が間違っていると、コンパイル時に例外として捕捉される。
終りに
簡単ではあるが、NativeAOTのrd.xmlについて書いた。
NativeAOTは、ネイティブバイナリ実行時にしか出ないエラーがあったり、何も言わずにSEGVで落ちる場合等があるので、癖が強く使いやすいとは言えないが、PublishSingleFileよりもコンパクトにまとまったり(1-10MB程度)、初回起動が非常に早かったり(ほぼCのネイティブバイナリと変わらない)するので、将来的にもっと使いやすいものになると良いと思う。