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

按照以往的方式創建 WPF 空白應用。編輯 MainWindow.xaml 添加一點界面代碼
<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 里面添加兩個 P/Invoke 定義函數,代碼如下
[DllImport("Lib1.dll")]
private static extern int Start();
[DllImport("Lib1.dll")]
private static extern void SetGreetText(IntPtr greetText, int charCount);
通過以上代碼可見此時的對 Lib1.dll 的調用和對其他的 Win32 DLL 調用是一樣的 P/Invoke 寫法
在窗口啟動完成之后,調用 Start 啟動 ASP.NET Core 服務,代碼如下
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Start();
}
點擊更新按鈕時,將文本框的內容更新同步到 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);
}
}
如以上代碼所示,調用的時候需要將字符串對象固定,取出指針傳遞過去
如此簡單代碼就完成了 WPF 調用 AOT 版本的 ASP.NET Core 服務,開啟 HTTP 服務
嘗試運行 WPF 項目,運行之前別忘了拷貝 Lib1.dll 文件到輸出文件夾路徑,然后用 Lib1 里面自帶的 .http 文件發送請求,可見如下界面

點擊按鈕,更新一下文本內容,再次發送 http 請求,可見如下界面

通過以上界面可以知道,此時的 ASP.NET Core 部分已經收到了來自 WPF 部分的數據,且將其通過 HTTP 對外發射數據
本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個代碼倉庫比較龐大,使用以下命令行可以進行部分拉取,拉取速度比較快
先創建一個空文件夾,接著使用命令行 cd 命令進入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼。如果依然拉取不到代碼,可以發郵件向我要代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
獲取代碼之后,進入 WPFDemo/BarnemwheanejayHelbellacall 文件夾,即可獲取到源代碼
更多技術博客,請參閱 博客導航
博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請訪問 https://blog.lindexi.com/
如圖片看不見,請在瀏覽器開啟不安全http內容兼容

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

浙公網安備 33010602011771號