一聚磺、引言
站長接觸 AOT 已有 3 個月之久坯台,此前在《好消息:NET 9 X86 AOT的突破 - 支持老舊Win7與XP環(huán)境》一文中就有所提及。在這段時間里瘫寝,站長使用 Avalonia 開發(fā)的項目也成功完成了 AOT 發(fā)布測試蜒蕾。然而,這一過程并非一帆風(fēng)順焕阿。站長在項目功能完成大半部分才開始進(jìn)行 AOT 測試咪啡,期間遭遇了不少問題,可謂是 “踩坑無數(shù)”暮屡。為了方便日后回顧撤摸,也為了給廣大讀者提供參考,在此將這段經(jīng)歷進(jìn)行總結(jié)褒纲。
.NET AOT是將.NET代碼提前編譯為本機代碼的技術(shù)准夷。其優(yōu)勢眾多,啟動速度快莺掠,減少運行時資源占用衫嵌,還提高安全性。AOT發(fā)布后無需再安裝.NET運行時等依賴彻秆。.NET 8楔绞、9 AOT發(fā)布后,可在XP掖棉、Win7非SP1操作系統(tǒng)下運行墓律。這使得應(yīng)用部署更便捷,能適應(yīng)更多老舊系統(tǒng)環(huán)境幔亥,為開發(fā)者拓展了應(yīng)用場景,在性能提升的同時察纯,也增加了系統(tǒng)兼容性帕棉,讓.NET應(yīng)用的開發(fā)和部署更具靈活性和廣泛性,給用戶帶來更好的體驗饼记。
二香伴、經(jīng)驗之談
(一)測試策略的重要性
從項目創(chuàng)建伊始,就應(yīng)養(yǎng)成良好的習(xí)慣具则,即只要添加了新功能或使用了較新的語法即纲,就及時進(jìn)行 AOT 發(fā)布測試。否則博肋,問題積累到后期低斋,解決起來會異常艱難蜂厅,站長就因前期忽視了這一點,付出了慘痛的代價膊畴。無奈的解決方法是重新創(chuàng)建項目掘猿,然后逐個還原功能并進(jìn)行 AOT 測試。經(jīng)過了一周的加班AOT測試唇跨,每個 AOT 發(fā)布過程大致如下:
- 內(nèi)網(wǎng) AOT 發(fā)布一次需 2稠通、3 分鐘,這段時間只能看看需求文檔买猖、技術(shù)文章改橘、需求文檔、技術(shù)文章玉控。唧龄。。
- 發(fā)布完成奸远,運行無效果既棺,體現(xiàn)在雙擊未出現(xiàn)界面,進(jìn)程列表沒有它懒叛,說明程序崩潰了丸冕,查看系統(tǒng)應(yīng)用事件日志,日志中通常會包含異常警告信息薛窥。
- 依據(jù)日志信息檢查代碼胖烛,修改相關(guān) API。
- 再次進(jìn)行 AOT 發(fā)布诅迷,重復(fù)上述 1 - 3 步驟佩番。
經(jīng)過一周的努力,項目 AOT 后功能測試終于正常罢杉,至此收工趟畏。
(二)AOT 需要注意的點及解決方法
1. 添加rd.xml
在主工程創(chuàng)建一個XML文件,例如Roots.xml
滩租,內(nèi)容大致如下:
<linker>
<assembly fullname="CodeWF.Toolbox.Desktop" preserve="All" />
</linker>
需要支持AOT的工程赋秀,在該XML中添加一個assembly
節(jié)點,fullname
是程序集名稱律想,CodeWF.Toolbox.Desktop
是站長小工具的主工程名猎莲,點擊查看源碼。
在主工程添加ItemGroup
節(jié)點關(guān)聯(lián)該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" />
3. App.config讀寫
在.NET Core中使用System.Configuration.ConfigurationManager
包操作App.config文件,rd.xml
需添加如下內(nèi)容:
<assembly fullname="System.Configuration.ConfigurationManager" preserve="All" />
使用Assembly.GetEntryAssembly().location
失敗,目前使用ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
獲取的應(yīng)用程序程序配置身笤,指定路徑的方式后續(xù)再研究豹悬。
4. HttpClient使用
rd.xml
添加如下內(nèi)容:
<assembly fullname="System.Net.Http" preserve="All" />
5. Dapper支持
Dapper的AOT支持需要安裝Dapper.AOT
包,rd.xml
添加如下內(nèi)容:
<assembly fullname="Dapper" preserve="All" />
<assembly fullname="Dapper.AOT" preserve="All" />
數(shù)據(jù)庫操作的方法需要添加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
序列化
public static bool ToJson<T>(this T obj, out string? json, out string? errorMsg)
{
if (obj == null)
{
json = default;
errorMsg = "Please provide object";
return false;
}
var 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, out T? obj, out string? errorMsg)
{
if (string.IsNullOrWhiteSpace(json))
{
obj = default;
errorMsg = "Please provide json string";
return false;
}
try
{
var options = new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
obj = JsonSerializer.Deserialize<T>(json!, options);
errorMsg = default;
return true;
}
catch (Exception ex)
{
obj = default;
errorMsg = ex.Message;
return false;
}
}
7. 反射問題
參考項目CodeWF.NetWeaver
- 創(chuàng)建指定類型的
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)!;
}
}
- 反射調(diào)用
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 })
解決辦法,轉(zhuǎn)換為實現(xiàn)的接口調(diào)用:
// List<T>
(obj as IList).Add(child);
// Dictionary<Key, Value>
(obj as IDictionary)[key] = value;
- 獲取數(shù)組莹弊、
List<T>
涤久、Dictionary<key, value>
的元素個數(shù)
同上面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成功:直接通過轉(zhuǎn)換為基類型或?qū)崿F(xiàn)的接口調(diào)用屬性即可:
// 數(shù)組
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
運行响迂,請?zhí)砑?code>YY-Thunks包:
<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />
并指定目標(biāo)框架為net9.0-windows
。
9. Winform\兼容XP
如果第8條后還運行不了细疚,請參考上一篇文章《.NET 9 AOT的突破 - 支持老舊Win7與XP環(huán)境 - 碼界工坊 (dotnet9.com)》添加VC-LTL包蔗彤,這里不贅述。
10. 其他
還有許多其他需要注意的地方疯兼,后續(xù)想起來逐漸完善本文然遏。
三、總結(jié)
AOT 發(fā)布測試雖然過程中可能會遇到諸多問題吧彪,但通過及時的測試和正確的配置調(diào)整待侵,最終能夠?qū)崿F(xiàn)項目的順利發(fā)布。希望以上總結(jié)的經(jīng)驗?zāi)軐Υ蠹以?AOT 使用過程中有所幫助姨裸,讓大家在開發(fā)過程中少走彎路秧倾,提高項目的開發(fā)效率和質(zhì)量。同時傀缩,也期待大家在實踐中不斷探索和總結(jié)那先,共同推動技術(shù)的進(jìn)步和發(fā)展。
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