このページには自動翻訳されたテキストを含めることができます。
.NET で Native AOT アプリケーションを開発する方法
.NET 8 では、Native AOT とも呼ばれる Ahead-of-time コンパイルが包括的にサポートされます。この種のアプリケーションは通常、マネージド ソリューションよりも起動が速く、メモリ消費も少なくなります。
.NET AOT の公開では、トリミングされた自己完結型アプリケーションが生成されます。このようなアプリケーションは次の特徴があります。
- .NET ランタイムがインストールされていないマシンで実行できます。
- Windows x64 など、特定のランタイム環境を対象にできます。
- 未使用コードを含みません。

私たちは PDF 処理用の Docotic.Pdf ライブラリを開発しています。AOT 互換性は、2024 年に最も多く寄せられた機能要求の 1 つです。この記事では、AOT 互換性を実現するまでの取り組みを説明します。.NET プロジェクトで AOT の問題を見つけて修正する方法を学べます。
.NET AOT 公開の基本
Native AOT アプリケーションを公開するには、プロジェクト ファイルに次のプロパティを追加します。
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
前提条件 のインストールも必要になる場合があります。その後、Visual Studio からアプリを公開できます。または、dotnet publish コマンドを使ってコマンドラインからプロジェクトを公開できます。
dotnet publish -r linux-x64 -c Release
.NET ライブラリの AOT 互換性
Native AOT の公開は .NET 7 と .NET 8 で利用できます。.NET 7 ではコンソール アプリケーションのみ公開できます。.NET 8 では、ASP.NET Core アプリケーションも公開できます。
ただし、Docotic.Pdf のコア ライブラリは .NET Standard 2.0 を対象にしています。AOT に適した形にできるでしょうか。もちろん可能です。
.NET Standard、.NET 5、.NET 6 を対象とする .NET ライブラリでも、AOT 互換性の問題を引き続き検出して修正できます。その後、Native AOT アプリケーションがそのライブラリを利用できます。
AOT の問題を見つける
AOT 互換性に関する最初のサポート依頼は、たいてい次のような内容です。
Docotic.Pdf を WebAssembly 用にコンパイルして使うことを検討しています。AOT にコンパイルすると trim 警告が出ます。
warning IL2104: Assembly 'BitMiracle.Docotic.Pdf' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries
あまり有益ではありません。問題を再現し、これらの trim 警告の詳細を取得しましょう。
公開時の警告を取得する
AOT の .NET 問題を見つける最も柔軟な方法は、テスト アプリケーションを公開することです。必要な手順は次のとおりです。
PublishAot = trueを設定した .NET 8 のコンソール プロジェクトを作成する- プロジェクトへの参照を追加し、その型をいくつか使用する
Docotic.Pdf で使用している csproj の設定は次のとおりです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Docotic\Docotic.csproj" />
</ItemGroup>
</Project>
TrimmerSingleWarn = false により、警告の詳細情報を取得できます。そうでない場合は、「Assembly 'X' produced trim warnings」というメッセージしか得られません。
そして、Program.cs の C# コードは次のとおりです。
using BitMiracle.Docotic.Pdf;
using var pdf = new PdfDocument();
チェック対象のアセンブリの読み込みを強制するために、いくつかのコードを追加してください。公開時警告を得るために、プロジェクトのすべての機能を網羅する必要はありません。単一の型を使えば十分です。ただし、実行時エラーを捕捉する には、より多くのコードを書く必要があります。
このプロジェクトに対する dotnet publish -r win-x64 -c Release コマンドは、trim 警告と AOT 警告を返しました。以下は一覧の短縮版です。
AOT analysis warning IL3050: Org.BouncyCastle.Utilities.Enums.GetEnumValues(Type): Using member 'System.Enum.GetValues(Type)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling.
Trim analysis warning IL2026: LogProviders.Log4NetLogProvider.Log4NetLogger.GetCreateLoggingEvent(ParameterExpression,UnaryExpression,ParameterExpression,UnaryExpression,Type): Using member 'System.Linq.Expressions.Expression.Property(Expression,String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code.
Trim analysis warning IL2057: LogProviders.LogProviderBase.FindType(String,String[]): Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String)'.
Trim analysis warning IL2070: LogProviders.NLogLogProvider.NLogLogger.GetIsEnabledDelegate(Type,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String)'. The parameter 'loggerType' of method 'LogProviders.NLogLogProvider.NLogLogger.GetIsEnabledDelegate(Type,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
Trim analysis warning IL2075: LogProviders.Log4NetLogProvider.GetOpenNdcMethod(): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String)'. The return value of method 'LogProviders.LogProviderBase.FindType(String,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
根本原因が見つかりました。問題を修正する前に、AOT 互換性の問題を見つける別の方法を確認しましょう。
AOT 互換性アナライザーを使う
Microsoft は、.NET 7 または .NET 8 プロジェクトで AOT の問題を見つけるための Roslyn アナライザーを提供しています。これらは、コードを書いている段階で AOT の問題をハイライトできます。ただし、テスト アプリケーションを公開した場合のほうが、より多くの問題を検出できます。
静的解析を使うには、.csproj ファイルに次のプロパティを追加します。
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
このような解析を .NET Standard 2.0 プロジェクトで使うには、.NET 7 または .NET 8 フレームワークを対象にする必要があります。プロジェクト構成は次のようになります。
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
この設定を大規模なコア プロジェクトに適用したところ、1036 件のビルド エラーが発生しました。

それらはすべて AOT 互換性に関係していたのでしょうか。IsAotCompatible プロパティを外して再ビルドしてみました。それでも 1036 件のエラーが出ました。
すべてのエラーは、.NET Standard 2.0 と比べた .NET 8 の改善に関連していました。私たちの場合、問題の大半は nullable 参照型のサポート改善に関するものでした。また、新しい ArgumentOutOfRangeException.ThrowIfLessThan と ArgumentNullException.ThrowIfNull ヘルパー メソッドに関する CA1500 と CA1512 のエラーも多数ありました。
すべてのエラーを修正してから IsAotCompatible プロパティを有効にしました。プロジェクトのビルドは AOT エラーなしで完了しました。静的解析では、公開プロセス中に見つかった警告は検出されませんでした 🙁
確認のため、次のテスト コードをコードベースに追加しました。
Type t = typeof(int);
t = typeof(List<>).MakeGenericType(t);
Console.WriteLine(Activator.CreateInstance(t));
string s = Console.ReadLine() ?? string.Empty;
Type? type = Type.GetType(s);
if (type != null)
{
foreach (var m in type.GetMethods())
Console.WriteLine(m.Name);
}
この C# コードは、.NET AOT と trimming と 100% 互換性がありません。そして静的解析は次のエラーを検出しました。
error IL3050: Using member 'System.Type.MakeGenericType(params Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.
error IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String)'. It's not possible to guarantee the availability of the target type.
つまり、Roslyn アナライザーは機能し、一部の問題は見つけられます。ただし、私たちのケースでは何も見つかりませんでした。
実行時エラーを捕捉する
ライブラリを native AOT と互換にする方法 という記事には、次の原則が記されています。
AOT 用に公開したときに警告が出ないアプリケーションは、AOT なしの場合と同じように動作します。
実際には、AOT 後のバージョンが異なる動作をする場合もあります。私たちは AOT と trim の警告をすべて修正しました。すべての自動テストは通過しました。テスト アプリケーションは PDF ドキュメントを正しく処理していました。しかし、同じアプリケーションの AOT 版では誤ったドキュメントが生成されました。

