在 win32 应用中实现源提供程序 (C++/WinRT)

注意

一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 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&param2");
    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&param2");
    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 设置为在启动源提供程序进程后自动开始调试源提供程序进程。

要附加到正在运行的进程,请执行以下操作:

  1. 在 Visual Studio 中,单击“调试”->“附加到进程”。
  2. 筛选进程并找到所需是源提供程序应用程序。
  3. 附加调试程序。

要在小组件最初启动时将调试程序自动附加到进程,请执行以下操作:

  1. 在 Visual Studio 中,选择“调试”->“其他调试目标”->“调试安装的应用包”>。
  2. 筛选包并查找所需的源提供程序包。
  3. 选择它并选中显示“不启动,但在启动时调试代码”的框。
  4. 单击 “附加”

将控制台应用转换为 Windows 应用

将本演练中创建的控制台应用转换为 Windows 应用:

  1. 在“解决方案资源管理器”中右键单击 ExampleWidgetProvider 项目,并选择“属性”。 导航到 “链接器”->“系统”,并将 SubSystem 从“控制台”更改为“Windows”。 此操作也可通过将 <SubSystem>Windows</SubSystem> 添加到 .vcxproj 的 <Link>..</Link> 部分完成。
  2. 在 main.cpp 中,将 int main() 更改为 int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/)

显示输出类型设置为 Windows 应用程序的 C++ 源提供程序项目属性的屏幕截图

发布源提供程序应用

开发和测试源提供程序后,可以在 Microsoft Store 上发布应用,以便用户在其设备上安装源。 有关发布应用的分步指南,请参阅在 Microsoft Store 中发布应用

源存储集合

在 Microsoft 应用商店上发布应用后,可以请求将应用包含在源应用商店集合中,以帮助用户发现具有 Windows 源的应用。 若要提交请求,请参阅 “提交源/板”以获取应用商店集合的附加内容。