【UAP】使用 .NET Core App 編寫 UAP
眾所周知,2024年9月微軟正式宣布了 .NET Core App 的 UWP 支持,至此我們終于可以在新版 csproj 用 .NET 8 及以上編寫 UWP 了,那么我們可不可以通過修改清單的方式來讓 UWP 變成 UAP 呢?

UWP 和 UAP 使用的是同一套 WinRT API ,Windows 區分 UAP 和 UWP 的方式是看清單,只要是用 UAP 的清單就會仿真成 Win8.1 模式,于是我們只需要將清單變成 UAP 的樣子就行了。所以首先我們新建一個 .NET 9 Native AOT 的 UWP 項目

然后我們修改清單,Win8 App 清單如下,內容按需填寫,Win8.1 App 的清單可以通過在 Github 搜索OSMaxVersionTested language:XML找到,6.2是 Win8,6.3是 Win8.1 ($targetentrypoint$需配合 ApplicationEntryPoint使用)
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest">
<Identity
Name="wherewhere.CoreAppUAP"
Publisher="CN=where"
Version="0.0.1.0" />
<Properties>
<DisplayName>CoreAppUAP</DisplayName>
<PublisherDisplayName>wherewhere</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Prerequisites>
<OSMinVersion>6.2.0</OSMinVersion>
<OSMaxVersionTested>6.3.0</OSMaxVersionTested>
</Prerequisites>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<VisualElements
DisplayName="CoreAppUAP"
Logo="Assets\MediumTile.png"
SmallLogo="Assets\AppIcon.png"
Description="CoreAppUAP"
ForegroundText="light"
BackgroundColor="transparent">
<DefaultTile WideLogo="Assets\WideTile.png"/>
<SplashScreen Image="Assets\SplashScreen.png"/>
<InitialRotationPreference>
<Rotation Preference="landscape"/>
<Rotation Preference="portrait"/>
<Rotation Preference="landscapeFlipped"/>
<Rotation Preference="portraitFlipped"/>
</InitialRotationPreference>
<LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png"/>
</VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
然后我們在csproj中添加以下內容來取消引用VC++和TargetDeviceFamily
<PropertyGroup>
<AddMicrosoftVCLibsSDKReference>False</AddMicrosoftVCLibsSDKReference>
<EnableAppxWindowsUniversalTargetDeviceFamilyItem>False</EnableAppxWindowsUniversalTargetDeviceFamilyItem>
</PropertyGroup>
不過刪除了這些引用仍然會在清單生成Dependencies標簽,如果Dependencies是空的注冊時會報錯,所以我們需要添加任務來刪除清單中的Dependencies元素
<UsingTask
TaskName="RemoveDependencies"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<AppxManifestPath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Using Namespace="System.Linq" />
<Using Namespace="System.Xml.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try
{
var xdoc = XDocument.Load(AppxManifestPath);
var ns = xdoc.Root.Name.Namespace;
var dependencies = xdoc.Root.Descendants(ns + "Dependencies");
if (dependencies != null)
{
foreach (var node in dependencies.ToArray())
{
if (!node.HasElements)
{
node.Remove();
}
}
}
xdoc.Save(AppxManifestPath);
}
catch
{
Log.LogError("Failed to load Appx Manifest.");
_Success = false;
}
]]>
</Code>
</Task>
</UsingTask>
<Target
Name="RemoveDependencies"
AfterTargets="AfterGenerateAppxManifest">
<Message Importance="high" Text="RemoveDependencies" />
<RemoveDependencies AppxManifestPath="%(FinalAppxManifest.Identity)" />
</Target>
由于 XAML 編譯器編譯App.xaml時生成的入口點會使用DispatcherQueueSynchronizationContext來注冊線程上下文,這是16299才加入的 API,UAP 在獲取DispatcherQueue時會返回null,所以我們需要手動生成入口點和注冊線程上下文
首先我們需要添加DISABLE_XAML_GENERATED_MAIN來注釋自動生成的入口點
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
然后手動編寫程序入口點
public static class Program
{
public static void Main(string[] args) => Application.Start(static p => _ = new App());
}
接著我們需要手動創建一個使用CoreDispatcher的線程上下文
/// <summary>
/// Provides a synchronization context for <see cref="CoreDispatcher"/>.
/// </summary>
/// <param name="dispatcher">The <see cref="CoreDispatcher"/> to associate this <see cref="CoreDispatcherSynchronizationContext"/> with.</param>
public sealed class CoreDispatcherSynchronizationContext(CoreDispatcher dispatcher) : SynchronizationContext
{
/// <inheritdoc />
public override void Post(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
_ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => d.Invoke(state));
}
/// <inheritdoc />
public override SynchronizationContext CreateCopy() => new CoreDispatcherSynchronizationContext(dispatcher);
/// <inheritdoc />
public override void Send(SendOrPostCallback d, object? state) => throw new NotSupportedException("'SynchronizationContext.Send' is not supported.");
}
并在合適的時間注冊線程上下文,比如OnWindowCreated
protected override void OnWindowCreated(WindowCreatedEventArgs e)
{
if (SynchronizationContext.Current == null)
{
CoreDispatcherSynchronizationContext context = new(e.Window.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
}
base.OnWindowCreated(e);
}
然后就可以點運行運行了

經測試,熱重載等調試功能正常,UAP 可以調用一些與 UI 無關的新 WinRT API,甚至可以擴展標題欄,不過打包后無法成功在 Win8.1 安裝,原因未知

本文來自博客園,作者:where-where,轉載請注明原文鏈接:http://www.rzrgm.cn/wherewhere/p/18770443

浙公網安備 33010602011771號