💡

Native AOT トラブル対策 Tips 集

2025/03/22に公開

はじめに

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 にします。

NativeMethods.json
{
	"$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)

参考リンク

主な改訂履歴

  • 2025/03/22 初版。
  • 2025/03/27 「AOT でよく使う設定」を新規作成。

Discussion