☰
Windows-universal-samples 的 Custom Capability 示例:UWP 自定义功能的声明、签名与设备/服务访问实战
2026/9/25 14:29:48 网站建设 项目流程
  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

UWP(Universal Windows Platform)应用的能力(Capability)体系分为普通能力与受限能力两大类,而本仓库中的Custom Capability 示例(即archived/CustomCapability/README.md所对应的样例)演示的是第三类——自定义功能(Custom Capabilities):由 OEM/驱动开发者自定义、需要在驱动侧授权并通过 SCCD 文件签发的受限能力。通过本文,你将掌握如何在应用清单中声明自定义能力、如何用其连接受保护的 NT 服务(RPC)、如何访问仅对特定应用开放的 OSR USB FX-2 自定义设备(IOCTL/流读写/异步事件),以及如何借助自定义能力读写 SMBIOS/UEFI 固件变量,并了解配套的构建、部署与运行前提。

自定义能力(Custom Capability)是什么

普通的 UWP 能力(如microphone、picturesLibrary)由系统预定义、直接写在清单即可生效;受限能力(Restricted Capability,如smbios、protectedApp)则需要特殊授权。而自定义能力是 Windows 10 引入的一种机制:能力名称、语义和授权对象全部由能力拥有者(通常是硬件驱动开发者或系统服务提供者)自行定义,并通过一个SCCD(Custom Capability Descriptor)文件声明哪些应用包(按包族名 + 证书签名哈希)被授权使用该能力。

archived/CustomCapability/README.md(以及当前活跃版本 Samples/CustomCapability/README.md)明确指出:示例中附带的 SCCD 文件并非有效签名版本,仅用于教学演示;只要开发机处于“开发者模式”(Developer Mode),应用依然可以完成部署运行。真正的产品应用中,必须按照微软文档“Custom Capabilities for Universal Windows Platform apps”的流程申请并获得正确签名的 SCCD 文件。

示例中一共使用了两个自定义能力:

  • microsoft.hsaTestCustomCapability_q536wpkpf5cy2:用于访问示例 NT 服务与 OSR FX-2 自定义设备;
  • microsoft.firmwareRead_cw5n1h2txyewy/microsoft.firmwareWrite_cw5n1h2txyewy:用于读写 UEFI 固件变量。

从随附的 SCCD 文件可以直观看到能力描述符的结构(archived/CustomCapability/js/CustomCapability.SCCD,C++ 版本见 Samples/CustomCapability/cpp/CustomCapability.SCCD):

<?xml version="1.0" encoding="utf-8"?> <CustomCapabilityDescriptor xmlns="http://schemas.microsoft.com/appx/2016/sccd" xmlns:s="http://schemas.microsoft.com/appx/2016/sccd"> <CustomCapabilities> <CustomCapability Name="microsoft.firmwareRead_cw5n1h2txyewy"></CustomCapability> <CustomCapability Name="microsoft.hsaTestCustomCapability_q536wpkpf5cy2"></CustomCapability> </CustomCapabilities> <AuthorizedEntities> <AuthorizedEntity AppPackageFamilyName="Microsoft.SDKSamples.CustomCapability.JS_8wekyb3d8bbwe" CertificateSignatureHash="ca9fc964db7e0c2938778f4559946833e7a8cfde0f3eaa07650766d4764e86c4"></AuthorizedEntity> <AuthorizedEntity AppPackageFamilyName="Microsoft.SDKSamples.CustomCapability.JS_8wekyb3d8bbwe" CertificateSignatureHash="c39c58eff76e19113531f0776e2f5b7c34e5172f5d62aa31482f9e37b32ec242"></AuthorizedEntity> </AuthorizedEntities> <Catalog>xxxx</Catalog> </CustomCapabilityDescriptor>

