Native AOT トラブル対策 Tips 集
はじめに
C# アプリを Native AOT(以降「AOT」)でビルドする際のあれやこれやです。基本的にはリフレクションを避けてソース生成(ソースジェネレーター)を活用する方向になります。
AOT についてはまだ情報が少なく、知見をお持ちの方はコメントいただけると幸いです。
AOT ビルドの流れについてはこちらの記事をどうぞ。
サンプルプログラム
サンプルプログラムは GitHub に上げてあります。
全般 Tips
AOT でよく使う設定
プロジェクトファイル(.csproj)直接編集により AOT の設定を変更することができます。
<PropertyGroup>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<OptimizationPreference>speed</OptimizationPreference>
<TrimMode>partial</TrimMode>
</PropertyGroup>
PublishAot
<PublishAot>true</PublishAot>
で AOT を有効にします。プロジェクトプロパティの「ネイティブ AOT の公開」と同じです。
IsAotCompatible
<IsAotCompatible>true</IsAotCompatible>
でプロジェクトを AOT 互換として構成します。
これにより IsTrimmable も true になり、ライブラリのトリミング警告が表示されるようになります。
互換構成せずにトリミング警告だけ表示するには、代わりに <EnableTrimAnalyzer>true</EnableTrimAnalyzer>
を使用します。
SuppressTrimAnalysisWarnings
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
でトリミングによって破損する可能性があるパターンで警告を表示します。
OptimizationPreference
AOT デプロイの最適化方法を指定します。
<OptimizationPreference>speed</OptimizationPreference>
で速度最適化、<OptimizationPreference>size</OptimizationPreference>
でサイズ最適化になります。
未指定の場合は混合アプローチで、速度とサイズの間くらいです。
TrimMode
<TrimMode>full</TrimMode>
で最大限トリミングし、ファイルサイズが小さくなります。注意しないと事故が起こりやすくなると言えるかもしれません。
<TrimMode>partial</TrimMode>
はトリミングをオプトインしたアセンブリのみをトリミングするので、ファイルサイズが少し大きくなります。
COM を使う
COM を AOT で使用するには、こちらの記事をどうぞ。
CsWin32 を使う
Windows API を P/Invoke するための CsWin32 を AOT で使用するには、allowMarshaling を false にします。
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true,
"allowMarshaling": false
}
CsWin32 を分離プロジェクトで使う方法についてはこちらの記事をどうぞ。
DllImport
DllImport
属性は AOT では使えないので、代わりに LibraryImport
属性を使います。アンセーフコードの使用許可が必要です。
public static partial class extLib
{
[LibraryImport("myDll")]
public static partial Int32 SomeFunc(Int32 arg);
}
Int32 result = extLib.SomeFunc(99);
DllImport
より LibraryImport
のほうがマーシャリングが強化されているようです。
JSON の読み書き
AOT で JSON (System.Text.Json) を使おうとすると以下のような警告が出ます。
IL2026 Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute'~
IL3050 Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)' which has 'RequiresDynamicCodeAttribute'~
ソース生成により JSON を使えるようになります。
JsonSerializerContext の派生クラスを作り、JsonSerializable
属性で JSON 化したいクラスを指定します。
[JsonSerializable(typeof(AddressBook))]
internal partial class MyJsonSerializerContext : JsonSerializerContext
{
}
シリアライズ・デシリアライズ時に MyJsonSerializerContext を指定します。
String json = JsonSerializer.Serialize(addressBook, MyJsonSerializerContext.Default.AddressBook);
AddressBook addressBook2 = JsonSerializer.Deserialize(json, MyJsonSerializerContext.Default.AddressBook) ?? throw new Exception("デシリアライズ失敗");
カスタムウィンドウプロシージャー
Windows API の SetWindowSubclass() によってウィンドウプロシージャーをカスタム化するのを AOT でやるには、こちらの記事の「AOT の場合」章をどうぞ。
WinUI 3 Tips
ComboBox を使うと落ちる
こちらをご覧ください。
ComboBox の DisplayMemberPath が使えない
ComboBox で DisplayMemberPath を設定しても該当のプロパティーは表示されません。
GeneratedBindableCustomProperty を設定することで表示されるようになります。
<ComboBox ItemsSource="{x:Bind ViewModel.TestList}" DisplayMemberPath="Name" />
[GeneratedBindableCustomProperty([nameof(Name)], [typeof(String)])]
internal partial record Person(String Name, Int32 Age);
public List<Person> TestList
{
get;
} =
[
new ("太郎", 20),
];
FileSavePicker.FileTypeChoices で例外
fileSavePicker.FileTypeChoices.Add(string1, [string2]);
のように FileTypeChoices にコレクション式で追加しようとすると、AOT では例外が発生します。
コレクション式を使わなければ大丈夫です。
fileSavePicker.FileTypeChoices.Add(string1, new[] { string2 });
ItemsSource をバインドすると落ちる
ComboBox や ItemsView などの ItemsSource を AOT で使用すると実行時に落ちたりエラーになったりします。
CsWinRT パッケージをプロジェクトにインストールすることで使用できるようになります。
AOT を使うなら CsWinRT は入れておく方が良いように思います(本来は依存で入っているような気もするのですが、Windows App SDK 1.7 現在はうまくいっていないようです)。
MVVM バインド
動的バインディングの Binding
はそのままでは動作しません。以下のような警告が出ます。
WMC1510 Ensure the property path is trimming and AOT compatible by making use of 'Compiled Bindings (x:bind)'~
解決策① x:Bind を使う
Binding
の代わりに静的バインディングの x:Bind
を使います。
<TextBlock Text="{x:Bind ViewModel.TestInt32, Mode=OneWay}" />
解決策② GeneratedBindableCustomProperty 属性を使う
何らかの事情で x:Bind
が使えない場合、頑張れば Binding
も使えます。
XAML 側に x:DataType
を使います。
<Page
x:Class="TestAotTips.Views.MainWindows.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:v="using:TestAotTips.ViewModels.MainWindows"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:DataType="v:MainPageViewModel"
mc:Ignorable="d">
<TextBlock Text="{Binding TestInt32}" />
</Page>
バインドされる ViewModel のクラスに GeneratedBindableCustomProperty
属性を付けます。
[GeneratedBindableCustomProperty]
public partial class MainPageViewModel : ObservableRecipient
{
}
確認環境
項目 | 環境 |
---|---|
OS | Windows 11 Pro 23H2 |
Visual Studio | 2022 17.13.4 |
.NET | 9.0 |
Template Studio for WinUI | 5.5 |
WinUIEx | 2.5.1 |
Windows App SDK | 1.7.250310001 (1.7.0) |
参考リンク
- AOT Support?
- AOT デプロイを最適化する
- CsWin32
- CsWin32 を別プロジェクトに分離する
- CsWinRT
- Native AOT で COM を使ってみた
- .NET 9 の
LibraryImport
自動マーシャリング機能を使いこなすうえでの注意点 - .NET trimming and AOT support in C#/WinRT
- SetWindowSubclass によるウィンドウプロシージャーのカスタム
- System.Text.Json でソース生成を使用する方法
- Windows.Storage.Pickers.FileSavePicker.FileTypeChoices.Add throw System.InvalidCastException: 'Specified cast is not valid.' with PublishAOT=true
- WinUI 3 で Native AOT が簡単になっていた
- サンプルプログラム
- トリミングのオプション
- トリミング用に .NET ライブラリを準備する
- プラットフォーム呼び出し用のソース生成
主な改訂履歴
- 2025/03/22 初版。
- 2025/03/27 「AOT でよく使う設定」を新規作成。
Discussion