- 示例工程
【免费下载链接】Windows-universal-samples
API samples for the Universal Windows Platform.
本篇技术指南以 Samples/AppServices/README.md 为骨架,结合 Windows-universal-samples 仓库中 C# 与 C++/WinRT 两个版本的完整实现源码,系统讲解 UWP「应用服务(App Service)」的声明、实现、连接、消息收发与远程调用全流程。读完本文,你将掌握如何用AppServiceConnection让一个应用向另一个应用(甚至另一台电脑)暴露可调用的服务,并能在自己的 UWP 项目中复刻「打开即用」与「长连接复用」两种典型通信模式。
示例概览:一次跨应用调用的最小闭环
App Services(应用服务)是 UWP 提供的应用间通信机制:一个应用(Provider,服务提供方)把一段能力包装成服务并注册到系统,另一个应用(Client,客户端)通过AppServiceConnection发现并调用它,消息以ValueSet(键值对字典)为载体传递。整个过程无需两个应用直接握手 socket,系统负责把客户端进程与服务进程连接起来。
本示例由两个 UWP 项目外加一个后台任务组成(见 Samples/AppServices 目录):
| 项目 | 职责 | C# 路径 | C++/WinRT 路径 |
|---|---|---|---|
| AppServicesProvider | 服务提供方,注册名为com.microsoft.randomnumbergenerator的应用服务 | cs/AppServicesProvider | cppwinrt/AppServicesProvider |
| RandomNumberService | 承载服务逻辑的 Windows 运行时后台任务(IBackgroundTask) | cs/RandomNumberService/RandomNumberGeneratorTask.cs | cppwinrt/RandomNumberService/RandomNumberGeneratorTask.cpp |
| AppServicesClient | 服务调用方,内置两个场景页 | cs/AppServicesClient | cppwinrt/AppServicesClient |
服务本身的功能很纯粹:接收客户端传入的最小值/最大值,返回一个区间内的随机整数。客户端则演示了两种连接策略:
- 打开-连接-关闭(OpenCloseConnection):调用一次就建立连接、发送消息、取回结果并立即释放连接,对应 OpenCloseConnectionScenario.xaml;
- 保持连接(KeepConnectionOpen):连接打开后持续复用,直到用户显式关闭,对应 KeepConnectionOpenScenario.xaml。
两个场景的 XAML 界面(最小值、最大值、结果三个控件)存放在共享目录 Samples/AppServices/shared,由各语言的代码后置文件驱动逻辑,这正是 Windows-universal-samples 仓库典型的「共享 UI + 多语言实现」组织方式。
服务提供方:两步声明一个可被调用的应用服务
要让系统知道某个应用「提供」了服务,必须完成两件事:在清单中声明扩展,实现后台任务并挂接RequestReceived事件。
1. 清单声明(Package.appxmanifest)
以 C# 版本的 cs/AppServicesProvider/Package.appxmanifest 为例,关键片段如下:
<Identity Name="Microsoft.SDKSamples.AppServicesProvider.CS" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" /> ... <Extensions> <uap:Extension Category="windows.appService" EntryPoint="RandomNumberService.RandomNumberGeneratorTask"> <uap3:AppService Name="com.microsoft.randomnumbergenerator" SupportsRemoteSystems="true"/> </uap:Extension> </Extensions>逐项说明:
uap:Extension的Category="windows.appService"声明这是一个应用服务扩展;EntryPoint指向后台任务类RandomNumberService.RandomNumberGeneratorTask,它必须实现IBackgroundTask;uap3:AppService的Name是服务的全局标识符,客户端连接时必须精确匹配这个字符串(本示例为com.microsoft.randomnumbergenerator,采用了反向域名命名惯例);SupportsRemoteSystems="true"允许其他电脑上的客户端通过远程系统框架连接本服务,这是 README 特别强调的声明,也是与 Samples/RemoteSystems 示例联动的关键开关。
同样地,C++/WinRT 版本的清单 cppwinrt/AppServicesProvider/Package.appxmanifest 结构完全一致,仅Identity.Name变为Microsoft.SDKSamples.AppServicesProvider.CPPWINRT。注意两个清单的TargetDeviceFamily都声明了MinVersion="10.0.14393.0"、MaxVersionTested="10.0.22621.0",说明该示例面向 Windows 10 周年更新(14393)及以上的 Universal 平台。
2. 后台任务:挂接请求并回复
服务本体是 RandomNumberGeneratorTask.cs 中的IBackgroundTask,核心逻辑浓缩在Run与OnRequestReceived两个方法中:
public void Run(IBackgroundTaskInstance taskInstance) { // 1. 获取服务延迟(deferral),防止后台任务在等待期间被系统终止 serviceDeferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnTaskCanceled; // 2. 初始化随机数生成器 randomNumberGenerator = new Random((int)DateTime.Now.Ticks); // 3. 从触发器详情中取出系统注入的 AppServiceConnection var details = taskInstance.TriggerDetails as AppServiceTriggerDetails; connection = details.AppServiceConnection; // 4. 监听客户端发来的请求 connection.RequestReceived += OnRequestReceived; }请求处理函数拿到AppServiceRequestReceivedEventArgs后,先取消息延迟(保证能安全地使用异步 API 回复),再解析ValueSet参数并回写结果:
async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var messageDeferral = args.GetDeferral(); // 消息级延迟 try { var input = args.Request.Message; // 客户端传来的 ValueSet int minValue = (int)input["minvalue"]; int maxValue = (int)input["maxvalue"]; var result = new ValueSet(); result.Add("result", randomNumberGenerator.Next(minValue, maxValue)); await args.Request.SendResponseAsync(result); // 回复客户端 } finally { messageDeferral.Complete(); // 通知平台本次响应处理完毕 } }两个延迟(deferral)是应用服务编程中最重要的细节:任务级延迟(taskInstance.GetDeferral())让后台任务在连接生命周期内不被系统回收;消息级延迟(args.GetDeferral())则保证异步的SendResponseAsync执行完之前,平台不会判定响应超时。任务被取消(OnTaskCanceled)时则要释放连接并Complete服务延迟,形成对称的清理路径。
C++/WinRT 版本 RandomNumberGeneratorTask.cpp 的职责划分与 C# 完全一致,只是 API 形态换成投影类型:事件用{ get_weak(), &... }弱引用回调防悬挂、ValueSet用Insert/TryLookup、随机数改用<random>的uniform_int_distribution,返回类型是fire_and_forget协程;并且它对maxvalue < minvalue的异常输入做了防御性兜底(把最大值钳制为最小值),可作为健壮性处理的参考。两种语言都遵循「Run 建连接 → RequestReceived 响应 → 取消时清理」这一固定流程。
客户端场景一:打开连接、调用一次、立即关闭
对应 OpenCloseConnectionScenario.xaml.cs,这是应用服务最简调用范式:用using块把连接的生命周期限制在一次调用内。
连接建立阶段——设置服务名与包系列名(PFN),随后OpenAsync:
using (var connection = new AppServiceConnection()) { connection.AppServiceName = "com.microsoft.randomnumbergenerator"; connection.PackageFamilyName = "Microsoft.SDKSamples.AppServicesProvider.CS_8wekyb3d8bbwe"; AppServiceConnectionStatus status = await connection.OpenAsync(); ... }两个标识符缺一不可,其来源分别是:
AppServiceName:与提供方清单中uap3:AppService Name完全一致的字符串;PackageFamilyName:提供方应用的程序包系列名。提供方自带一个 ShowPackageFamilyName.xaml.cs 辅助页面,通过Package.Current.Id.FamilyName直接读出当前包的 PFN 展示在界面上——当提供方部署到你的设备后,PFN 以该页面显示为准,代码里硬编码的..._8wekyb3d8bbwe后缀对应微软 SDK 签名证书。
OpenAsync的返回值需要完整处理,示例对每种失败原因都给出了可操作的提示:
AppServiceConnectionStatus | 含义与排查方向(源自源码注释/提示文案) |
|---|---|
Success | 连接成功 |
AppNotInstalled | 提供方应用未部署到设备,需先部署 AppServicesProvider |
AppUnavailable | 提供方不可用,可能正在更新,或安装在已不可用的可移动设备上 |
AppServiceUnavailable | 提供方已安装,但未提供AppServiceName指定的服务(查清单声明) |
Unknown | 未知错误 |
消息收发阶段——用ValueSet装载参数并发送:
var inputs = new ValueSet(); inputs.Add("minvalue", minValueInput); inputs.Add("maxvalue", maxValueInput); AppServiceResponse response = await connection.SendMessageAsync(inputs); if (response.Status == AppServiceResponseStatus.Success && response.Message.ContainsKey("result")) { var resultText = response.Message["result"].ToString(); Result.Text = resultText; }SendMessageAsync返回的AppServiceResponse同样需要按状态分支处理:Failure表示服务未能确认消息(可能已被终止或RequestReceived处理器异常)、ResourceLimitsExceeded表示服务超出系统分配的资源而被终止、Unknown为未知失败。UI 侧则在GenerateRandomNumber_Click里先对文本框做int.TryParse校验,并强制maxValue > minValue,保证发往服务的参数永远合法——这些校验逻辑与 XAML 中的 OpenCloseConnectionScenario.xaml 控件一一对应。
客户端场景二:长连接复用与生命周期管理
对应 KeepConnectionOpenScenario.xaml.cs。与场景一不同,这里把AppServiceConnection提升为页面级字段,连接只建立一次,可被多次SendMessageAsync复用:
private AppServiceConnection connection; // 打开连接:防重入 + 注册 ServiceClosed if (connection != null) { rootPage.NotifyUser("A connection already exists", NotifyType.ErrorMessage); return; } connection = new AppServiceConnection(); connection.AppServiceName = "com.microsoft.randomnumbergenerator"; connection.PackageFamilyName = "Microsoft.SDKSamples.AppServicesProvider.CS_8wekyb3d8bbwe"; connection.ServiceClosed += Connection_ServiceClosed; AppServiceConnectionStatus status = await connection.OpenAsync(); // 注意:await 期间连接可能已被 ServiceClosed 置空,必须判空 if (connection == null) { rootPage.NotifyUser("Connection was closed", NotifyType.ErrorMessage); return; }这段代码示范了长连接场景下最容易踩的三个坑,源码里都有对应处理:
- 防重入:点击「Open Connection」前先检查
connection != null,避免重复建连; await竞态:await connection.OpenAsync()挂起期间,用户可能已点击关闭、ServiceClosed可能已触发,因此恢复执行后必须先判空再继续;- 服务端断开通知:订阅
ServiceClosed事件,在回调里通过Dispatcher.RunAsync切回 UI 线程,Dispose()并置空连接引用:
private async void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { if (connection != null) { connection.Dispose(); connection = null; } }); }关闭路径同样值得注意:CloseConnection_Click直接Dispose()连接并置空,而页面级清理放在OnNavigatedFrom中,保证离开场景页时连接一定被释放,不留悬挂引用。发送消息的方法体与场景一高度相似,只是在发送前先校验connection == null并给出「You need to open a connection before trying to generate a random number.」的引导提示,形成完整的 UX 闭环。
跨设备:SupportsRemoteSystems 与 RemoteSystems 示例的联动
README 明确指出:RemoteSystems 示例包含一个从另一台电脑连接本服务的场景,而本示例正是通过在清单中声明SupportsRemoteSystems="true"来允许这种远程连接。两处仓库证据相互印证:
- 提供方清单(C# 版与 C++/WinRT 版)都带
SupportsRemoteSystems="true"; - 远程客户端实现在 Samples/RemoteSystems/cs/Scenario3_LaunchAppServices.xaml.cs:它先通过
RemoteSystem发现选定远端设备,构造RemoteSystemConnectionRequest,再用同一个AppServiceConnection对象调用OpenRemoteAsync而不是OpenAsync:
RemoteSystemConnectionRequest connectionRequest = new RemoteSystemConnectionRequest(selectedSystem); using (AppServiceConnection connection = new AppServiceConnection { AppServiceName = "com.microsoft.randomnumbergenerator", PackageFamilyName = "Microsoft.SDKSamples.AppServicesProvider.CS_8wekyb3d8bbwe" }) { AppServiceConnectionStatus status = await connection.OpenRemoteAsync(connectionRequest); if (status == AppServiceConnectionStatus.Success) { // 连接成功后即可像本地一样 SendMessageAsync await SendMessageToRemoteAppServiceAsync(connection); } }可见客户端侧的编程模型在本地与远程之间完全一致——只需把OpenAsync换成OpenRemoteAsync并传入连接请求。这也解释了为什么 README 建议把 AppServices 与 RemoteSystems 两个示例搭配学习:前者是服务的「提供端 + 本地消费端」,后者是服务的「远程消费端」。
构建与运行
环境与系统要求
README 列出的系统要求为:客户端:Windows 10;服务器:Windows Server 2016 Technical Preview;手机:Windows 10。结合清单中的TargetDeviceFamily(MinVersion="10.0.14393.0"),构建环境需为 Windows 10 周年更新或更高版本,并使用 Visual Studio 与 Windows 开发者工具。
构建步骤
- 若从 ZIP 下载整个示例集合,务必解压完整归档(不是只解压目标示例文件夹),因为示例依赖仓库根目录的 SharedContent 共享内容(参见 README front-matter 中的
extendedZipContent对SharedContent与LICENSE的引用); - 启动 Visual Studio,选择File > Open > Project/Solution;
- 进入 Samples/AppServices 下的子目录,按偏好选择语言版本的解决方案文件(.sln):当前仓库提供cs(C#)与cppwinrt(C++/WinRT)两套,与 README 的 front-matter(
languages: csharp, cpp, cppwinrt)对应;早期的 C++/CX 与 Visual Basic 版本已归档至 archived/AppServices,仅供历史参考; - 按
Ctrl+Shift+B(或Build > Build Solution)编译。
运行步骤
- 仅部署:选择Build > Deploy Solution;
- 部署并运行:
- 确保AppServicesProvider 项目已部署(否则客户端会得到
AppNotInstalled错误,源码中对此有专门提示); - 把AppServicesClient 设为启动项目;
- 按
F5(调试运行)或Ctrl+F5(不调试运行)。
- 确保AppServicesProvider 项目已部署(否则客户端会得到
两个场景的界面都要求先输入整数形式的 Minimum Value 与 Maximum Value 再点击 Generate Random Number;场景二还需先点击打开连接,这是两份客户端源码中共有的交互前提。
小结
从 README 的两个项目、两个场景出发,结合仓库源码可以看到 UWP 应用服务的完整技术栈:提供方用「清单windows.appService扩展 +IBackgroundTask+RequestReceived事件」暴露服务,客户端用「AppServiceName+PackageFamilyName定位服务、OpenAsync/OpenRemoteAsync建立连接、ValueSet收发消息、ServiceClosed与双 deferral 管理生命周期」,再加上SupportsRemoteSystems="true"即可一键扩展到跨设备调用。这套模式可直接迁移到任意「两个 UWP 应用间解耦通信」的真实需求中,例如主应用调用独立打包的扩展应用能力、IoT 场景下的控制面服务等。更多联动示例可继续阅读 Samples/RemoteSystems 的远程应用服务场景。
相关主题
- 关联示例:Samples/RemoteSystems(远程应用服务调用)
- 历史实现:archived/AppServices(C++/CX 与 Visual Basic 归档版)
- 核心 API 命名空间:
Windows.ApplicationModel.AppServices,其中AppServiceConnection用于客户端建立连接与发送消息,AppServiceTriggerDetails用于服务端接收并响应消息——两者在本示例的提供端与消费端源码中均有直接使用。
- 示例工程
【免费下载链接】Windows-universal-samples
API samples for the Universal Windows Platform.
相关推荐
Windows Universal Platform开发指南:构建跨设备应用的最佳实践
Windows Universal Platform开发指南:构建跨设备应用的最佳实践 Windows Universal Platform(UWP)是微软推出
示例工程如何快速掌握无限长视频生成:InfiniteTalk完整使用指南
如何快速掌握无限长视频生成:InfiniteTalk完整使用指南 你是否曾为传统视频生成工具的时长限制和连贯性问题而烦恼?想要制作一段自然的对话视频,却受限于软
人工智能大模型媒体生成多模态语音数字人Windows-universal-samples响应式设计:UWP应用多设备适配技巧
Windows universal samples响应式设计:UWP应用多设备适配技巧 你是否还在为UWP应用在不同设备上的显示效果不一致而烦恼?本文将通过Wi
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考