原文地址:https://www.cnblogs.com/kewei/p/16722674.html
0 起因
這段日子看到dotnet7-rc1發布,我對NativeAot功能比較感興趣,如果aot成功,這意味了我們的dotnet程序在防破解的上直接指數級提高。我隨手使用asp.netcore-7.0模板創建了一個默認的web程序,發現aot發布出來,web服務完全使用,這是之前那些preview版本做不到的。想到fastgithub本質上也是基于asp.netcore-6.0框架的項目,于是走上fastgithub的aot改造之路。
1 改造步驟
1.1 升級框架
將所有項目的TargetFramework值改為7.0,fastgithub使用Directory.Build.props,所以我只需要在Directory.Build.props文件修改一個地方,所有項目生效了。
1.2 升級nuget包
所有項目的nuget包進行升級,像有些是6.0.x版本的,如果有7.0.x-rc.x.x的更新包,就升級到最新rc版本。
1.3 json序列化
如果您的使用JsonSerializer序列化了內部未公開的類型,則需要改為JsonSerializerContext(源代碼生成)方式,比如我在想序列化下面的EndPointItem
類型的實例,需要如下改進:
private record EndPointItem(string Host, int Port);[JsonSerializable(typeof(EndPointItem[]))]
[JsonSourceGenerationOptions(WriteIndented = true,PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
private partial class EndPointItemsContext : JsonSerializerContext
{
}
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(endPointItems, EndPointItemsContext.Default.EndPointItemArray);
2 aot發布
我發布在vs上進行發布時有問題,我們需要在使用cli來發布,cli發布還能為我們提供更多的編譯信息輸出。
2.1 單文件的發布命令
set output=./publish
if exist "%output%" rd /S /Q "%output%"
dotnet publish -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained -r win-x64 -o "%output%/fastgithub_win-x64" ./FastGithub/FastGithub.csproj
aot編譯之后也是單個文件,所以如果您的程序使用PublishSingleFile模式發布不能正常運行的話,就不用試著aot發布了。
2.2 aot發布的命令
set output=./publish
if exist "%output%" rd /S /Q "%output%"
dotnet publish -c Release /p:PublishAot=true /p:PublishTrimmed=true --self-contained -r win-x64 -o "%output%/fastgithub_win-x64" ./FastGithub/FastGithub.csproj
我們只需要把之前的PublishSingleFile
改為PublishAot
,他們兩個不能同時設置為true。經過幾分鐘的滿屏黃色警告之后,我們終于得到aot版本的40MB左右的fastgtihub.exe,迫不及待地運行了fastgithub.exe,不幸的是程序運行異常:
Unhandled Exception: System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.---> System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.---> System.NotSupportedException: 'Org.BouncyCastle.Security.DigestUtilities+DigestAlgorithm[]' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibilityat System.Reflection.Runtime.General.TypeUnifier.WithVerifiedTypeHandle(RuntimeArrayTypeInfo, RuntimeTypeInfo) + 0x5bat System.Array.InternalCreate(RuntimeType, Int32, Int32*, Int32*) + 0x5cat System.Array.CreateInstance(Type, Int32) + 0x46at System.RuntimeType.GetEnumValues() + 0x86at Org.BouncyCastle.Utilities.Enums.GetArbitraryValue(Type enumType) + 0xaat Org.BouncyCastle.Security.DigestUtilities..cctor() + 0x86
2.3 嘗試解決BouncyCastle
BouncyCastle是用于生成ca證書和服務器證書的第三方庫,在dotnet6時或以前,我們沒有其它庫可以完成這個功能。以上的異常大概是提示了DigestUtilities這個類型的某個內部私有類型被裁剪了,所以無法創建這個已裁剪掉類型的數組類型。我想到可以給項目的ItemGroup加上<TrimmerRootAssembly Include="BouncyCastle.Crypto" />
,讓這個程序集不要裁剪,然后再進行新一輪aot編譯,不幸的是這次是編譯時異常:
CVTRES : fatal error CVT1103: 無法讀取文件 [D:\github\FastGithub\FastGithub\FastGithub.csproj]
LINK : fatal error LNK1123: 轉換到 COFF 期間失敗: 文件無效或損壞 [D:\github\FastGithub\FastGithub\FastGithub.csproj]
C:\Program Files\dotnet\sdk\7.0.100-rc.1.22431.12\Sdks\Microsoft.DotNet.ILCompiler\build\Microsoft.NETCore.Native.targe
ts(349,5): error MSB3073: 命令“"C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.34.31721\bin\Hostx
64\x64\link.exe" @"obj\Release\net7.0\win-x64\native\link.rsp"”已退出,代碼為 1123。 [D:\github\FastGithub\FastGithub\FastGithu
b.csproj]
2.4 移除BouncyCastle
迫于無奈,我們必須移除對BouncyCastle的依賴,轉為使用基礎庫來實現證書生成,這方面幾乎沒有任何可以查到有幫助的資料,我花了整整一天來改造,感興趣證書生成的同學,可以參考CertGenerator.cs。去掉BouncyCastle之后再aot發布,程序可以運行起來了,沒有任何異常,但是發現程序沒有攔截任何流量。
2.5 查找程序不干活的原因
由于沒有任何的異常輸出,咱也不知道是啥情況,現在使用debug模式繼續aot發布,然后運行fastgithub.exe,在vs附加到fastgithub進程,下斷點分析。經過一路跟蹤,我發現如下一個分支,總是進入return邏輯:
var domain = question.Name;
if (this.fastGithubConfig.IsMatch(question.Name.ToString()) == false)
{return;
}
我想看看fastGithubConfig現在是什么值,為什么總是不匹配,但是經過aot之后,無法發現fastGithubConfig這個局部變量,而函數內的變量,也不再是crl類型,而是一種為調試而存在的代理類型一樣,可看的信息也很少。
于是我加入大量的log,通過log看看fastGithubConfig是什么值,最后發現是配置綁定到Options的字典類型屬性時,綁定不成功(但也沒有任何異常或日志)。
2.6 解決配置綁定到字典的問題
這個問題咱實在不知道怎么解決,那就github上發起問題吧:services.Configure(configuration) failure at PublishAot,果然回復很積極,告訴咱們目前可以在任意調用的函數加上[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary<string, DomainConfig>))]
。經過這么修改之后,配置綁定到Options生效了。
3 后續
經過這么一個實際項目aot之后,我對aot有了初步的了解,個人覺得aot基本可以用小型程序的發布,期待到dotnet8之后,NativeAot變成沒有坑。