其语义是:声明两个自定义能力(<CustomCapabilities>节点),并声明两个授权实体(<AuthorizedEntities>节点)——每个实体由AppPackageFamilyName(应用包族名)与CertificateSignatureHash(签名证书哈希)共同锁定。驱动或服务端在签发 Security Descriptor 时会依据这份授权清单,仅放行匹配的包。

在应用清单中声明自定义能力

自定义能力通过uap4命名空间下的CustomCapability元素声明(uap4 对应 Windows 10 版本 1703 及更高)。以 JavaScript 版本清单 archived/CustomCapability/js/package.appxmanifest 为例,其Capabilities节如下:

<Capabilities> <rescap:Capability Name="smbios" /> <!-- Enable "protectedApp" capability before store submission to access firmware variables in production environment --> <!-- <rescap:Capability Name="protectedApp" /> --> <uap4:CustomCapability Name="microsoft.firmwareRead_cw5n1h2txyewy" /> <uap4:CustomCapability Name="microsoft.hsaTestCustomCapability_q536wpkpf5cy2" /> </Capabilities>

要点解读:

  • uap4:CustomCapability声明的能力名称必须与 SCCD 文件中<CustomCapability Name="...">完全一致;
  • smbios与protectedApp是受限能力(rescap命名空间)。示例默认只启用了smbios,protectedApp被注释掉——生产环境访问固件变量前需要取消注释,并配合 INTEGRITYCHECK 触发商店对受保护应用的强制签名(详见下文“固件访问”小节);
  • 清单同时还注册了一个windows.backgroundTasks扩展(Osrusbfx2Task.ConnectedTask,Task Type="systemEvent"),用于接收驱动在 OSR FX-2 设备接入时引发的自定义系统事件(详见“自定义系统事件触发器”小节)。C++ 版本的清单 Samples/CustomCapability/cpp/Package.appxmanifest 结构完全一致,仅包名/Publisher 不同。

场景一:用自定义能力连接 NT 服务(RPC)

这是示例中架构最完整的场景:一个 UWP 客户端(CustomCapability.exe)通过 RPC 连接一个本地 NT 服务(RpcServer.exe)。为演示起见,该服务从“虚拟设备”读取数据。

通信流程

原文档给出了如下时序图,完整描述了 UI 操作与 RPC 调用的对应关系:

CLIENT SERVER ------------------------ ----------------- | | | | | CustomCapability.exe | | RpcServer.exe | | | | | ------------------------ ----------------- | | | | Click Start button |-------------StartMetering-------------->| | [Blocking call] | | | | | Updates Sample |<------------MeteringData----------------| text box | [Sent at choosen sample period] | | | | | Move the sample |-----------SetSamplePeriod-------------->| period slider |<-------SetSamplePeriod completes--------| | | Click Stop button |-------------StopMetering--------------->| |<--------StopMetering completes----------| | | |<-------StartMetering completes----------| | |

即:点击Start发起阻塞式的StartMetering调用 → 服务按选定采样周期通过 RPC 回调MeteringData推送数据 → UI 文本框实时刷新;拖动sample period 滑块调用SetSamplePeriod动态调整采样周期;点击Stop调用StopMetering结束采集,最后StartMetering返回完成。

RPC 接口定义

RPC 接口定义在 Samples/CustomCapability/Service/Interface/RpcInterface.Idl 中,示例核心方法如下:

