I: Background
1. Storytelling
NET AOT programming, it is inevitable that you will struggle with the issue of generic, serialization, and reflection and repeated attempts to rectify the problem, this article we will talk about the relevant processing solutions.
II: Common problem solving
1. Generalized issues
Those of you who have studied generalization should know that from theOpen type
pastoralType of closure
Often there will be a separate MethodTable, and share EEClass, for the value type of the generic equivalent of a different individual, if the AOT Compiler process does not produce such individual information separately, naturally, at runtime will report an error, so that may be a bit confused, to give a simple example.
internal class Program
{
static void Main(string[] args)
{
var type = (());
try
{
var mylist = typeof(List<>).MakeGenericType(type);
var instance = (mylist);
int count = (int)("Count").GetValue(instance);
(count);
}
catch (Exception ex)
{
(());
();
}
();
}
}
public class Location
{
}
From the above diagram it looks like an exception is thrown directly, mainly due to theLocation
What to do when you get kicked out of the dependency graph? Obviously it's possible to justnew List<Location>
to the dependency graph, but direct new in code is a very intrusive operation, so how to make it less intrusive? Naturally, this is done with the help of AOT's unique rd (Runtime Directives) xml mechanism, which can be found here:/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/
The rd mechanism is very powerful, roughly as follows:
(1) You can specify assemblies, types, and methods to be used as the root node of the compilation graph, which is partially integrated with ILLink.
2) You can manually initialize the generic, or use a method under the generic as the root node.
3) Pinvoke support for Marshal and Delegate.
In the ilc source code compilationRoots are used to host the root nodes past rd, so you can find out.
foreach (var rdXmlFilePath in Get(_command.RdXmlFilePaths))
{
(new RdXmlRootProvider(typeSystemContext, rdXmlFilePath));
}
With this knowledge it is possible to instantiate theList<Location>
up, refer to the following:
<?xml version="1.0" encoding="utf-8" ?>
<Directives xmlns="/netfx/2013/01/metadata">
<Application>
<Assembly Name="Example_21_1">
<Type Name="`1[[Example_21_1.Location,Example_21_1]]" Dynamic="Required All" />
</Assembly>
</Application>
</Directives>
Also just do a little introduction in csproj.
<Project Sdk="">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<RdXmlFile Include="" />
</ItemGroup>
</Project>
After the execution of the following, it is important to note thatDynamic="Required All"
It can putList<Location>
All methods and fields under are injected into the dependency graph, such as the Count property method in the following figure.
2. Serialization issues
Serialization will involve a lot of reflection, and reflection and the need to get a lot of metadata support, so a lot of third-party Json serialization can not be achieved, but the official Json serialization with the help of SourceGenerator will be the original dll in the metadata migrated to the hard-coded, which is disguised as the implementation of the AOT of the Json serialization, the reference code is as follows:
namespace Example_21_1
{
internal class Program
{
static void Main(string[] args)
{
var person = new Person()
{
Name = "john",
Age = 30,
BirthDate = new DateTime(1993, 5, 15),
Gender = "Mail"
};
var jsonString = (person,
);
(jsonString);
();
}
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Person))]
internal partial class SourceGenerationContext : JsonSerializerContext { }
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public string Gender { get; set; }
}
When debugging with VS, you'll notice an extra file and use the
properties
The array holds the metadata for Person, as shown in the screenshot below:
3. Reflections
Reflection is actually a more tangled problem, simple reflection of the AOT compiler can easily speculate, but slightly need to be context-sensitive can not get it, after all, involves a large amount of arithmetic needed to context-sensitive, and the current AOT compilation itself is relatively slow, so there is no support for the time being, I believe that subsequent versions of the bar will be improved, followed by an example of a demonstration of the next.
internal class Program
{
static void Main(string[] args)
{
Invoke(typeof(Person));
();
}
static void Invoke(Type type)
{
var props = ();
foreach (var prop in props)
{
(prop);
}
}
}
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public string Gender { get; set; }
}
This code will not extract the attributes in the AOT because theInvoke(typeof(Person));
cap (a poem) There's a
Type type
parameter, and while we can tell the intent of this code with the naked eye, ilc's depth-first it doesn't know what you need in Person, so it just keeps Person itself, if you want to observe it straight on:
- commander-in-chief (military)
<PublishAot>true</PublishAot>
adapt (a story to another medium)<PublishTrimmed>true</PublishTrimmed>
- Use dotnet publish to publish.
- Observations using ILSPY.
The screenshot below shows that Person is empty.
With this base it's relatively simple, and in order for Person to retain attributes, it can be silly to use theDynamicallyAccessedMembers
to tell AOT exactly what I want, such asPublicProperties
is all the attributes, but of course it can be set to ALL.
static void Invoke([DynamicallyAccessedMembers()] Type type)
{
var props = ();
foreach (var prop in props)
{
(prop);
}
}
For less invasiveness, you can use an exotic xml like TrimmerRootDescriptor for more advanced customization, e.g. I don't want theGender
field , refer to the official link for details:/dotnet/runtime/blob/main/docs/tools/illink/#xml-examples
<Project Sdk="">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<IlcGenerateMapFile>true</IlcGenerateMapFile>
</PropertyGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="" />
</ItemGroup>
</Project>
Then comes the xml configuration.
<?xml version="1.0" encoding="utf-8" ?>
<linker>
<assembly fullname="Example_21_1">
<type fullname="Example_21_1.Person">
<property signature="System.Int32 Age" />
<property signature=" Name" />
<property signature=" BirthDate" />
</type>
</assembly>
</linker>
Looking at the picture below, everything is perfect.
III: Summary
In the process of releasing the program into AOT, always encountered this or that pitfall, this is to provide a theoretical basis for the later, and Runtime Directives this non-intrusive instantiation, it is worth paying attention to ha.