将丰富激活与应用生命周期 API 配合使用

在 Windows 应用 SDK 中,应用生命周期 API 为所有已打包和未打包的应用引入了对 UWP 样式丰富激活行为的支持。 第一个版本侧重于将最常用的激活类型引入未打包的应用,将来的版本旨在支持更多 UWP 应用的 44 种激活类型

支持丰富激活需要完成两个步骤:

  • 告诉系统你的应用支持一种或多种丰富激活类型。
  • 接收并处理应用在激活后收到的丰富激活有效负载。

先决条件

若要在 Windows 应用 SDK 中使用应用生命周期 API,请执行以下操作:

  1. 下载并安装 Windows 应用 SDK 的最新版本。 有关详细信息,请参阅 WinUI 入门。
  2. 按照说明创建你的第一个 WinUI 3 项目,或者在现有项目中使用 Windows App SDK

未打包应用的激活详细信息

当前版本的 Windows 应用 SDK 支持四种最常见的未打包应用激活类型。 这些激活类型由 ExtendedActivationKind 枚举定义。

激活类型 说明
Launch 当用户双击应用的图标或者通过 ShellExecuteCreateProcess 以编程方式打开应用时,将从命令行激活该应用。
File 通过 ShellExecuteLauncher.LaunchFileAsync 或命令行打开某种类型的文件时,将激活已注册该文件类型的应用。
Protocol 通过 ShellExecuteLauncher.LaunchUriAsync 或命令行执行某种协议的字符串时,将激活已注册该协议的应用。
StartupTask 当用户登录到 Windows 时,将由于创建了某个注册表项或者在已知的启动文件夹中创建了快捷方式而激活应用。

每种类型的未打包应用以不同的方式检索其命令行参数。 例如,C++ Win32 应用预期会接收以字符串形式传递给 WinMain 的激活参数(不过它们也可以选择调用 GetCommandLineW)。 但是,Windows 窗体应用必须调用 Environment.GetCommandLineArgs,因为不会自动向其传递参数。

已打包应用的激活详细信息

使用 Windows 应用 SDK 的已打包应用支持 UWP 的全部 44 种激活类型。 每种激活类型都有自身对应的 IActivatedEventArgs 实现,其中包含与该特定激活类型相关的属性。

已打包的应用始终在其 AppInstance.Activated 事件处理程序中接收激活事件参数,并且还可以选择调用 AppInstance.GetActivatedEventArgs

激活注册

默认情况下,所有应用都支持 Launch 激活类型。 与 UWP 不同,Windows 应用 SDK Launch 激活类型包括命令行启动。 应用可以通过多种方式注册其他激活类型。

  • 使用 Windows 应用 SDK 的未打包应用可以通过 Windows 应用 SDK 中的应用生命周期 API 注册(和取消注册)其他激活类型。
  • 未打包的应用可以继续使用编写注册表项的传统方法注册其他激活类型。
  • 已打包的应用可以通过其应用程序清单中的条目注册其他激活类型。

激活注册是按用户进行的。 如果为多个用户安装了你的应用,则你需要为每个用户重新注册激活。

示例

注册丰富激活

虽然应用可以随时调用注册 API,但最常用的方案是在应用启动时检查注册。

此示例演示未打包的应用在启动时如何使用 ActivationRegistrationManager 类的以下静态方法注册多种激活类型:

此示例还演示了如何使用 MddBootstrapInitializeMddBootstrapShutdown 函数来初始化和清理对 Windows 应用 SDK 框架包的引用。 所有未打包的应用都必须执行此操作才能使用 Windows 应用 SDK 提供的 API。 有关详细信息,请参阅将 Windows 应用 SDK 运行时用于使用外部位置打包的应用或未打包的应用

注意

此示例同时注册与三种映像文件类型的关联。 这种做法非常方便,但结果与单独注册每种文件类型相同;注册新的映像类型不会覆盖以前的注册。 但是,如果应用使用不同的谓词集重新注册了已注册的文件类型,则会覆盖该文件类型的前一个谓词集。

const UINT32 majorMinorVersion{ WINDOWSAPPSDK_RELEASE_MAJORMINOR };
PCWSTR versionTag{ WINDOWSAPPSDK_RELEASE_VERSION_TAG_W };
const PACKAGE_VERSION minVersion{ WINDOWSAPPSDK_RUNTIME_VERSION_UINT64 };
WCHAR szExePath[MAX_PATH]{};
WCHAR szExePathAndIconIndex[MAX_PATH + 8]{};

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // Initialize Windows App SDK framework package for unpackaged apps.
    HRESULT hr{ MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion) };
    if (FAILED(hr))
    {
        wprintf(L"Error 0x%X in MddBootstrapInitialize(0x%08X, %s, %hu.%hu.%hu.%hu)\n",
            hr, majorMinorVersion, versionTag, minVersion.Major, 
            minVersion.Minor, minVersion.Build, minVersion.Revision);
        return hr;
    }

    // Get the current executable filesystem path, so we can
    // use it later in registering for activation kinds.
    GetModuleFileName(NULL, szExePath, MAX_PATH);
    wcscpy_s(szExePathAndIconIndex, szExePath);
    wcscat_s(szExePathAndIconIndex, L",1");

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_CLASSNAME, szWindowClass, MAX_LOADSTRING);
    RegisterWindowClass(hInstance);
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Uninitialize Windows App SDK.
    MddBootstrapShutdown();
    return (int)msg.wParam;
}

