WPF 引用 ASP.NET Core 的 AOT 版本
現(xiàn)在 ASP.NET Core 早已支持 AOT 發(fā)布了,只是絕大部分教程都是教大家將其作為應(yīng)用發(fā)布。在本文里面,咱將嘗試進(jìn)行類(lèi)庫(kù)發(fā)布,發(fā)布之后是一個(gè) DLL 文件。通過(guò) UnmanagedCallersOnly 導(dǎo)出函數(shù)被其他應(yīng)用程序所使用
混合 WPF 和 ASP.NET Core 兩個(gè)框架到一個(gè)進(jìn)程里面是比較舒服的事情,讓 WPF 負(fù)責(zé)界面顯示邏輯和一些交互控制,讓 ASP.NET Core 負(fù)責(zé)提供 HTTP 服務(wù),各干各的,各自發(fā)揮優(yōu)勢(shì)
在上篇博客中,介紹了直接項(xiàng)目引用的方式,在一個(gè)進(jìn)程里面跑起來(lái) WPF 和 ASP.NET Core 框架,詳細(xì)請(qǐng)看 dotnet 簡(jiǎn)單方法在一個(gè)進(jìn)程內(nèi)同時(shí)跑起 WPF 和 ASP.NET Core 框架
在本文這里將著重和大家介紹在 WPF 里面調(diào)用的是 AOT 發(fā)布的 ASP.NET Core 類(lèi)庫(kù)。此方式可以將整個(gè) ASP.NET Core 負(fù)載打成一個(gè) DLL 文件,不會(huì)在輸出路徑上帶上一些 ASP.NET Core 的 DLL 文件,適用于 HTTP 服務(wù)屬于業(yè)務(wù)邊緣模塊,不想為邊緣的模塊添加依賴(lài)。也適用于讓 WPF 和 ASP.NET Core 各自依賴(lài)不同的 .NET 版本
首先通過(guò)如下命令新建支持 AOT 的 ASP.NET Core 應(yīng)用
dotnet new webapiaot -o Lib1
默認(rèn)模版創(chuàng)建的是頂層語(yǔ)句,即沒(méi)有在 Program.cs 寫(xiě) Main 函數(shù)等做法。咱需要將其進(jìn)行改造,改造包含將默認(rèn)的代碼放在一個(gè)函數(shù)里,和刪除其中的演示代碼。對(duì)于本文的演示需求來(lái)說(shuō),只需保留 MapGet 一條到 / 路徑即可,返回一個(gè)字符串字段的值,代碼如下
public static class Program
{
private static string _greetText = "Hello from Lib1!";
private static void StartInner()
{
var builder = WebApplication.CreateSlimBuilder([]);
var app = builder.Build();
app.MapGet("/", () => _greetText);
app.Run();
}
}
大家可以看到,我刪除了 Main 方法,這是因?yàn)榇隧?xiàng)目將作為類(lèi)庫(kù)發(fā)布,不能有 Main 入口函數(shù),有了也沒(méi)用。因此只將代碼放在 StartInner 里面。附帶也能看到 ASP.NET Core 非常簡(jiǎn)潔,短短 5 行有效代碼就可以完成 HTTP 服務(wù)
為了能夠調(diào)用 StartInner 開(kāi)啟輔助,咱再包裝 Start 方法,且用 UnmanagedCallersOnly 進(jìn)行公開(kāi),代碼如下
public static class Program
{
[UnmanagedCallersOnly(EntryPoint = "Start", CallConvs = [typeof(CallConvCdecl)])]
public static int Start()
{
Console.WriteLine($"Start run");
Task.Run(StartInner);
return 2;
}
}
以上代碼有一個(gè)不良實(shí)踐,那就是將 StartInner 這個(gè)長(zhǎng)時(shí)間執(zhí)行的方法放在 Task.Run 里面,這樣做將會(huì)占用線程池資源
繼續(xù)再添加一個(gè)方法,用于給 WPF 層修改返回的 _greetText 字段,代碼如下
[UnmanagedCallersOnly(EntryPoint = "SetGreetText", CallConvs = [typeof(CallConvCdecl)])]
public static void SetGreetText(IntPtr greetText, int charCount)
{
_greetText = Marshal.PtrToStringUni(greetText, charCount);
}
當(dāng)前的 .NET 的類(lèi)庫(kù) AOT 只支持基礎(chǔ)數(shù)據(jù)類(lèi)型,不能傳遞 .NET 的對(duì)象,這也就是為什么參數(shù)需要使用指針而不是字符串的原因。在后文將告訴大家簡(jiǎn)單的調(diào)用方法
如此就完成了簡(jiǎn)單的 ASP.NET Core 部分的活了,全部代碼都在 Program.cs 里面,代碼如下
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lib1;
public static class Program
{
[UnmanagedCallersOnly(EntryPoint = "Start", CallConvs = [typeof(CallConvCdecl)])]
public static int Start()
{
Console.WriteLine($"Start 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 string _greetText = "Hello from Lib1!";
private static void StartInner()
{
var builder = WebApplication.CreateSlimBuilder([]);
var app = builder.Build();
app.MapGet("/", () => _greetText);
app.Run();
}
}
由于當(dāng)前的 .NET 存在一個(gè)已知問(wèn)題,將導(dǎo)致 ASP.NET Core 作為類(lèi)庫(kù)發(fā)布之后,再被另一個(gè) .NET 應(yīng)用引用時(shí),會(huì)在控制臺(tái)輸出卡住。解決此問(wèn)題需要在 csproj 里面添加如下代碼配置,詳細(xì)請(qǐng)參閱 https://github.com/dotnet/runtime/issues/118773
<PropertyGroup>
<EventSourceSupport>false</EventSourceSupport>
</PropertyGroup>
如果不知道以上代碼應(yīng)該如何加到 csproj 上,還請(qǐng)使用本文末尾提供的命令行獲取本文的所有代碼,通過(guò)對(duì)比我的代碼了解應(yīng)該如何編寫(xiě)
完成準(zhǔn)備工作之后,執(zhí)行如下命令進(jìn)行發(fā)布
dotnet publish -r win-x64
發(fā)布完成之后,拷貝發(fā)布出來(lái)的 Lib1.dll 文件,將其放入到 WPF 應(yīng)用的輸出文件夾路徑。此時(shí)的 Lib1.dll 不是一個(gè)包含 IL 的 .NET 程序集的 DLL 文件,因此不能作為 WPF 項(xiàng)目的引用 DLL 文件,最多只能作為 None 方式作為引用如果較新則復(fù)制到輸出路徑
這樣發(fā)布出來(lái)的簡(jiǎn)單 ASP.NET Core 類(lèi)庫(kù)只有 7MB 左右,以下截圖中,只有 Lib1.dll 是必需的,其他都可以丟掉

按照以往的方式創(chuàng)建 WPF 空白應(yīng)用。編輯 MainWindow.xaml 添加一點(diǎn)界面代碼
<Grid>
<StackPanel VerticalAlignment="Center">
<Grid MinWidth="300" HorizontalAlignment="Center">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0 5 5 5"></Setter>
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="0 5 0 5"></Setter>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="歡迎詞:"></TextBlock>
<TextBox x:Name="GreetTextBox" Grid.Column="1" Text="Hello"></TextBox>
</Grid>
<Button x:Name="UpdateButton" Width="100" Height="30" Margin="10" Click="UpdateButton_OnClick">更新</Button>
</StackPanel>
</Grid>
在 MainWindow.xaml.cs 里面添加兩個(gè) P/Invoke 定義函數(shù),代碼如下
[DllImport("Lib1.dll")]
private static extern int Start();
[DllImport("Lib1.dll")]
private static extern void SetGreetText(IntPtr greetText, int charCount);
通過(guò)以上代碼可見(jiàn)此時(shí)的對(duì) Lib1.dll 的調(diào)用和對(duì)其他的 Win32 DLL 調(diào)用是一樣的 P/Invoke 寫(xiě)法
在窗口啟動(dòng)完成之后,調(diào)用 Start 啟動(dòng) ASP.NET Core 服務(wù),代碼如下
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Start();
}
點(diǎn)擊更新按鈕時(shí),將文本框的內(nèi)容更新同步到 ASP.NET Core 上,代碼如下
private unsafe void UpdateButton_OnClick(object sender, RoutedEventArgs e)
{
var greetText = GreetTextBox.Text;
fixed (char* c = greetText)
{
SetGreetText(new IntPtr(c), greetText.Length);
}
}
如以上代碼所示,調(diào)用的時(shí)候需要將字符串對(duì)象固定,取出指針傳遞過(guò)去
如此簡(jiǎn)單代碼就完成了 WPF 調(diào)用 AOT 版本的 ASP.NET Core 服務(wù),開(kāi)啟 HTTP 服務(wù)
嘗試運(yùn)行 WPF 項(xiàng)目,運(yùn)行之前別忘了拷貝 Lib1.dll 文件到輸出文件夾路徑,然后用 Lib1 里面自帶的 .http 文件發(fā)送請(qǐng)求,可見(jiàn)如下界面

點(diǎn)擊按鈕,更新一下文本內(nèi)容,再次發(fā)送 http 請(qǐng)求,可見(jiàn)如下界面

通過(guò)以上界面可以知道,此時(shí)的 ASP.NET Core 部分已經(jīng)收到了來(lái)自 WPF 部分的數(shù)據(jù),且將其通過(guò) HTTP 對(duì)外發(fā)射數(shù)據(jù)
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個(gè)代碼倉(cāng)庫(kù)比較龐大,使用以下命令行可以進(jìn)行部分拉取,拉取速度比較快
先創(chuàng)建一個(gè)空文件夾,接著使用命令行 cd 命令進(jìn)入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
以上使用的是國(guó)內(nèi)的 gitee 的源,如果 gitee 不能訪問(wèn),請(qǐng)?zhí)鎿Q為 github 的源。請(qǐng)?jiān)诿钚欣^續(xù)輸入以下代碼,將 gitee 源換成 github 源進(jìn)行拉取代碼。如果依然拉取不到代碼,可以發(fā)郵件向我要代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
獲取代碼之后,進(jìn)入 WPFDemo/BarnemwheanejayHelbellacall 文件夾,即可獲取到源代碼
更多技術(shù)博客,請(qǐng)參閱 博客導(dǎo)航
博客園博客只做備份,博客發(fā)布就不再更新,如果想看最新博客,請(qǐng)?jiān)L問(wèn) https://blog.lindexi.com/
如圖片看不見(jiàn),請(qǐng)?jiān)跒g覽器開(kāi)啟不安全http內(nèi)容兼容

本作品采用知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議進(jìn)行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但務(wù)必保留文章署名[林德熙](http://www.rzrgm.cn/lindexi)(包含鏈接:http://www.rzrgm.cn/lindexi ),不得用于商業(yè)目的,基于本文修改后的作品務(wù)必以相同的許可發(fā)布。如有任何疑問(wèn),請(qǐng)與我[聯(lián)系](mailto:lindexi_gd@163.com)。

浙公網(wǎng)安備 33010602011771號(hào)