Native AOT Support
Uno Platform 6.6 introduces support for .NET Native AOT deployment across Android, iOS, Linux, macOS, and Windows.
Enabling Native AOT enables faster app startup and improves performance, typically at the cost of larger app sizes. Consider the Uno.Chefs sample app:
| Sample | Environment | Runtime | Publish Size | Startup Times (s) |
|---|---|---|---|---|
| Uno.Chefs | Android, .NET 10 | MonoVM | 93M | 0.895s |
| Uno.Chefs | Android, .NET 10 | NativeAOT | 109M (117% MonoVM) |
0.348s (39% MonoVM) |
| Uno.Chefs | iOS, .NET 10 | MonoVM | 138M | 0.940s |
| Uno.Chefs | iOS, .NET 10 | NativeAOT | 122M (88% MonoVM) |
0.742s (79% MonoVM) |
| Uno.Chefs | Linux, .NET 10 | CoreCLR | 541M | 0.87s |
| Uno.Chefs | Linux, .NET 10 | NativeAOT | 625M (118% CoreCLR) |
0.35s (40% CoreCLR) |
| Uno.Chefs | macOS, .NET 10 | CoreCLR | 547M | 1.347s |
| Uno.Chefs | macOS, .NET 10 | NativeAOT | 720M (141% CoreCLR) |
0.555s (41% CoreCLR) |
| Uno.Chefs | Windows, .NET 10 | CoreCLR | 725M | 1.605s |
| Uno.Chefs | Windows, .NET 10 | NativeAOT | 970M (139% CoreCLR) |
0.824s (51% CoreCLR) |
Note
App startup times are provided for comparison purposes. Actual startup times will vary depending on hardware.
Note
Native AOT on Android will emit an XA1040 warning.
Prerequisites
Please see the .NET SDK Prerequisites documentation.
Some platforms have additional prerequisites.
Publishing Android apps with Native AOT requires NDK r27 or later, in addition to the normal .NET for Android SDK requirements.
Publish Native AOT using the CLI
Publishing apps mirrors the .NET documentation,
and requires setting the $(PublishAot) MSBuild property within App.csproj to true:
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
However, simply setting $(PublishAot) will cause issues for iOS; see dotnet/sdk#21877. Consequently, if your app multi-targets multiple target frameworks and iOS is one of those multiple frameworks, then you need to either:
Set
$(PublishAot)for each target framework separately, as per Native AOT deployment on iOS and Mac Catalyst > Publish using Native AOT:<PropertyGroup> <PublishAot Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' ">true</PublishAot> <PublishAot Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst' ">true</PublishAot> <!-- repeat for any other platforms that Native AOT should be enabled on… --> </PropertyGroup>Exclude
$(PublishAot)for target frameworks which cause the error described in dotnet/sdk#21877. At the time of this writing, excluding onlybrowserwasmfrom Native AOT allowsdotnet publishto work reliably.<PropertyGroup> <PublishAot Condition=" $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'browserwasm' ">true</PublishAot> </PropertyGroup>
It is also recommended to set the $(IsAotCompatible) MSBuild property in projects, which enables additional trimmer warnings:
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
Producing the Native AOT package requires a dotnet publish invocation that provides Runtime Identifier (-r) option, and if the project is multi-targeted, then the Target Framework (-f) option is also required:
dotnet publish -f net10.0-ios -r ios-arm64 App.csproj
During testing, it is often desirable to change $(PublishAot) on a per-build basis, so that NativeAOT and non-NativeAOT packages can be produced without editing the .csproj file. Use an intermediate MSBuild property which sets $(PublishAot) in only App projects:
<PropertyGroup>
<PublishAot Condition=" '$(PublishAot)' == ''
And '$(TestPublishAot)' != ''
And $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'browserwasm' ">$(TestPublishAot)</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
This enables:
dotnet publish -f net10.0-ios -r ios-arm64 -p:TestPublishAot=true App.csproj
The intermediate MSBuild property is useful when you have an App.csproj which contains a project reference to a netstandard2.0 project, as using dotnet publish -p:PublishAot=true App.csproj … will generate build failures:
…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(120,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.
Uno Platform Feature Support
Uno Platform Features work with Native AOT, but some adaptations may be required.
Animations
Animations on Attached Properties may require reflection adaptations.
Binding expressions to Attached properties (Storyboard setters) may require reflection adaptations.
Styling
Attached Property Style binding may require reflection adaptations.
Data Binding
Attached Properties binding may require reflection adaptations.
Runtime Performance
Expando Binding and DynamicObject Binding likely will not work under Native AOT, as it involves dynamic.
Microsoft.Web.WebView2.Core.CoreWebView2
The CoreWebView2 type does not work on Linux or Windows when using Native AOT. It does work on macOS.
See unoplatform/uno#23142 and unoplatform/uno#23159 for more details.
Others
AttachedProperty Binding and AttachedProperty Styling may require reflection adaptations.
Limitations of Native AOT deployment
Please refer to the Limitations of Native AOT deployment documentation. In particular:
- No dynamic loading, for example,
Assembly.LoadFile.- No runtime code generation, for example,
System.Reflection.Emit.- Requires trimming, which has limitations.
- Implies compilation into a single file, which has known incompatibilities.
Some System.Reflection facilities may not work either, such as
Type.MakeGenericType()
and MethodInfo.MakeGenericMethod().
Many Uno Platform features such as XAML Binding expressions may rely upon System.Reflection, often in "fallback" code paths.
Native AOT supports System.Reflection, in that methods such as object.GetType() and Type.GetProperties() work.
However, Native AOT is a trimming environment, meaning that types and properties (and more!) may be removed as part of the build process.
Consequently, when a Uno Platform app is built for Native AOT and runs, it is possible that types and members that are required are not present, which often presents as UI bugs such as missing content. The easiest way to obtain actionable information about missing types and members is via log messages.
Log Everything
Broadly speaking, Uno Platform apps write diagnostic log messages via two separate mechanisms:
- via an
ILoggerinstance obtained from a.Log()extension method; see Logging. - via an
ILoggerinstance obtained via Dependency Injection, when using Uno.Extensions.
You need to see messages from both sources in order to track down, understand, and fix runtime errors. This may require changes to your App startup code: if you have an App.InitializeLogging() method, then:
- Ensure that it is called from your relevant
Main()or startup code, and - Ensure it emits output in Release configuration builds, at least during testing. Native AOT is only enabled in Release configuration builds.
If your app does not have an App.InitializeLogging() method, then add one and call it from your startup code. See unoplatform/uno.extensions#3008.
Messages will be written to your app's console output.
Reflection metadata
While Native AOT supports System.Reflection, there are a number of constraints when using Reflection, and XAML often uses Reflection in "fallback" code paths. Consequently, apps may not work properly when running in a Native AOT environment.
Native AOT compiles managed code into a single native binary. In order to support System.Reflection, a Reflection metadata "database" is generated at build time. To gain insight into what is retained from the trimming process and what is kept in Reflection metadata, add the following options to the dotnet publish command:
-p:TrimmerSingleWarn=false -p:_ExtraTrimmerArgs=--verbose: Show all warning messages from the trimmer during the build. Often times you will see "Assembly X has trimmer warnings" with no specifics; these options show all the warnings.-p:IlcGenerateMetadataLog=true: Emit$(MSBuildProjectName).metadata.csvwithin$(IntermediateOutputPath). This allows viewing the "reflection metadata" that will be accessible to the app at runtime.-p:EmitCompilerGeneratedFiles=true "-p:CompilerGeneratedFilesOutputPath=PATH": Emit C# source generator output into PATH. It is frequently useful to separately review and search generated output.-p:IlcGenerateMstatFile=true: Emit$(MSBuildProjectName).mstatwithin$(IntermediateOutputPath). This is an assembly that references all types, fields, non-inlined methods, etc., which make up the resulting native binary after trimming.The
.mstatcan be disassembled with ildasm.The output of monodis may be easier to understand, such as
monodis --typeref App.mstatto view all referenced types, andmonodis --memberref App.mstatto view all referenced members.Note
Just because the
.mstatfile references a type or property does not mean that the member is available via Reflection. Only information within the.metadata.csvfile generated by-p:IlcGenerateMetadataLog=trueis accessible via Reflection.
For example, to build an App project which prints all trimmer messages, retains all generated C# Source generator output, and dumps Reflection metadata:
dotnet publish -f net10.0-ios -r ios-arm64 -p:TestPublishAot=true -bl App.csproj `
-p:TrimmerSingleWarn=false -p:_ExtraTrimmerArgs=--verbose -p:IlcGenerateMetadataLog=true -p:IlcGenerateMstatFile=true `
-p:EmitCompilerGeneratedFiles=true "-p:CompilerGeneratedFilesOutputPath=$(Join-Path -Path $pwd -ChildPath ../_gen)"
There are three ways to add types and members to Reflection metadata:
- The
DynamicallyAccessedMembersAttributecustom attribute - The
DynamicDependencyAttributecustom attribute. - With Linker Descriptor XML files via the
@(TrimmerRootDescriptor)item group and other locations.
Adapt an app to Native AOT deployment
An app may require changes in order to run under Native AOT. (It is also possible that Native AOT cannot be used at all, depending on the app dependencies.)
See the Fixing trim warnings Microsoft documentation.
Additional common problems and their workarounds follow.
Use of Type.GetType(string)
Type.GetType(string) and related overloads only work reliably when given a string constant:
var t = Type.GetType("System.Int32, System.Runtime");
Any other use may mean that the specified type isn't available at runtime, resulting in an exception.
If Type.GetType(string) must be used, always use a string constant containing an assembly-qualified name.
JSON serialization
Many apps (and templates) rely on reflection-based serialization behavior (for example, System.Text.Json default runtime contract discovery). Under Native AOT, this can fail because required members and metadata can be removed.
Prefer System.Text.Json source generators for DTOs and commonly serialized types.
When using Uno.Extensions.Serialization, use the .AddJsonTypeInfo() extension method to register the Json Source Generator output:
using System.Text.Json.Serialization;
public record Person(string name, int age, double height, double weight);
[JsonSerializable(typeof(Person))]
public partial class PersonJsonContext : JsonSerializerContext
{
}
partial class App
{
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
var appBuilder = this.CreateBuilder(args)
.Configure(host => {
host
.UseSerialization(services =>
{
services
.AddJsonTypeInfo(PersonJsonContext.Default);
});
});
…
}
}
Properties
Properties may be referenced via XAML Binding Expressions, and if not preserved by Native AOT then using them will result in messages such as:
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ListenButtonContent] property getter does not exist on type [Uno.Gallery.Views.Samples.ClipboardSamplePageViewModel]
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [Message] property getter does not exist on type [Uno.Gallery.Views.Samples.ClipboardSamplePageViewModel]
There are two general ways to fix this.
The easiest way is to add Microsoft.UI.Xaml.Data.BindableAttribute to the declaring type:
[Microsoft.UI.Xaml.Data.Bindable]
partial class ClipboardSamplePageViewModel {
}
However, this will not work if the type has init-only properties and also WinRT support is required, as this will hit microsoft/microsoft-ui-xaml#8723.
If WinRT support is required, then [DynamicDependency] must be used:
partial class ClipboardSamplePageViewModel {
[DynamicDependency(nameof(ListenButtonContent))]
[DynamicDependency(nameof(Message))]
public ClipboardSamplePageViewModel() {…}
}
XAML Attached Properties
XAML binding can involve Reflection, which can include use of attached properties. If Reflection Metadata doesn't include a required attached property, then the app log will contain messages such as:
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ShowMeTheXAML:XamlDisplayExtensions.Header] property getter does not exist on type [ShowMeTheXAML.XamlDisplay]
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ShowMeTheXAML:XamlDisplayExtensions.Description] property getter does not exist on type [ShowMeTheXAML.XamlDisplay]
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ShowMeTheXAML:XamlDisplayExtensions.Options] property getter does not exist on type [ShowMeTheXAML.XamlDisplay]
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ShowMeTheXAML:XamlDisplayExtensions.PrettyXaml] property getter does not exist on type [ShowMeTheXAML.XamlDisplay]
fail: Uno.UI.DataBinding.BindingPropertyHelper[0]
The [ShowMeTheXAML:XamlDisplayExtensions.ShowXaml] property getter does not exist on type [ShowMeTheXAML.XamlDisplay]
Fix this by using [DynamicDependency] on the attached property so that the associated Get and Set methods are preserved:
diff --git a/Uno.Gallery/Extensions/XamlDisplayExtensions.cs b/Uno.Gallery/Extensions/XamlDisplayExtensions.cs
index 764886bb..f146a16f 100644
--- a/Uno.Gallery/Extensions/XamlDisplayExtensions.cs
+++ b/Uno.Gallery/Extensions/XamlDisplayExtensions.cs
@@ -1,6 +1,7 @@
#pragma warning disable
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
@@ -28,7 +29,12 @@ namespace ShowMeTheXAML
#region Property: Header
- public static DependencyProperty HeaderProperty { get; } = DependencyProperty.RegisterAttached(
+ public static DependencyProperty HeaderProperty
+ {
+ [DynamicDependency(nameof(GetHeader))]
+ [DynamicDependency(nameof(SetHeader))]
+ get;
+ } = DependencyProperty.RegisterAttached(
"Header",
typeof(string),
typeof(XamlDisplayExtensions),
All use of DependencyProperty.RegisterAttached() is potentially suspect and should be reviewed.