[uuid (F72945BF-CB3E-403E-B198-6149328304EF), // You must change this when you change the interface version(1.0), pointer_default(unique), ] interface RpcInterface { // context_handle_noserialize in acf for RPC to call rundown when the client goes away typedef [context_handle] void* PCONTEXT_HANDLE_TYPE; typedef [ref] PCONTEXT_HANDLE_TYPE * PPCONTEXT_HANDLE_TYPE; // // RPC methods to retrieve/clean client context // void RemoteOpen([in] handle_t hBinding, [out] PPCONTEXT_HANDLE_TYPE pphContext); void RemoteClose([in, out] PPCONTEXT_HANDLE_TYPE pphContext); // // Metering Interface // void StartMetering( [in] PCONTEXT_HANDLE_TYPE phContext, [in] __int64 samplePeriod, [in, optional] __int64 context); void SetSamplePeriod( [in] PCONTEXT_HANDLE_TYPE phContext, [in] __int64 samplePeriod); void StopMetering([in] PCONTEXT_HANDLE_TYPE phContext); [callback] void MeteringDataEvent( [in] __int64 data, [in, optional] __int64 context); }

值得注意的实现细节:

  • PCONTEXT_HANDLE_TYPE是context handle(上下文句柄),配合.acf文件中的context_handle_noserialize,可在客户端断开时触发 RPC run-down,由服务端自动清理客户端上下文;
  • [callback]标注的MeteringDataEvent是反向回调——服务端主动向客户端推送计量数据;
  • 该文件第 15 行的 uuid 是接口标识,一旦修改接口定义必须同步更换新 uuid(详见原文档“Modifying the sample”一节)。

服务端的安全模型:由能力字符串派生 SID

服务端(Samples/CustomCapability/Service/Server 目录,包含RpcServer.cpp、HsaService.cpp、Metering.cpp、ServiceInstaller.cpp等)负责两件关键事:

  1. 使用DeriveCapabilitySidsFromName函数把能力字符串(如microsoft.hsaTestCustomCapability_q536wpkpf5cy2)转换为对应的SID;
  2. 用该 SID 创建 RPC 端点的Security Descriptor(安全描述符)。

只有声明了该自定义能力且被 SCCD 授权的应用,其进程令牌才携带对应的能力 SID,才能通过 RPC 端点的 ACL 校验,从而建立连接。这就是“自定义能力控制 NT 服务访问权”的本质。

运行 NT 服务场景的前置条件

在运行“Connect to an NT service”场景前,必须先把服务跑起来:

  1. 先编译示例的服务端部分——该部分依赖Windows SDK for Desktop C++ Apps,因此需要先安装对应桌面 C++ 开发组件;
  2. 以管理员权限打开命令行,用下面两种方式之一启动服务:
    • 作为系统服务安装并启动:rpcserver.exe -install完成安装,随后用sc start hsaservice启动(服务名为hsaservice);
    • 直接以控制台模式运行:rpcserver.exe -console。

场景二:用自定义能力访问自定义设备(OSR USB FX-2)

这一组场景演示如何用自定义能力访问OSR USB FX-2 Learning Kit(OSR 出品的 USB 学习板)。设备驱动代码不在本仓库,位于微软 Windows-driver-samples 仓库的usb/umdf2_fx2/driver目录;驱动必须为Windows 10 1703 或更高版本构建,才能支持示例使用的自定义能力(驱动侧需通过 INF/代码把设备接口标记为受限,并授权给对应的能力 SID)。

连接设备:DeviceWatcher + CustomDevice.FromIdAsync

“Connect to the OSR FX-2 device”场景演示了四件事:

  • 在清单中声明microsoft.hsaTestCustomCapability_q536wpkpf5cy2(见上文清单);
  • 更新驱动代码或 INF,使设备接口通过自定义能力开放访问;
  • 用DeviceWatcher按设备接口 GUID 枚举 OSR FX-2 设备;
  • 用CustomDevice.FromIdAsync打开特定设备实例。

从 C# 源码 Samples/CustomCapability/cs/DeviceList.cs 可以看到枚举器的构建方式:先用CustomDevice.GetDeviceSelector(Fx2Driver.DeviceInterfaceGuid)构造基于接口 GUID 的 AQS 选择器,再通过DeviceInformation.CreateWatcher创建DeviceWatcher,并监听Added/Removed/EnumerationCompleted事件维护设备列表;同时还会在应用挂起(Suspending)时停止 watcher、恢复(Resuming)时重启,避免后台占用。

JavaScript 版(archived/CustomCapability/js/js/scenario2_deviceConnect.js)展示了打开设备的调用方式:

var p = Windows.Devices.Custom.CustomDevice.fromIdAsync( id, Windows.Devices.Custom.DeviceAccessMode.readWrite, Windows.Devices.Custom.DeviceSharingMode.exclusive );

其中DeviceAccessMode.readWrite表示以读写方式打开,DeviceSharingMode.exclusive表示独占共享模式。该文件的diagnoseConnectionError函数还总结了最常见的四种连接失败原因:设备元数据未向任何应用授予自定义特权访问、设备元数据与应用包在应用名/发布者 ID 上不一致、设备接口未被驱动正确标记为受限、设备已断开。

设备的接口 GUID 与 IOCTL 代码定义在 Samples/CustomCapability/cs/Fx2Driver.cs(C++ 对应 Samples/CustomCapability/cpp/Fx2Driver.h):

public static readonly Guid DeviceInterfaceGuid = new Guid("573E8C73-0CB4-4471-A1BF-FAB26C31D384");

发送 IOCTL:读写 7 段 LED

“Send IOCTLs to the device”场景演示向设备发送 I/O 控制码:

  • 发送 IOCTL 设置 7 段 LED 的值(SetSevenSegmentDisplay);
  • 发送 IOCTL 读取 7 段 LED 的值(GetSevenSegmentDisplay)。

I/O 控制码的值与含义由设备自身定义。从 Samples/CustomCapability/cs/Fx2Driver.cs 可以看到它们是用IOControlCode构造的:

public const ushort DeviceType = 65500; public const ushort FunctionBase = 0x800; public static IOControlCode SetSevenSegmentDisplay = new IOControlCode(DeviceType, FunctionBase + 8, IOControlAccessMode.Write, IOControlBufferingMethod.Buffered); public static IOControlCode GetSevenSegmentDisplay = new IOControlCode(DeviceType, FunctionBase + 7, IOControlAccessMode.Read, IOControlBufferingMethod.Buffered);

IOControlCode的四个参数分别对应 CTL_CODE 的设备类型、功能号、访问模式(Read/Write)与缓冲方式(Buffered 等);文件里同时维护了 0~9 的七段数码管编码表(SevenSegmentValues),并提供DigitToSevenSegment/SevenSegmentToDigit双向换算。

处理异步设备事件:DIP 开关

“Handle asynchronous device events”场景演示:

  • 发送一个直到 DIP 开关发生变化才完成的 IOCTL(GetInterruptMessage,使用IOControlBufferingMethod.DirectOutput);
  • 取消正在等待开关变化的任务;
  • 发送 IOCTL 读取开关的当前状态(ReadSwitches)。

操作方式:点击“Begin Receiving Switch Change Events”后拨动 DIP 开关,应用会在开关状态变化时显示当前状态;也可以直接点击“Get Switch State”立即读取开关状态。这一模式展示了“设备中断型”异步事件如何在 UWP 中通过可取消的 I/O 任务实现。

读写操作:设备内部存储

“Read and Write operations”场景演示:

  • 向设备的OutputStream写入数据(写入设备内部内存);
  • 从设备的InputStream读取数据(读取设备内部内存)。

每次点击“Write Block”会向设备写入一条消息,点击“Read Block”则读取一条消息。OSR FX-2 内置4 个消息缓冲区:缓冲区满时,下一次写操作会阻塞等待,直到有消息被读出腾出空间;缓冲区空时,下一次读操作会阻塞等待,直到有消息写入。这一机制体现了流式接口基于缓冲区的背压(backpressure)语义。

场景三:触发自定义系统事件(CustomSystemEventTrigger)

“Raising Custom System Event Trigger”场景演示如何使用CustomSystemEventTrigger类型的后台任务:当 OSR FX-2 设备接入系统时,由驱动侧抛出一个自定义系统事件,进而触发应用注册的后台任务。事件触发代码位于 OSR FX-2 的 KMDF 驱动(Windows-driver-samples 仓库usb/kmdf_fx2,需为Windows 10 1803 或更高版本构建)。

这一机制的意义在于:自定义设备/NT 服务可以主动向系统汇报事件,从而唤醒 UWP 应用的后台任务,而无需应用常驻或轮询。应用侧只需在清单中注册windows.backgroundTasks扩展(本示例为Osrusbfx2Task.ConnectedTask),并在代码里注册对应触发器类型的后台任务。

场景四:固件访问(SMBIOS 与 UEFI 变量)

“Firmware access”场景演示通过受限能力与自定义能力访问固件:

  • 声明受限能力smbios后,应用即可读取 SMBIOS 信息——调用桌面 APIGetSystemFirmwareTable与EnumSystemFirmwareTables,并以'RSMB'(Raw SMBIOS)作为表提供者(provider)参数;
  • 访问 UEFI 变量则使用以下两个自定义能力:
    • microsoft.firmwareRead_cw5n1h2txyewy:通过GetFirmwareEnvironmentVariable读取 UEFI 变量;
    • microsoft.firmwareWrite_cw5n1h2txyewy:通过SetFirmwareEnvironmentVariable读写 UEFI 变量。

原文档特别强调 UEFI 访问的三重额外前提:

  1. 应用还必须声明protectedApp受限能力;
  2. 项目需启用INTEGRITYCHECK链接选项(强制签名校验),这会在商店提交时为受保护应用触发必要的商店签名流程;目前INTEGRITYCHECK 只能在 C++ 项目属性中启用;
  3. UEFI 变量仅在应用由属于 Administrators 组的用户使用时才能访问。

从 C++ 版本清单 Samples/CustomCapability/cpp/Package.appxmanifest 可以看到smbios已启用、protectedApp以注释形式保留,便于开发者按上述流程自行开启。

构建、部署与运行

原文档给出的系统要求与构建步骤如下:

  • 系统要求(对应 JavaScript 归档版):Client/Phone 为 Windows 10 version 1703,Server 为 Windows Server 2016 Technical Preview;活跃版本要求 Windows 10 build 15063 或更高(Samples/CustomCapability/README.md),并需 Visual Studio 构建、Windows 10 运行;
  • 构建:解压完整归档(不要只解压单个示例文件夹)→ 用 Visual Studio 打开对应语言的.sln(JavaScript 归档版对应 archived/CustomCapability/js/CustomCapability.sln)→ 按Ctrl+Shift+B或选择Build > Build Solution;
  • 仅部署:选择Build > Deploy Solution;
  • 部署并运行:按F5(或Debug > Start Debugging)调试运行;按Ctrl+F5(或Debug > Start Without Debugging)免调试运行;运行“Connect to an NT service”场景前需先按上文步骤启动 NT 服务。

修改示例的注意事项:如果修改了 RPC 接口,务必同步更换 Samples/CustomCapability/Service/Interface/RpcInterface.Idl 第 15 行的接口 ID,因为 RPC 接口 ID 必须全局唯一;产品化时还需要用正确签名的 SCCD 替换示例中的无效 SCCD。

相关示例与延伸阅读

原文档在 Related topics 中给出的关联示例(活跃版本位于 Samples/CustomCapability/README.md 的 Related samples 一节)同样围绕“自定义设备访问”主题:

  • IoT-GPIO
  • IoT-I2C
  • IoT-SPI
  • Custom HID device access
  • Custom serial device access
  • Custom USB device access

此外,JavaScript 归档版(archived/CustomCapability)与活跃版(Samples/CustomCapability)互为镜像:前者保留在archived目录中以供查阅,后者则持续维护并增加了 C++/C# 实现。核心 API 是Windows.Devices.Custom.CustomDevice运行时类(FromIdAsync、GetDeviceSelector、IOControlCode、InputStream/OutputStream等),可结合官方“Custom Capabilities for Universal Windows Platform apps”与“Hardware access for Universal Windows Platform apps”文档进一步理解能力申请与硬件访问的整体流程。

  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询