AOT の公開によって、必要なコードの一部が誤って削除されていました。私たちは Optimization removes necessary code の issue を作成しました。.NET チームはすぐにこれを確認して修正しました。そして、修正が利用可能になるまでの間、C# コードに回避策を追加しました。
この話の教訓は、公開時の警告や静的解析だけでは不十分な場合があるということです。テスト アプリケーションでプロジェクトの主要機能を使い、デプロイ後にテスト アプリケーションを実行してください。
Docotic.Pdf のテスト アプリケーションでは、PDF 圧縮、テキスト抽出、その他の主要機能を使います。私たちは次のように使用しています。
AotCompatibility.exe compress "C:\compressed.pdf" "C:\input.pdf" "optional-password"
公開済みアプリケーションの手動テストは便利ではありません。プロセスを自動化する価値があります。
AOT の問題を自動的に検出する
Docotic.Pdf の開発は 自動テスト に基づいています。すべての機能やバグに対してテストがあります。.NET AOT 互換性も例外ではありません。
Native AOT アプリケーションのテストを自動化する方法は 2 つあります。
AOT アプリケーションの外側にテストを配置する
ここでは通常の NUnit/xUnit/MSTest プロジェクトを使います。このプロジェクトをビルドする前に、AOT テスト アプリケーションを公開します。テストは Process.Start メソッドを使って公開済みアプリを実行します。
主な利点は、テスト基盤を AOT アプリから分離できることです。ユニット テスト フレームワークの機能をフルに利用できます。テスト フレームワーク内の AOT 互換性の問題は関係ありません。既存のテスト ケースを再利用しやすいことも利点です。
テスト プロジェクトのサンプル構成 は GitHub で利用できます。Docotic.Tests プロジェクトには、マネージド版と AOT 版の両方に対する自動テストが含まれます。AotCompatibility は AOT 公開用のテスト アプリケーションです。
AotCompatibility.csproj は、Release 構成の post-build イベントでテスト アプリケーションを公開します。'$(PublishProtocol)'=='' 条件により、Visual Studio の「Publish」ウィザードを使う場合はこの手順が無効になります。
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release' And '$(PublishProtocol)'==''">
<Exec Command=""$(SolutionDir)Scripts\publish.bat"" />
</Target>
publish.bat スクリプトは単純です。
@echo off
SET AOT_TEST_DIR=%~dp0\..\AotCompatibility
dotnet publish "%TRIM_TEST_DIR%\AotCompatibility.csproj" --no-build /warnaserror /p:PublishProfile="%AOT_TEST_DIR%\Properties\PublishProfiles\win-x64-installer.pubxml"
ここでは --no-build 引数を使っている点に注意してください。post-build イベントの前にプロジェクトはすでにビルドされています。再度ビルドする必要はありません。
NativeAot.cs のテストは、公開済みの AotCompatibility.exe をさまざまな引数で実行します。Docotic.Tests と AotCompatibility プロジェクトは、同じテスト対象コードを共有しています。これにより、コードを重複させずにマネージド版と AOT 版を検証できます。
AOT アプリケーション内にテストを配置する
別の方法として、AOT テスト アプリケーション内にテストを書くこともできます。2024 年 7 月時点では、MS Test の早期プレビューのみが使用できます。詳細は Testing your native AOT Applications 記事を参照してください。
主な問題は、テスト フレームワーク内の AOT の問題によってテストの信頼性が低下することです。また、利用できるテスト フレームワーク機能も限られています。そのため、テスト基盤は AOT アプリケーションの外側に置くことを推奨します。
AOT と trim の警告を修正する
公開時の警告を抑制しないでください。通常、警告の抑制は誤った解決策です。AOT や trim の警告を抑制すると、プロジェクトは AOT 互換であると主張することになります。しかし、警告の根本原因は残ったままです。抑制した警告は、最終的にアプリケーションの実行時障害につながる可能性があります。
各警告に関連するコードを削除するか、書き換えてください。サードパーティ コンポーネントの更新も役立つ場合があります。
Docotic.Pdf で AOT と trim の警告をどのように修正したかを見ていきましょう。すでに 公開時の警告を取得する セクションでいくつか確認しています。
LibLog の trim 警告
ログ記録には LibLog ライブラリを使っていました。LibLog はリフレクションに依存して、一般的なロギング フレームワークを検出して利用します。設計上、AOT と互換性がありません。
現在、.NET のログ記録標準は Microsoft.Extensions.Logging パッケージです。そして、LibLog の開発は現在凍結されています。
そのため、私たちはコードから LibLog を完全に削除しました。代わりに Docotic.Pdf.Logging アドオンをリリースしました。これは Microsoft.Extensions.Logging.Abstractions のインターフェイスに依存します。これによりコア ライブラリのサイズが小さくなり、関連する trim 警告もすべて解消されました。