void RegisterForActivation()
{
    OutputMessage(L"Registering for rich activation");

    // Register one or more supported filetypes, specifying 
    // an icon (specified by binary file path plus resource index),
    // a display name to use in Shell and Settings,
    // zero or more verbs for the File Explorer context menu,
    // and the path to the EXE to register for activation.
    hstring myFileTypes[3] = { L".foo", L".foo2", L".foo3" };
    hstring verbs[2] = { L"view", L"edit" };
    ActivationRegistrationManager::RegisterForFileTypeActivation(
        myFileTypes,
        szExePathAndIconIndex,
        L"Contoso File Types",
        verbs,
        szExePath
    );

    // Register a URI scheme for protocol activation,
    // specifying the scheme name, icon, display name and EXE path.
    ActivationRegistrationManager::RegisterForProtocolActivation(
        L"foo",
        szExePathAndIconIndex,
        L"Contoso Foo Protocol",
        szExePath
    );

    // Register for startup activation.
    // As we're registering for startup activation multiple times,
    // and this is a multi-instance app, we'll get multiple instances
    // activated at startup.
    ActivationRegistrationManager::RegisterForStartupActivation(
        L"ContosoStartupId",
        szExePath
    );

    // If we don't specify the EXE, it will default to this EXE.
    ActivationRegistrationManager::RegisterForStartupActivation(
        L"ContosoStartupId2",
        L""
    );
}

获取丰富激活事件参数

激活后,应用必须检索其激活事件参数。 在此示例中,未打包的应用调用 AppInstance.GetActivatedEventArgs 方法以获取激活事件的事件参数,然后使用 AppActivationArguments.Kind 属性来检索不同激活类型的事件参数。

注意

Win32 应用通常很快就能在其 WinMain 方法中获得命令行参数。 同样,这些应用应在以前使用提供的 lpCmdLine 参数或调用 GetCommandLineW 的同一位置调用 AppInstance.GetActivatedEventArgs

void GetActivationInfo()
{
    AppActivationArguments args = AppInstance::GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind();
    if (kind == ExtendedActivationKind::Launch)
    {
        ILaunchActivatedEventArgs launchArgs = 
            args.Data().as<ILaunchActivatedEventArgs>();
        if (launchArgs != NULL)
        {
            winrt::hstring argString = launchArgs.Arguments().c_str();
            std::vector<std::wstring> argStrings = split_strings(argString);
            OutputMessage(L"Launch activation");
            for (std::wstring s : argStrings)
            {
                OutputMessage(s.c_str());
            }
        }
    }
    else if (kind == ExtendedActivationKind::File)
    {
        IFileActivatedEventArgs fileArgs = 
            args.Data().as<IFileActivatedEventArgs>();
        if (fileArgs != NULL)
        {
            IStorageItem file = fileArgs.Files().GetAt(0);
            OutputFormattedMessage(
                L"File activation: %s", file.Name().c_str());
        }
    }
    else if (kind == ExtendedActivationKind::Protocol)
    {
        IProtocolActivatedEventArgs protocolArgs = 
            args.Data().as<IProtocolActivatedEventArgs>();
        if (protocolArgs != NULL)
        {
            Uri uri = protocolArgs.Uri();
            OutputFormattedMessage(
                L"Protocol activation: %s", uri.RawUri().c_str());
        }
    }
    else if (kind == ExtendedActivationKind::StartupTask)
    {
        IStartupTaskActivatedEventArgs startupArgs = 
            args.Data().as<IStartupTaskActivatedEventArgs>();
        if (startupArgs != NULL)
        {
            OutputFormattedMessage(
                L"Startup activation: %s", startupArgs.TaskId().c_str());
        }
    }
}

注销

此示例演示未打包的应用如何使用 ActivationRegistrationManager 类的以下静态方法动态取消注册特定的激活类型:

注意

在取消注册激活启动时,应用必须使用它最初注册时所用的同一 taskId。

void UnregisterForActivation()
{
    OutputMessage(L"Unregistering for rich activation");
    
    // Unregister one or more registered filetypes.
    try
    {
        hstring myFileTypes[3] = { L".foo", L".foo2", L".foo3" };
        ActivationRegistrationManager::UnregisterForFileTypeActivation(
            myFileTypes,
            szExePath
        );
    }
    catch (...)
    {
        OutputMessage(L"Error unregistering file types");
    }

    // Unregister a protocol scheme.
    ActivationRegistrationManager::UnregisterForProtocolActivation(
        L"foo",
        L"");

    // Unregister for startup activation.
    ActivationRegistrationManager::UnregisterForStartupActivation(
        L"ContosoStartupId");
    ActivationRegistrationManager::UnregisterForStartupActivation(
        L"ContosoStartupId2");
}