前言
随着 .NET 8 的发布,ASP.NET Core 已原生支持 AOT(Ahead-of-Time)编译。虽然大多数教程仍聚焦于将 ASP.NET Core 发布为独立可执行文件(.exe),但其实它也支持以类库形式发布为 .dll 文件,并通过 UnmanagedCallersOnly 导出函数,供其他应用程序调用。
本文将带你探索一种新颖的架构方式:在一个 WPF 进程中,集成 AOT 编译的 ASP.NET Core 类库,实现本地 HTTP 服务。
这种设计让:
-
WPF 负责 UI 层逻辑与用户交互
-
ASP.NET Core 提供轻量级 HTTP 接口服务
两者各司其职,充分发挥各自优势。
为什么选择 AOT 类库方式?
相比传统的项目引用或 NuGet 包集成,AOT 发布为 DLL 有以下优势:
| 优势 | 说明 |
|---|---|
| ✅ 无依赖污染 | 输出仅一个 DLL,不带 ASP.NET Core 的大量依赖 DLL |
| ✅ 模块解耦 | HTTP 服务作为边缘功能,可独立编译、部署 |
| ✅ 版本隔离 | WPF 和 ASP.NET Core 可使用不同 .NET 版本运行(通过原生互操作) |
| ✅ 轻量化部署 | AOT 编译后体积小,启动快,适合嵌入式场景 |
第一步:创建支持 AOT 的 ASP.NET Core 类库
1、创建项目
dotnet new webapiaot -o Lib1
cd Lib1
该模板默认启用 AOT 支持,生成的是顶层语句风格的 Program.cs。
2、改造 Program.cs
删除 Main 方法(类库不能有入口点),将逻辑封装到静态方法中,并通过 UnmanagedCallersOnly 导出函数。
完整代码如下:
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lib1;
public static class Program
{
private static string _greetText = "Hello from Lib1!";
[UnmanagedCallersOnly(EntryPoint = "Start", CallConvs = [typeof(CallConvCdecl)])]
public static int Start()
{
Console.WriteLine("Start run");
// ⚠️ 注意:此处使用 Task.Run 会占用线程池资源,仅用于演示
Task.Run(StartInner);
return 2; // 返回值示例
}
[UnmanagedCallersOnly(EntryPoint = "SetGreetText", CallConvs = [typeof(CallConvCdecl)])]
public static void SetGreetText(IntPtr greetText, int charCount)
{
_greetText = Marshal.PtrToStringUni(greetText, charCount);
}
private static void StartInner()
{
var builder = WebApplication.CreateSlimBuilder([]);
var app = builder.Build();
app.MapGet("/", () => _greetText);
app.Run(); // 启动 Web 服务
}
}
📌 关键说明:
UnmanagedCallersOnly允许非托管代码调用 .NET 方法。- 参数必须是基础类型或指针(如
IntPtr),不支持 .NET 对象跨边界传递。- 使用
Marshal.PtrToStringUni将指针转换为字符串。
第二步:解决 AOT 类库已知问题
由于 .NET 运行时的一个 已知 Bug,当 AOT 类库被 .NET 应用加载时,控制台输出可能会卡住。
解决方案:禁用 EventSource 支持
在 Lib1.csproj 中添加:
<PropertyGroup>
<EventSourceSupport>false</EventSourceSupport>
</PropertyGroup>
第三步:发布 AOT 类库
执行发布命令:
dotnet publish -r win-x64 --self-contained
发布成功后,可在 bin/Debug/net8.0/win-x64/publish/ 找到 Lib1.dll。
💡 文件大小:约 7MB(Slim + AOT 编译优化后)
✅ 唯一必需文件:
Lib1.dll
❌ 其他 DLL(如System.*)可删除
第四步:在 WPF 项目中调用 AOT 类库
1、创建 WPF 项目
dotnet new wpf -o WPFDemo
2、添加 Lib1.dll 到输出目录
将 Lib1.dll 拷贝到 WPFDemo/bin/Debug/net8.0/ 目录下。
⚠️ 注意:不能直接添加项目引用(因为是 AOT 原生 DLL),只能通过
DllImport调用。
3、编辑 MainWindow.xaml
添加简单的 UI 界面用于设置欢迎词并触发更新:
<Grid>
<StackPanel VerticalAlignment="Center">
<Grid MinWidth="300" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0 5 5 5"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="0 5 0 5"/>
</Style>
</Grid.Resources>
<TextBlock Text="欢迎词:"/>
<TextBox x:Name="GreetTextBox" Grid.Column="1" Text="Hello"/>
<Button x:Name="UpdateButton" Grid.Row="1" Grid.ColumnSpan="2"
Width="100" Height="30" Margin="10"
Click="UpdateButton_OnClick">更新</Button>
</Grid>
</StackPanel>
</Grid>
4、添加 P/Invoke 调用定义
在 MainWindow.xaml.cs 中声明外部函数:
[DllImport("Lib1.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int Start();
[DllImport("Lib1.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetGreetText(IntPtr greetText, int charCount);
✅ 调用方式与 Win32 API 完全一致。
5、启动时调用 Start()
在窗口加载时启动 ASP.NET Core 服务:
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Start(); // 启动 HTTP 服务
}
6、实现按钮点击更新文本
private unsafe void UpdateButton_OnClick(object sender, RoutedEventArgs e)
{
var greetText = GreetTextBox.Text;
fixed (char* c = greetText)
{
SetGreetText(new IntPtr(c), greetText.Length);
}
}
🔐 使用
fixed固定字符串内存,获取指针后传递给原生函数。
第五步:测试验证
1、运行 WPF 应用
2、使用 .http 文件或浏览器访问 http://localhost:5000/
初始返回:
Hello from Lib1!
3、修改文本框内容,点击【更新】按钮
4、再次请求接口,返回内容已更新!
成功实现 WPF 与 AOT 版 ASP.NET Core 的双向通信!
总结
本文实现:
-
使用 AOT 将 ASP.NET Core 编译为独立 DLL
-
通过
UnmanagedCallersOnly导出函数 -
在 WPF 中通过
DllImport调用并传参 -
实现 UI 与 HTTP 服务分离协作
适用场景:
- 桌面应用需提供本地 API 接口(如调试、插件、配置)
- 嵌入式设备中的小型 Web 控制台
- 需要最小化部署体积的服务模块
获取完整源码
由于仓库较大,建议使用部分拉取方式:
# 创建空文件夹并进入
mkdir wpf-aspnetcore-aot
cd wpf-aspnetcore-aot
# 初始化并拉取指定提交
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
若无法访问 Gitee,请切换至 GitHub:
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
进入目录:WPFDemo/BarnemwheanejayHelbellacall 即可查看完整代码。
后续优化建议
1、避免 Task.Run:应使用专用线程或异步调度器运行 app.Run(),避免占用线程池。
2、增加 Stop 接口:导出 Stop 函数,优雅关闭 Web 服务。
3、错误处理:增加返回码、异常日志等机制。
4、跨平台支持:发布 linux-x64 或 osx-x64 版本供其他平台调用。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:lindexi
出处:.cnblogs.com/lindexi/p/19049877
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!