注意
一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。
本文演示了创建一个简单源提供程序的过程,该提供程序用于注册源内容 URI 并实现 IFeedProvider 接口。 小组件板会调用此接口方法来请求自定义查询字符串参数,通常是为了支持身份验证场景。 源提供程序可以支持单个源或多个源。
要使用 C++/WinRT 实现源提供程序,请参阅在 C# Windows 应用中实现源提供程序 (C++/WinRT)。
先决条件
- 设备必须启用开发人员模式。 有关详细信息,请参阅启用用于开发的设备。
- 具有通用 Windows 平台开发工作负载的 Visual Studio 2022 或更高版本。 请确保从可选下拉列表添加 C++ (v143) 组件。
创建新的 C++/WinRT win32 控制台应用
在 Visual Studio 中,创建新的项目。 在“创建新项目”对话框中,将语言筛选器设置为“C++”,将平台筛选器设置为 Windows,然后选择“Windows 控制台应用程序 (C++/WinRT)”项目模板。 将新项目命名为“ExampleFeedProvider”。 在此演示中,请确保未选中“将解决方案和项目放在同一目录中”。 出现提示时,请将应用的目标 Windows 版本设置为版本 10.022631.2787 或更高版本。
添加对 Windows 应用 SDK 和 Windows 实现库 NuGet 包的引用
此示例使用最新稳定版 Windows 应用 SDK NuGet 包。 在“解决方案资源管理器”中,右键单击“引用”,然后选择“管理 NuGet 包...”。在 NuGet 包管理器中,选择“浏览”选项卡并搜索“Microsoft.WindowsAppSDK”。 在“版本”下拉列表中选择最新稳定版本,然后单击“安装”。
此示例还使用了 Windows 实现库 NuGet 包。 在“解决方案资源管理器”中,右键单击“引用”,然后选择“管理 NuGet 包...”。在 NuGet 包管理器中,选择“浏览”选项卡并搜索“Microsoft.Windows.ImplementationLibrary”。 在“版本”下拉列表中选择最新版本,然后单击“安装”。
在预编译头文件 pch.h 中,添加以下 include 指令。
//pch.h
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>
注意
必须首先在任何 WinRT 标头之前包括 wil/cppwinrt.h 标头。
要正确关闭源提供程序应用,需要 winrt::get_module_lock 的自定义实现。 预先声明 SignalLocalServerShutdown 方法,该方法将在 main.cpp 文件中进行定义,并将设置一个事件来指示应用退出。 将以下代码添加到 pch.h 文件中,紧跟在 #pragma once
指令下方,然后再包括其他代码。
//pch.h
#include <stdint.h>
#include <combaseapi.h>
// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();
namespace winrt
{
inline auto get_module_lock() noexcept
{
struct service_lock
{
uint32_t operator++() noexcept
{
return ::CoAddRefServerProcess();
}
uint32_t operator--() noexcept
{
const auto ref = ::CoReleaseServerProcess();
if (ref == 0)
{
SignalLocalServerShutdown();
}
return ref;
}
};
return service_lock{};
}
}
#define WINRT_CUSTOM_MODULE_LOCK
添加一个 FeedProvider 类来处理源操作
在 Visual Studio 中,右键单击“解决方案资源管理器”中的 ExampleFeedProvider
项目并选择“添加”-“类”。 在“添加类”对话框中,将类命名为“FeedProvider”,然后单击“添加”。
声明实现 IFeedProvider 接口的类
IFeedProvider 接口定义了小组件板为了启动源提供程序操作而将调用的方法。 将 FeedProvider.h 文件中的空类定义替换为以下代码。 此代码声明一个实现 IFeedProvider 接口的结构,并声明接口方法的原型。
// FeedProvider.h
#pragma once
struct FeedProvider : winrt::implements<FeedProvider, winrt::Microsoft::Windows::Widgets::Feeds::Providers::IFeedProvider>
{
FeedProvider() {}
/* IFeedrovider required functions that need to be implemented */
void OnFeedProviderEnabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedProviderEnabledArgs args);
void OnFeedProviderDisabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedProviderDisabledArgs args);
void OnFeedEnabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedEnabledArgs args);
void OnFeedDisabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedDisabledArgs args);
void OnCustomQueryParametersRequested(winrt::Microsoft::Windows::Widgets::Feeds::Providers::CustomQueryParametersRequestedArgs args);
/* IFeedProvider required functions that need to be implemented */
};
实现 IFeedProvider 方法
在接下来的几个部分中,我们将实现 IFeedProvider 接口的方法。 在深入了解接口方法之前,请在 include 指令之后将以下行添加到 FeedProvider.cpp
,以将源提供程序 API 拉取到 winrt 命名空间中,并允许访问我们在上一步中声明的映射。
注意
传递到 IFeedProvider 接口回调方法的对象仅保证在回调中有效。 不应存储对这些对象的引用,因为它们在回调上下文之外的行为未定义。
// WidgetProvider.cpp
namespace winrt
{
using namespace Microsoft::Windows::Widgets::Feeds::Providers;
}
启用Feed提供者
当小组件板主机创建与提供程序关联的源时,将调用 OnFeedProviderEnabled 方法。 在此方法的实现中,使用将传递给提供源内容的 URL 的参数生成一个查询字符串,包括任何必要的身份验证令牌。 创建一个 CustomQueryParametersUpdateOptions 实例,从标识已启用的源和查询字符串的事件参数传入 FeedProviderDefinitionId。 获取默认的 FeedManager 并调用 SetCustomQueryParameters,从而将查询字符串参数注册到小组件板。
// FeedProvider.cs
void FeedProvider::OnFeedProviderEnabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedProviderEnabledArgs args)
{
std::wstringstream wstringstream;
wstringstream << args.FeedProviderDefinitionId().c_str() << L" feed provider was enabled." << std::endl;
_putws(wstringstream.str().c_str());
auto updateOptions = winrt::CustomQueryParametersUpdateOptions(args.FeedProviderDefinitionId(), L"param1¶m2");
winrt::FeedManager::GetDefault().SetCustomQueryParameters(updateOptions);
}
供给提供者被禁用时
此提供程序的所有源都已禁用时,小组件板将调用 OnFeedProviderDisabled。 源提供程序无需执行任何操作即可响应此方法调用。 方法调用可用于遥测目的,或根据需要更新查询字符串参数或撤销身份验证令牌。 如果应用仅支持单个源提供程序,或者应用支持的所有源提供程序都已被禁用,则应用可以退出以响应此回调。
// FeedProvider.cs
void FeedProvider::OnFeedProviderDisabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedProviderDisabledArgs args)
{
std::wstringstream wstringstream;
wstringstream << args.FeedProviderDefinitionId().c_str() << L" feed provider was disabled." << std::endl;
_putws(wstringstream.str().c_str());
}
OnFeedEnabled、OnFeedDisabled
启用或禁用源时,小组件板将调用 OnFeedEnabled 和 OnFeedDisabled。 源提供程序无需执行任何操作即可响应这些方法调用。 方法调用可用于遥测目的,或根据需要更新查询字符串参数或撤销身份验证令牌。
// FeedProvider.cs
void FeedProvider::OnFeedEnabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedEnabledArgs args)
{
std::wstringstream wstringstream;
wstringstream << args.FeedDefinitionId().c_str() << L" feed was enabled." << std::endl;
_putws(wstringstream.str().c_str());
}
// FeedProvider.cs
void FeedProvider::OnFeedDisabled(winrt::Microsoft::Windows::Widgets::Feeds::Providers::FeedDisabledArgs args)
{
std::wstringstream wstringstream;
wstringstream << args.FeedDefinitionId().c_str() << L" feed was disabled." << std::endl;
_putws(wstringstream.str().c_str());
}
OnCustomQueryParametersRequested
当小组件板确定需要刷新与源提供程序关联的自定义查询参数时,将引发 OnCustomQueryParametersRequested。 例如,从远程 Web 服务提取源内容的操作失败时,可能会调用此方法。 传递到此方法的 CustomQueryParametersRequestedArgs 的 FeedProviderDefinitionId 属性指定了要为其请求查询字符串参数的源。 提供程序应重新生成查询字符串,并通过调用 SetCustomQueryParameters 将其传回小组件板。
// FeedProvider.cs
void FeedProvider::OnCustomQueryParametersRequested(winrt::Microsoft::Windows::Widgets::Feeds::Providers::CustomQueryParametersRequestedArgs args)
{
std::wstringstream wstringstream;
wstringstream << L"CustomQueryParameters were requested for " << args.FeedProviderDefinitionId().c_str() << std::endl;
_putws(wstringstream.str().c_str());
auto updateOptions = winrt::CustomQueryParametersUpdateOptions(args.FeedProviderDefinitionId(), L"param1¶m2");
winrt::FeedManager::GetDefault().SetCustomQueryParameters(updateOptions);
}
注册将按请求实例化 FeedProvider 的类工厂
将定义 FeedProvider 类的标头添加到应用的 main.cpp
文件顶部的 include 中。 我们还将在此处包括 mutex。
// main.cpp
...
#include "FeedProvider.h"
#include <mutex>
声明将触发应用程序退出的事件和将设置该事件的 SignalLocalServerShutdown 函数。 将以下代码粘贴到 main.cpp 中。
// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);
void SignalLocalServerShutdown()
{
g_shudownEvent.SetEvent();
}
然后需要创建一个 CLSID,以标识用于激活 COM 的源提供程序。 转到“工具”->“创建 GUID”,在 Visual Studio 中生成 GUID。 选择“static const GUID =”选项,单击“复制”,然后将其粘贴到 中。 使用以下 C++/WinRT 语法更新 GUID 定义,将 GUID 变量名称设置为 feed_provider_clsid。 保留 GUID 的注释版本,因为稍后在打包应用时需要此格式。
// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID feed_provider_clsid
{
0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};
将以下类工厂定义添加到 main.cpp
。 这主要是不特定于源提供程序实现的样本代码。 请注意,CoWaitForMultipleObjects 在应用退出之前会等待触发关闭事件。
// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
STDMETHODIMP CreateInstance(
::IUnknown* outer,
GUID const& iid,
void** result) noexcept final
{
*result = nullptr;
std::unique_lock lock(mutex);
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!instance)
{
instance = winrt::make<FeedProvider>();
}
return instance.as(iid, result);
}
STDMETHODIMP LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
T instance{ nullptr };
std::mutex mutex;
};
int main()
{
winrt::init_apartment();
wil::unique_com_class_object_cookie feedProviderFactory;
auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Feeds::Providers::IFeedProvider>>();
winrt::check_hresult(CoRegisterClassObject(
feed_provider_clsid,
factory.get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
feedProviderFactory.put()));
DWORD index{};
HANDLE events[] = { g_shudownEvent.get() };
winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
INFINITE,
static_cast<ULONG>(std::size(events)), events, &index));
return 0;
}
打包源提供程序应用
在当前发行版中,只有打包的应用才能注册为源提供程序。 以下步骤将详细演示打包应用并更新应用部件清单,以将应用作为源提供程序注册到操作系统的过程。
创建 MSIX 打包项目
在“解决方案资源管理器”中,右键单击所需解决方案,然后选择“添加”-“新项目...”。在“添加新项目”对话框中,选择“Windows 应用程序打包项目”模板,然后单击“下一步”。 将项目名称设置为“ExampleFeedProviderPackage”,然后单击“创建”。 出现提示时,将目标版本设置为版本 1809 或更高版本,然后单击“确定”。 然后右键单击“ExampleFeedProviderPackage”项目,再选择“添加”->“项目引用”。 选择 ExampleFeedProvider 项目,然后单击“确定”。
将 Windows 应用 SDK 包引用添加到打包项目
需要将对 Windows 应用 SDK nuget 包的引用添加到 MSIX 打包项目。 在“解决方案资源管理器”中,双击 ExampleFeedProviderPackage 项目以打开 ExampleFeedProviderPackage.wapproj 文件。 在 Project 元素中添加以下 xml。
<!--ExampleFeedProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231116003-experimentalpr">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
注意
确保 PackageReference 元素中指定的版本与在上一步中引用的最新稳定版本匹配。
如果计算机上已安装正确版本的 Windows 应用 SDK,并且你不希望在包中捆绑 SDK 运行时,则可以在 ExampleFeedProviderPackage 项目的 Package.appxmanifest 文件中指定包依赖项。
<!--Package.appxmanifest-->
...
<Dependencies>
...
<PackageDependency Name="Microsoft.WindowsAppRuntime.1.5.233430000-experimental1" MinVersion="2000.638.7.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
...
</Dependencies>
...
更新包清单
在“解决方案资源管理器”中,右键单击 文件并选择“查看代码”以打开清单 xml 文件。 接下来,需要为我们将使用的应用包扩展添加一些命名空间声明。 将以下命名空间定义添加到顶级 Package 元素。
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
在 Application 元素内,创建名为 Extensions 的新空元素。 请确保此新元素位于 uap:VisualElements 的结束标记之后。
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
需要添加的第一个扩展是 ComServer 扩展。 这会向 OS 注册可执行文件的入口点。 此扩展是打包的应用,等效于通过设置注册表项注册 COM 服务器,它并不特定于源提供程序。添加以下 com:Extension 元素作为 Extension 元素的子元素。 将 com:Class 元素的 Id 属性中的 GUID 更改为在上一步中生成的 GUID。
<!-- Package.appxmanifest -->
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="ExampleFeedProvider\ExampleFeedProvider.exe" Arguments="-RegisterProcessAsComServer" DisplayName="C++ Feed Provider App">
<com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="FeedProvider" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
然后添加将应用注册为源提供程序的扩展。 将 uap3:Extension 元素粘贴到以下代码片段中,作为 Extension 元素的子元素。 请务必将 COM 元素的 ClassId 属性替换为前面步骤中使用的 GUID。
<!-- Package.appxmanifest -->
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets.feeds" DisplayName="ContosoFeed" Id="com.examplewidgets.examplefeed" PublicFolder="Public">
<uap3:Properties>
<FeedProvider Icon="ms-appx:Assets\StoreLogo.png" Description="FeedDescription">
<Activation>
<!-- Apps exports COM interface which implements IFeedProvider -->
<CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</Activation>
<Definitions>
<Definition Id="Contoso_Feed"
DisplayName="Contoso_Feed Feed"
Description="Feed representing Contoso"
ContentUri="https://www.contoso.com/"
Icon="ms-appx:Images\StoreLogo.png">
</Definition>
<Definition Id="Fabrikam_Feed"
DisplayName="Fabrikam Feed"
Description="Feed representing Example"
ContentUri="https://www.fabrikam.com/"
Icon="ms-appx:Images\StoreLogo.png">
</Definition>
</Definitions>
</FeedProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
有关所有这些元素的详细说明和格式信息,请参阅源提供程序包清单 XML 格式。
将图标添加到打包项目
在“解决方案资源管理器”中,右键单击 ExampleFeedProviderPackage 并选择“添加”->“新文件夹”。 将此文件夹命名为 ProviderAssets,因为这是上一步在 Package.appxmanifest
中使用的内容。 源的“图标”就将存储在此处。 添加所需的图标后,请确保图像名称与 中 Path=ProviderAssets\Package.appxmanifest
之后的名称一致,否则源将不会显示在小组件板中。
测试源提供程序
确保已从“解决方案平台”下拉列表中选择与开发计算机匹配的体系结构,例如“x64”。 在“解决方案资源管理器”中,右键单击所需解决方案,然后选择“生成解决方案”。 完成此操作后,右键单击 ExampleWidgetProviderPackage 并选择“部署”。 控制台应用应会在部署时启动,你将在控制台输出中看到已启用源。 打开小组件板,在“源”部分的顶部选项卡中应会看到新的源。
调试源提供程序
固定源后,小组件平台将启动源提供程序应用程序,以便接收和发送有关该源的相关信息。 要调试正在运行的源,可以将调试程序附加到正在运行的源提供程序应用程序,也可以将 Visual Studio 设置为在启动源提供程序进程后自动开始调试源提供程序进程。
要附加到正在运行的进程,请执行以下操作:
- 在 Visual Studio 中,单击“调试”->“附加到进程”。
- 筛选进程并找到所需是源提供程序应用程序。
- 附加调试程序。
要在小组件最初启动时将调试程序自动附加到进程,请执行以下操作:
- 在 Visual Studio 中,选择“调试”->“其他调试目标”->“调试安装的应用包”>。
- 筛选包并查找所需的源提供程序包。
- 选择它并选中显示“不启动,但在启动时调试代码”的框。
- 单击 “附加” 。
将控制台应用转换为 Windows 应用
将本演练中创建的控制台应用转换为 Windows 应用:
- 在“解决方案资源管理器”中右键单击 ExampleWidgetProvider 项目,并选择“属性”。 导航到 “链接器”->“系统”,并将 SubSystem 从“控制台”更改为“Windows”。 此操作也可通过将 <SubSystem>Windows</SubSystem> 添加到 .vcxproj 的 <Link>..</Link> 部分完成。
- 在 main.cpp 中,将
int main()
更改为int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/)
。
发布源提供程序应用
开发和测试源提供程序后,可以在 Microsoft Store 上发布应用,以便用户在其设备上安装源。 有关发布应用的分步指南,请参阅在 Microsoft Store 中发布应用。
源存储集合
在 Microsoft 应用商店上发布应用后,可以请求将应用包含在源应用商店集合中,以帮助用户发现具有 Windows 源的应用。 若要提交请求,请参阅 “提交源/板”以获取应用商店集合的附加内容。