I.はじめに。
站长接触 AOT 已有 3 个月之久,此前在《好消息:NET 9 X86 AOT 的突破 - 支持老旧 Win7 与 XP 环境》一文中就有所提及。在这段时间里,站长使用 Avalonia 开发的项目也成功完成了 AOT 发布测试。然而,这一过程并非一帆风顺。站长在项目功能完成大半部分才开始进行 AOT 测试,期间遭遇了不少问题,可谓是 “踩坑无数”。为了方便日后回顾,也为了给广大读者提供参考,在此将这段经历进行总结。
NET AOTは、. NETコードをネイティブコードに事前にコンパイルする技術です。多くの利点があり、起動速度が速く、実行時のリソース占有を減らし、セキュリティを向上させます。AOTのリリース後、. NETランタイムなどの依存関係をインストールする必要はありません。. NET 8と9 AOTのリリース後、XPとWin 7の非SP 1オペレーティングシステムで動作するようになりました。これにより、アプリケーションの展開がより便利になり、より古いシステム環境に適応し、開発者のアプリケーションシナリオを拡大し、パフォーマンスを向上させながら、システムの互換性を向上させ、. NETアプリケーションの開発と展開をより柔軟かつ広範にし、ユーザーにより良いエクスペリエンスをもたらします。
II.経験の議論
(1)試験戦略の重要性
プロジェクトの最初から、新しい機能が追加されたり、新しい構文が使用されたりするたびに、AOTリリーステストを行うという良い習慣を身につける必要があります。そうでなければ、問題は後の段階に蓄積され、解決は非常に困難になり、ウェブマスターは初期段階でこれを無視し、痛い代償を払った。手に負えない回避策は、プロジェクトを再作成してから、機能を1つずつ復元してAOTテストを行うことです。1週間の残業AOTテストの後、各AOTリリースプロセスはおおよそ以下の通りです。
- イントラネットAOTは1回に2、3分で公開され、この時間は要件文書、技術文書、要件文書、技術文書を見るだけです。
- リリース完了、実行は効果がなく、ダブルクリックに反映されてインターフェイスが表示されない、プロセスリストはそれがない、プログラムがクラッシュしたことを示し、システムアプリケーションイベントログを表示し、ログには通常例外警告メッセージが含まれています。
- ログ情報に基づいてコードをチェックし、関連するAPIを変更します。
- AOT発行を再度行い、上記の手順1 ~ 3を繰り返します。
1週間の作業の後、プロジェクトのAOT後機能テストは正常になり、終了しました。
(2)AOTの注意点と解決策
1. rd.xmlを追加
在主工程创建一个 XML 文件,例如Roots.xml,内容大致如下:
<linker>
<assembly fullname="CodeWF.Toolbox.Desktop" preserve="All" />
</linker>
需要支持 AOT 的工程,在该 XML 中添加一个assembly节点,fullname是程序集名称,CodeWF.Toolbox.Desktop是站长小工具的主工程名,点击查看源码。
在主工程添加ItemGroup节点关联该 XML 文件:
<ItemGroup>
<TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>
2. Prismのサポート
ウェブマスターはPrismフレームワークとDryIOCコンテナを使用しており、AOTをサポートするには以下のNuGetパッケージを追加する必要があります。
<PackageReference Include="Prism.Avalonia" Version="8.1.97.11073" />
<PackageReference Include="Prism.DryIoc.Avalonia" Version="8.1.97.11073" />
rd.xml需要添加
<assembly fullname="Prism" preserve="All" />
<assembly fullname="DryIoc" preserve="All" />
<assembly fullname="Prism.Avalonia" preserve="All" />
<assembly fullname="Prism.DryIoc.Avalonia" preserve="All" />
8.1.97.11073バージョンは最後のオープンソース版で、9. X以降は有料版です。
3. App.configの読み書き
在.NET Core 中使用System.Configuration.ConfigurationManager包操作 App.config 文件,rd.xml需添加如下内容:
<assembly fullname="System.Configuration.ConfigurationManager" preserve="All" />
使用Assembly.GetEntryAssembly().location失败,目前使用ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)获取的应用程序程序配置,指定路径的方式后续再研究。
4. HttpClientの利用
rd.xml添加如下内容:
<assembly fullname="System.Net.Http" preserve="All" />
一般不直接使用HttpClient,可尝试Refit之类的三方库用作Http请求更便捷:
<assembly fullname="Refit" preserve="All" />
5. Dapperサポート
Dapper 的 AOT 支持需要安装Dapper.AOT包,rd.xml添加如下内容:
<assembly fullname="Dapper" preserve="All" />
<assembly fullname="Dapper.AOT" preserve="All" />
数据库操作的方法需要添加DapperAOT特性,举例如下:
[DapperAot]
public static bool EnsureTableIsCreated()
{
try
{
using var connection = new SqliteConnection(DBConst.DBConnectionString);
connection.Open();
const string sql = $@"
CREATE TABLE IF NOT EXISTS {nameof(JsonPrettifyEntity)}(
{nameof(JsonPrettifyEntity.IsSortKey)} Bool,
{nameof(JsonPrettifyEntity.IndentSize)} INTEGER
)";
using var command = new SqliteCommand(sql, connection);
return command.ExecuteNonQuery() > 0;
}
catch (Exception ex)
{
return false;
}
}
6. System.Text.Json
Serializing
public static bool ToJson<T>(this T obj, JsonSerializerOptions? options, out string? json, out string? errorMsg)
{
if (obj == null)
{
json = default;
errorMsg = "Please provide object";
return false;
}
if (options == null)
{
options = new JsonSerializerOptions()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
};
}
try
{
json = JsonSerializer.Serialize(obj, options);
errorMsg = default;
return true;
}
catch (Exception ex)
{
json = default;
errorMsg = ex.Message;
return false;
}
}
デシリアライズ
public static bool FromJson<T>(this string? json, JsonSerializerOptions? options, out T? obj, out string? errorMsg)
{
if (string.IsNullOrWhiteSpace(json))
{
obj = default;
errorMsg = "Please provide json string";
return false;
}
try
{
if (options == null)
{
options = new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
Converters = { new NullableDateTimeConverter() }
};
}
obj = JsonSerializer.Deserialize<T>(json!, options);
errorMsg = default;
return true;
}
catch (Exception ex)
{
obj = default;
errorMsg = ex.Message;
return false;
}
}
上面配置简单类序列化与反序列化正常用,如果类中存在int?、double?之类的可空类型:
public class Project
{
public int? Id { get; set; }
public string Name { get; set; }
public int Record { get; set; }
[XmlArray(ElementName = "Members")]
[XmlArrayItem(typeof(Member))]
public List<Member> Members { get; set; }
}
则创建一个JsonSerializerContext的子类即可解决:
[JsonSerializable(typeof(Project))]
[JsonSerializable(typeof(List<Member>))]
internal partial class ProjectJsonSerializerContext : JsonSerializerContext
{
}
***注意:アクティブな呼び出しは不要です **
従来のJsonSerializer.Serialize projectの下位層は、Projectクラスのプロパティ、特性などの情報を解析するためにランタイムリフレクションに依存していますが、AOTコンパイルではコンパイル時に明示的に参照されていないリフレクションメタデータが切り取られ、シリアライズに失敗しますNotSupportedExceptionがスローされるか、一部のプロパティがシリアライズ/デシリアライズできない。
ProjectJson Serializer Contextは、コンパイル時に静的メタデータを生成することで動作します。
7. 反射問題の問題
参考项目CodeWF.NetWeaver
- 创建指定类型的
List<T>或Dictionary<T>实例:
public static object CreateInstance(Type type)
{
var itemTypes = type.GetGenericArguments();
if (typeof(IList).IsAssignableFrom(type))
{
var lstType = typeof(List<>);
var genericType = lstType.MakeGenericType(itemTypes.First());
return Activator.CreateInstance(genericType)!;
}
else
{
var dictType = typeof(Dictionary<,>);
var genericType = dictType.MakeGenericType(itemTypes.First(), itemTypes[1]);
return Activator.CreateInstance(genericType)!;
}
}
- 反射调用
List<T>和Dictionary<T>的Add方法添加元素失败,下面是伪代码:
// List<T>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ child })
// Dictionary<Key, Value>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ key, value })
解決策は、実装されたインターフェイス呼び出しに変換することです:
// List<T>
(obj as IList).Add(child);
// Dictionary<Key, Value>
(obj as IDictionary)[key] = value;
- 获取数组、
List<T>、Dictionary<key, value>的元素个数
同上面 Add 方法反射获取 Length 或 Count 属性皆返回 0,value.Property("Length", 0),封装的 Property 非 AOT 运行正确:
public static T Property<T>(this object obj, string propertyName, T defaultValue = default)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(nameof(propertyName));
var propertyInfo = obj.GetType().GetProperty(propertyName);
if (propertyInfo == null)
{
return defaultValue;
}
var value = propertyInfo.GetValue(obj);
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (InvalidCastException)
{
return defaultValue;
}
}
AOT 成功:プロパティは基底型または実装インタフェースに変換されて直接呼び出されます。
// 数组
var length = ((Array)value).Length;
// List<T>
if (value is IList list)
{
var count = list.Count;
}
// Dictionary<key, value>
if (value is IDictionary dictionary)
{
var count = dictionary.Count;
}
8. Windows 7のサポート
如遇 AOT 后无法在Windows 7运行,请添加YY-Thunks包:
<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />
并指定目标框架为net9.0-windows。
9. Winform\はXP互換です。
如果第 8 条后还运行不了,请参考上一篇文章《.NET 9 AOT 的突破 - 支持老旧 Win7 与 XP 环境 - 码坊 (dotnet9.com)》添加 VC-LTL 包,这里不赘述。
10. その他の
その後、この記事を改善するために注意すべき他の多くの点があります。
III.サマリー
AOTリリーステストプロセスでは多くの問題が発生する可能性がありますが、タイムリーなテストと適切な構成調整を通じて、最終的にプロジェクトの円滑なリリースを達成することができます。以上の経験がAOTの利用プロセスに役立ち、開発プロセスにおける迂回を減らし、プロジェクトの開発効率と品質を向上させることを願っています。同時に、皆さんが実践の中で探求し、総括し、技術の進歩と発展を共同で推進していくことを期待しています。
AOT参照可能なプロジェクト:
- CodeWF.NetWeaver: https://github.com/dotnet9/CodeWF.NetWeaver
- CodeWF.Tools:https://github.com/dotnet9/CodeWF.Tools
- CodeWF.Toolbox:https://github.com/dotnet9/CodeWF.Toolbox