当前位置:首页 > 文章中心 > 正文内容

AOT编译Avalonia应用:StarBlog Publisher项目实践与挑战

dgx6663周前 (07-06)文章中心8

前言

最近我使用 Avalonia 开发了一个文章发布工具,StarBlog Publisher。

Avalonia 是一个跨平台的 UI 框架,它可以在 Windows、Linux 和 macOS 上运行。它的特点是高性能、跨平台、易于使用。

Avalonia 有很多优点,比如高性能、跨平台、易于使用。但是,它也有一些缺点,比如学习曲线较陡峭、文档较难找到。

但 Avalonia 是基于 .NetCore 框架开发的,最终打包出来的可执行文件,如果选择 framework-dependant 发布,那么需要在客户端上安装 .NetCore 运行时环境,这对用户来说是一个很大的负担。如果使用 self-contained 发布,体积又比较大。

并且还容易被反编译,这在一些商业软件中是不允许的。(不过我这个项目是开源的,所以没有这个问题)

本文以 StarBlog Publisher 项目为例,记录一下使用 AOT 发布 Avalonia 应用的踩坑过程。

新的1.1版本已经发布,欢迎下载尝试: https://github.com/star-blog/starblog-publisher/releases

关于 AOT

从 .Net7 开始,逐步开始支持 AOT 发布,这是一个非常重要的特性。AOT 发布可以将 .Net 应用程序编译成不依赖运行库的机器码,体积较小,而且不容易被反编译。

AOT 发布的原理是将.Net 应用程序编译成 LLVM IR 代码,然后使用 LLVM 编译器将 LLVM IR 代码编译成机器码。LLVM 编译器可以将 LLVM IR 代码编译成不同的目标平台的机器码。

目前的 LTS 版本是 .Net8,对 AOT 的支持已经比较完善了,这次我来尝试使用 AOT 方式发布 Avalonia 应用。

PS:据说 .Net9 对 AOT 方式提供了很多优化和改进,接下来我会尝试一下。

使用 AOT 可能会遇到的问题

  • 兼容性问题 :AOT编译可能与某些依赖库不兼容,特别是那些依赖反射、动态代码生成或JIT编译的库。如果遇到问题,可能需要在rd.xml中添加更多配置。
  • 包大小 :AOT编译会生成更大的可执行文件(相比起 framework-dependant 模式而言),但启动速度更快。
  • 调试困难 :AOT编译的应用程序调试可能更加困难。
  • 第三方库 :检查项目中使用的第三方库是否支持AOT编译。例如, Microsoft.Extensions.AI 和 Microsoft.Extensions.AI.OpenAI 是预览版,可能需要特别注意其AOT兼容性。
  • Avalonia特定配置 :对于Avalonia应用,可能需要确保XAML相关的类型信息被正确保留。

修改项目文件

首先需要在项目文件中添加AOT相关的配置:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<able>enable</able>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>

<!-- AOT 相关配置 -->
<PublishAot>true</PublishAot>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>

<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<!-- 其余部分保持不变 -->
</Project>

JSON序列化问题

在AOT编译环境中,JSON序列化是一个常见的问题点,因为它通常依赖于运行时反射。

这个项目有几个地方用到了 JSON

一个是应用设置,另一个是网络请求

先说结论:Newtonsoft.Json 相比 System.Text.Json 对 AOT 的支持更好,如果要使用 AOT,优先使用 Newtonsoft.Json 库。

修改应用设置 AppSettings.cs 支持AOT

如果非要使用 System.Text.Json ,那么需要修改一下。用 Newtonsoft.Json 的话直接跳过。

using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using StarBlogPublisher.Services.Security;
using System.Text.Json.Serialization.Metadata; // 添加此命名空间

namespaceStarBlogPublisher.Services;

// 添加JsonSerializable特性,为AOT生成序列化代码
[JsonSerializable(typeof(AppSettings))]
internalpartialclassAppSettingsContext : JsonSerializerContext
{
}

publicclassAppSettings {
privatestaticreadonlystring ConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"StarBlogPublisher",
"settings.json"
);

// ... 现有代码 ...

private static AppSettings Load() {
try {
if (File.Exists(ConfigPath)) {
var json = File.ReadAllText(ConfigPath);
// 使用AOT友好的序列化方式
var settings = JsonSerializer.Deserialize(json, AppSettingsContext.Default.AppSettings);
return settings ?? new AppSettings();
}
}
catch (Exception ex) {
// 如果加载失败,返回默认设置
Console.WriteLine($"Failed to load app settings. {ex}");
}

returnnew AppSettings();
}

