如何在 Windows App SDK 项目中创建 AppService 和 Background Task

发布时间 2023-08-03 21:02:13作者: 云之幻

本文基于 Windows App SDK 1.3.230724000 和 CsWinRT 2.0.3 进行介绍,后续随着版本更迭,本文的方法不见得适用,请读者酌情取用。

Windows App SDK 承继于 UWP,UWP 的一些高级功能也同样被继承了过来,比如 AppService 和 Background Task。
两者原理相似,都是应用提供的一种可以独立运行的进程外(Out of Process)程序,如果你对 Windows 桌面开发比较了解,你可能会想到一个东西,就是 COM(组件对象模型)。
App Service 相当于一层 COM 的封装,它能提供给外部应用一个编程接口。比如 A 应用提供了一个 App Service,B应用就可以在不启动A应用的情况下通过这个接口与A应用交互,相当于是一个本地的API Server,随用随停,不会给系统造成负担。

如何在 Windows App SDK 项目中创建 App Service

1. 创建应用本体

App Service 是一个 WinRT 组件,它不能独立存在,需要依附于一个桌面应用。所以首先我们就需要创建一个 Windows App SDK 项目。

如何创建 Windows App SDK 项目?这个我就不多说了, 自己看 Windows 应用 SDK

2. 创建 AppService 项目

我们没有现成的 WinRT 组件项目模板(UWP 的不能直接用)。
好在直接用现成的类库模板改造也不困难。

创建类库项目

你要确保该类库项目的 .NET 版本与应用本体的 .NET 版本一致。
然后在 Visual Studio 中双击项目文件,添加 CsWinRT 的引用。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net7.0-windows10.0.22000.0</TargetFramework>
    <RootNamespace>AppServices</RootNamespace>
    <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
    <Platforms>x64;x86;arm64</Platforms>
    <CsWinRTComponent>true</CsWinRTComponent>
    <CsWinRTWindowsMetadata>10.0.22000.0</CsWinRTWindowsMetadata>
    <PlatformTarget>x64</PlatformTarget>
    <GenerateDocumentationFile>False</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.3" />
  </ItemGroup>
</Project>

在 .NET 5 之后,WinRT 就已经被剥离出来独自更新了,本文讲述的是基于 .NET 7 的 C#/WinRT 组件创建方法,其它的还有如 C++/WinRT,Rust/WinRT 等,此处就不再赘述了。

你会注意到 PropertyGroup 中新增了几个关于 CsWinRT 的属性,包括 .NET 也要与 Windows 版本关联。这些都是必需的,先按此步骤写吧。

如果在修改项目文件后,你的项目图标左下角出现了一个蓝色图标,显示 已挂起的添加 ,并且在构建时会跳过该项目,请打开配置管理器,先将 AppServices 项目的平台切换到 x86,关闭后你就会发现蓝色角标消失,此时再打开配置管理器,切换回 x64 即可。

创建入口文件

作为一个 WinRT 组件,我们必须要有一个密闭的公开类作为我们的入口点。
移除项目自带的 Class1.cs ,创建一个新的类型 Service.cs

using Windows.ApplicationModel.Background;

namespace AppServices;

public sealed class Service : IBackgroundTask
{
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        // Create app service.
    }
}

具体的服务代码我这里节约篇幅就不写了,你可以在 创建应用服务 中找到具体的代码,UWP 的代码可以直接复制过来,最多改一下类型的命名空间,这个就没什么好说的了。

3. 引用并注册

引用类库项目

创建好了应用服务,下一步我们就要在 WinAppSDK 主项目中引用了。
需要注意的是,在类库项目文件中,我们有一条关键属性:

<CsWinRTWindowsMetadata>10.0.22000.0</CsWinRTWindowsMetadata>

它表示当前引入的 WinRT API 版本,在添加该属性后,你的类库项目中会多出三个文件,分别是 WinRT.Host.dll, WinRT.Host.dll.muiWinRT.Host.Shim.dll

如果在主项目中,你的 TargetFramework 高于 22000 这个版本,换句话说,主项目引用的 WinRT 版本和类库的 WinRT 版本不一致时,会报这个错误:

error APPX1101: 负载包含两个或更多具有相同目标路径“Microsoft.Windows.SDK.NET.dll”的文件

解决方法有两个:
其一,统一版本。
其二,添加配置文件。

统一版本自不必说,这其实也是推荐的方式。
但有时候,我们需要在主项目中需要更新的 API,主项目的 TargetFramework 必须要调到一个较高的版本上时,我们就可以用另外一种方法:添加 Directory.Build.targets

在 AppServices 项目目录中创建一个 Directory.Build.targets 文件,然后把下面的内容粘贴进去:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="CsWinRTAuthoring_AddManagedDependencies" />
</Project>

这样,重新构建项目,APPX1101 错误就不会再出现了。

修改 Package.appxmanifest

Package.appxmanifest 是包清单文件,定义了这个应用的名称、资源和功能。添加了一个 AppService 之后,我们必须在清单中声明我们这个应用有 AppService,并且要提供服务的入口点,这样在部署应用后,系统才能识别,并在有外部请求时正确地启动你的应用服务。

右键点击主项目中的 Package.appxmanifest,选择 查看代码,也可以选中文件后按 F7
打开之后,你可以按照 将应用服务扩展添加到 Package.appxmanifest 中的内容来添加。

需要注意的是,在文档中添加 AppService 使用的是 uap3 前缀,实际上在 Windows App SDK 中,直接使用 uap 前缀也是可以的。结果如下:

<Applications>
  <Application Id="App"
    Executable="$targetnametoken$.exe"
    EntryPoint="$targetentrypoint$">
     ...
     <Extensions>
       <uap:Extension Category="windows.appService" EntryPoint="AppServices.Service">
         <uap:AppService Name="com.richasy.testappservice" uap4:SupportsMultipleInstances="true"/>
       </uap:Extension>
     </Extensions>
     ...
  </Application>
</Applications>

对于 UWP 来说,到这里就结束了。但是对于 WinAppSDK 来说,还有很关键的一步。
还记得上面提到的 WinRT.Host.dll 吗?

它是 WinRT 组件的运行时,Windows 尝试启动你的 AppService 时,需要调用它来执行你的 WinRT 代码。所以我们还需要在 Package.appxmanifest 中添加一个新的扩展:

<Application>
  ...
</Application>
<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>WinRT.Host.dll</Path>
            <ActivatableClass ActivatableClassId="AppServices.Service" ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

这个 Extensions 是写在 Application 标签外的哟~

到这里,AppService 的服务注册就结束了,Background Task 注册同理,这里就不展开了。

比起 UWP 来说,多了几个步骤,不过总体不算困难。但即便如此,也折腾了我一天的时间,实在是相关资料匮乏,只能自己踩坑了。

参考资料