Docotic.Layout アドオン内のデバッグコード
layout アドオン では、内部デバッグのためにリフレクションを使っています。これにより trim analysis 警告 IL2075 が発生していました。
該当コードはコードベースに存在しますが、クライアントは公開 API 経由では使用できません。解決策は、Release 構成からそのコードを除外することです。現在は Debug 構成でのみ使用されています。
BouncyCastle による AOT 警告 IL3050
BouncyCastle ライブラリは、PDF ドキュメントの署名で役立っています。次の警告はそのコードに由来します。
Org.BouncyCastle.Utilities.Enums.GetEnumValues(Type): Using member 'System.Enum.GetValues(Type)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. It might not be possible to create an array of the enum type at runtime. Use the GetValues<TEnum> overload or the GetValuesAsUnderlyingType method instead.
BouncyCastle は、AOT 互換ではない Enum.GetValues(Type) メソッドを使っています。
最も単純な解決策は、代わりに Enum.GetValues<T>() オーバーロードを使うことです。最新の BouncyCastle ではすでにこれが使われています。残念ながら、このオーバーロードは .NET 5 以降でしか利用できません。これは .NET Standard 2.0 では使えません。
さらに掘り下げて BouncyCastle のコードを分析しました。enum 定数の難読化を防ぐために使われていることがわかりました。代わりに、対応する enum については [System.Reflection.Obfuscation(Exclude = true)] 属性を使って難読化を無効にしました。そして、もはや必要でない Enum.GetValues(Type) の使用箇所を削除しました。
難読化されたビルドでの奇妙な問題
本番ビルドの保護には難読化を使っています。驚くことに、難読化されたビルドの .NET AOT デプロイでは、同様の警告が返されました。
<unknown>:0: error: symbol '__GenericLookupFromType_BitMiracle_Docotic_Pdf___4<System___Canon__System___Canon__System___Canon__Int32>_TypeHandle___System___Canon' is already defined
このようなエラーには、対応する IL30## や IL2### のコードがありませんでした。関連コードをどう特定するかは不明でした。
エラーは難読化版でのみ発生しました。難読化ツールの不具合かもしれません。難読化ツールを更新しましたが、エラーは残っていました。
問題範囲を絞り込む必要がありました。二分探索に基づく次の手順を使いました。
- 名前空間の半分について難読化を無効にする。エラーが発生するか確認する。エラーの原因となる名前空間が見つかるまで繰り返す。
- 手順 1 で見つけた名前空間内の型の半分について難読化を無効にする。エラーが発生するか確認する。エラーの原因となる型が見つかるまで繰り返す。
- 手順 2 で見つけた型内のメンバーの半分について難読化を無効にする。エラーが発生するか確認する。エラーの原因となるメンバーが見つかるまで繰り返す。
- 手順 3 で見つけたメンバーのコードを確認する。通常でない部分をコメントアウトし、エラーが発生するか確認する。
結果的に、手順 1 で詰まりました 🙂 すべての名前空間の難読化を無効にしても、エラーは発生し続けました。
生成されたアセンブリを ILSpy で分析しました。予想外のコンパイラ生成型がいくつか見つかりました。ILSpy には、それらの使用箇所が次のような C# コードとして表示されていました。
interface X
{
void f();
}
X obj = ..;
if (obj.f != null)
..
この奇妙なコードは、旧式 C コードから C# への移行 から来ていました。if (obj.f != null) 条件は完全に冗長です。このような条件を削除すると、エラーは解消しました。
GetCallingAssembly を使わない
別の runtime issue を捕捉しました。LicenseManager.AddLicenseData(string) メソッドの呼び出しは、AOT 公開後に次のエラーで失敗しました。
Assembly.GetCallingAssembly() throws System.PlatformNotSupportedException: Operation is not supported on this platform.
Assembly.GetCallingAssembly メソッドは、.NET 9 で Native AOT 用に実装される予定 です。修正が利用可能になるまで、負の影響を最小化するようにコードをリファクタリングしました。現在は、Application License の検証にのみ呼び出し元アセンブリ属性を使用しています。その他のライセンス種別は AOT と互換です。
特別な属性でコードをマークする
幸い、コアの Docotic.Pdf ライブラリとほとんどのアドオンで、AOT と trim の警告をすべて修正できました。ただし、AOT 非互換コードをすべて書き換えられるとは限りません。
.NET には、そのような状況向けの特別な属性があります。既知の AOT の問題をクライアントに伝えるために、API に属性を付けます。API の利用者は、そのマークされたメソッドを呼び出すと警告を受け取ります。
AOT 非互換の .NET コードをマークするための重要な属性は次のとおりです。
RequiresDynamicCodeRequiresUnreferencedCodeDynamicallyAccessedMembers
これらの属性の詳細は、公式の Introduction to AOT warnings と Prepare .NET libraries for trimming の記事で確認できます。
結論
Native AOT のデプロイは、.NET の世界で大きな前進です。通常の C# コードを書くだけで、ネイティブ アプリケーションやライブラリを得られます。このようなアプリケーションは通常より高速で、.NET ランタイムをインストールしなくても実行できます。C、Rust、その他の非 .NET プログラミング言語から公開済み DLL を使うこともできます。
AOT 互換性の問題を見つけるには、.NET 8 のテスト アプリケーションを公開してください。Roslyn アナライザーもありますが、検出できる問題は少なめです。
現在、コアの Docotic.Pdf ライブラリは AOT と trimming に対応しています。次のアドオンも互換です。
- Conformance アドオン
- Layout アドオン
- Gdi アドオン
- Logging アドオン
HTML to PDF アドオンにはまだ trim 警告が残っています。これが Native AOT ロードマップにおける次の目標です。
Native AOT や PDF 処理についての質問は、お問い合わせください。