public void Save() {
try {
var directory = Path.GetDirectoryName(ConfigPath);
if (!string.IsOrEmpty(directory)) {
Directory.CreateDirectory(directory);
}

// 使用AOT友好的序列化方式
var json = JsonSerializer.Serialize(this, AppSettingsContext.Default.AppSettings, new JsonSerializerOptions {
WriteIndented = true
});
File.WriteAllText(ConfigPath, json);

// 触发配置变更事件
SettingsChanged?.Invoke(this, EventArgs.Empty);
}
catch (Exception) {
// todo 处理保存失败的情况
}
}
}

解释

  1. 添加了 [JsonSerializable] 特性和 JsonSerializerContext 派生类,这是.NET中支持AOT的JSON序列化的关键。这会在编译时生成序列化代码,而不是依赖运行时反射。
  2. 修改了 Load()Save() 方法,使用 AppSettingsContext.Default.AppSettings 作为类型信息,而不是依赖运行时类型推断。
  3. 这种方法确保了在AOT环境中,所有需要的序列化代码都会在编译时生成,而不需要运行时反射。

此外,还需要在项目文件中确保已启用AOT编译的JSON源生成器:

<PropertyGroup>
<!-- 其他属性 -->
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

这些修改将确保AppSettings类在AOT编译环境中能够正确地进行JSON序列化和反序列化。

Refit在AOT模式下的JSON序列化问题

在AOT模式下,Refit库的JSON处理也可以使用 Newtonsoft.Json

先安装 Refit.Newtonsoft.Json 库,并且需要额外配置来处理类型信息。

添加类型预注册

需要创建一个新的类来预注册所有API接口中使用的类型:

using Newtonsoft.Json;
using StarBlogPublisher.Models;
using System.Collections.Generic;
using CodeLab.Share.ViewModels.Response;

namespaceStarBlogPublisher.Services;

/// <summary>
/// 为AOT编译预注册Refit使用的类型
/// </summary>
publicstaticclassRefitTypeRegistration
{
/// <summary>
/// 在应用启动时调用此方法,确保所有类型都被预注册
/// </summary>
public static void RegisterTypes()
{
// 注册常用的响应类型
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
// 添加自定义转换器如果需要
Converters = new List<JsonConverter>
{
// 可以添加自定义转换器
}
};

// 预热类型 - 确保这些类型在AOT编译时被包含
var types = new[]
{
typeof(ApiResponse<>),
typeof(ApiResponse<List<Category>>),
typeof(ApiResponse<List<WordCloud>>),
// 添加其他API响应类型
typeof(List<Category>),
typeof(Category),
typeof(WordCloud),
// 添加所有模型类型
};

// 触发类型加载
foreach (var type in types)
{
var _ = type.FullName;
}
}
}

修改ApiService类

修改ApiService类,确保在初始化时注册类型:

using Refit;
using StarBlogPublisher.Services.StarBlogApi;
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;

namespaceStarBlogPublisher.Services;

publicclassApiService {
privatestatic ApiService? _instance;

publicstatic ApiService Instance {
get {
_instance ??= new ApiService();
return _instance;
}
}

privatereadonly RefitSettings _refitSettings;

private ApiService() {
// 确保类型被注册
RefitTypeRegistration.RegisterTypes();

// 配置Refit设置
_refitSettings = new RefitSettings(new NewtonsoftJsonContentSerializer(
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
// 禁用反射优化,这在AOT环境中很重要
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
}
));
}

// ... 其余代码保持不变 ...
}

在App.axaml.cs中初始化类型注册

确保在应用启动时调用类型注册:

public override void OnFrameworkInitializationCompleted()
{
// 确保Refit类型被注册
RefitTypeRegistration.RegisterTypes();

// 其他初始化代码...

base.OnFrameworkInitializationCompleted();
}

添加AOT兼容性配置

由于AOT编译对反射和动态代码生成有限制,需要添加一个rd.xml文件来指定需要保留的类型:

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!-- 添加需要在AOT中保留的程序集和类型 -->
<Assembly Name="StarBlogPublisher" Dynamic="Required All" />
<Assembly Name="Avalonia.Markup.Xaml" Dynamic="Required All" />
<Assembly Name="Avalonia" Dynamic="Required All" />

<!-- Refit相关程序集 -->
<Assembly Name="Refit" Dynamic="Required All" />
<Assembly Name="Newtonsoft.Json" Dynamic="Required All" />

<!-- 添加API接口和模型类型 -->
<Assembly Name="StarBlogPublisher">
<Type Name="StarBlogPublisher.Services.StarBlogApi.IAuth" Dynamic="Required All" />
<Type Name="StarBlogPublisher.Services.StarBlogApi.ICategory" Dynamic="Required All" />
<Type Name="StarBlogPublisher.Services.StarBlogApi.IBlogPost" Dynamic="Required All" />
</Assembly>

<Assembly Name="CodeLab.Share">
<Type Name="CodeLab.Share.ViewModels.Response.ApiResponse`1" Dynamic="Required All" />
</Assembly>
</Application>
</Directives>

然后在项目文件中引用这个rd.xml文件:

<ItemGroup>
<RdXmlFile Include="rd.xml" />
</ItemGroup>

发布

使用以下命令发布AOT版本的应用程序:

dotnet publish -c Release -r win-x64 -p:PublishAot=true

对于其他平台,可以替换相应的RID:

  • Windows: win-x64
  • macOS: osx-x64
  • Linux: linux-x64

小结

AOT 发布是 .Net 平台一个重要的特性,它能将应用程序编译成不依赖运行时的机器码,不仅减小了发布包体积,还能提升启动速度,同时也增加了反编译的难度。

使用 AOT 方式发布 Avalonia 应用程序还是有一些坑的。:JSON序列化问题、类型注册问题以及AOT兼容性问题。针对这些问题,以下解决方案可以解决:

  1. JSON序列化方面,优先选择了对AOT支持更好的 Newtonsoft.Json 库,并通过类型预注册确保了序列化的正确性。
  2. 对于需要反射的功能,通过rd.xml文件显式声明需要保留的类型,解决了AOT编译时的类型裁剪问题。
  3. 在项目配置方面,通过合理设置AOT相关的编译选项,平衡了性能和包大小。

虽然AOT发布还存在一些限制,比如调试相对困难、部分第三方库可能不兼容等,但随着.Net平台的发展(特别是.Net9之后的版本),AOT的支持会越来越完善。对于需要高性能、小体积、反编译保护的Avalonia应用来说,AOT发布是一个值得考虑的选择。


扫描二维码推送至手机访问。

版权声明:本文由第六芝士网发布,如需转载请注明出处。

本文链接:http://www.dgx666.com/post/2835.html

标签: publisher下载
分享给朋友:

“AOT编译Avalonia应用:StarBlog Publisher项目实践与挑战” 的相关文章

CAD常见出错问题与处理方法

1.WIN10原版系统直接无法安装CAD2012,2014等等这是因为WIN10原版系统默认没有自带.net 3.5导致的。卸载高版本的.NET,安装.net 3.52.激活软件时出错运行激活软件,点击Patch时弹窗提示Could not get debug privilege! Are you...

去哪能安全、免费下载到正版的office?

嗨,各位木友们好呀,我是小木。我下载软件时有一个习惯,那就是尽量去软件官网找下载链接。有些软件,比如像腾讯、阿里系的,你一上官网就能看到醒目的下载链接,安全又便捷:但是有些软件,比如像微软的office,我保证等你找到链接时,你男朋友都跟人跑了…因此,此时很多人就会去各种奇奇怪怪的网站找下载链接,可...

你还不知道?这款名牌50寸电视现在只需1499,性价比爆棚!

再也不用每年只等双十一的大面积降价了,京东618让你在年中就可以放肆购!这次的年中大促比双十一还要给力,尤其是家电的降价幅度是前所未有的。看尚CANTV在此次618年中大促的活动中首当其冲,各大热门机型的优惠价打破了行业价格底线!经典尺寸系列的看尚超能电视 C49S抢购价直降400元,只需要1499...

恶意文件携带驱动人生数字签名,混淆视听执行后门

在现代数字化领域中,数字签名借助先进的加密技术,为文件和数据披上了一层坚固的“防护衣”,确保软件的完整性和来源的可靠性,极大地提升了软件的安全性。但是随着数字签名技术的广泛应用,一些安全检查也可能因此“放松警惕”,放过大部分带有数字签名的软件,为攻击者留下可乘之机。因为攻击者可以巧妙地利用数字签名,...

c# 10 教程:24 本机和 COM 互操作性

本章介绍如何与本机(非托管)动态链接库 (DLL) 和组件对象模型 (COM) 组件集成。除非另有说明,否则本章中提到的类型存在于 System 或 System.Runtime.InteropServices 命名空间中。调用本机 DLL是缩写,允许您访问非托管DLL(Unix上的)中的函数,结构...

Microsoft Office 系列安装包官方链接大全

目前各大网盘限速严重,bt磁力又有某迅某雷等拦路虎,还好现在家庭入户带宽普遍都在百兆以上了,直接从微软官方下载零售版安装包反而是最快速的方法。以下是Office不同版本的官方下载链接。请注意,这些链接可能会随着微软的更新而变化,因此如果遇到链接失效的情况,请直接访问微软官方网站获取最新信息。####...