Развертывание приложений с помощью API жизненного цикла приложения

Модель инстантирования приложения определяет, может ли одновременно выполняться несколько экземпляров процесса приложения.

Необходимые компоненты

Чтобы использовать API жизненного цикла приложения в пакете SDK для приложений Для Windows:

  1. Скачайте и установите последнюю версию пакета SDK для приложений Windows. Дополнительные сведения см. в статье Установка инструментов для Windows App SDK.
  2. Выполните инструкции по созданию простого проекта WinUI 3 или применению пакета SDK для приложений Windows в существующем проекте.

Одно экземплярные приложения

Примечание.

Пример реализации единого внедрения в приложении WinUI 3 с помощью C#см. в блоге разработчика Windows.

Приложения выполняются одним экземпляром, если одновременно может выполняться только один основной процесс. Попытка запустить второй экземпляр одноэлементного приложения обычно приводит к активации главного окна первого экземпляра. Обратите внимание, что это относится только к основному процессу. Одноэлементные приложения могут создавать несколько фоновых процессов и по-прежнему считаться одним экземпляром.

Приложения UWP по умолчанию являются одноэлементными. но возможность стать несколькими экземплярами путем принятия решения во время запуска, следует ли создать дополнительный экземпляр или активировать существующий экземпляр.

Приложение Windows Mail является хорошим примером одного экземплярного приложения. При первом запуске Mail будет создано новое окно. При попытке повторного запуска Почты будет активировано существующее окно "Почта".

Приложения с несколькими экземплярами

Приложения выполняются несколькими экземплярами, если основной процесс может выполняться несколько раз одновременно. При попытке запустить второй экземпляр много экземпляра приложения создается новый процесс и главное окно.

Традиционно распаковывание приложений по умолчанию выполняется с несколькими экземплярами, но может реализовать однозакторную настройку при необходимости. Обычно это делается с помощью одного именованного мьютекса, чтобы указать, запущено ли приложение.

Блокнот является хорошим примером много экземплярного приложения. Каждый раз, когда вы пытаетесь запустить Блокнот, создается новый экземпляр Блокнот независимо от того, сколько экземпляров уже запущено.

Как пакет SDK для приложений Windows отличается от инстантирования UWP

Поведение в пакете SDK для приложений Windows основано на модели UWP, классе, но с некоторыми ключевыми различиями:

Класс AppInstance

  • UWP: класс Windows.ApplicationModel.AppInstance ориентирован исключительно на сценарии перенаправления экземпляров.
  • Пакет SDK для приложений Windows: класс Microsoft.Windows.AppLifeycle.AppInstance поддерживает сценарии перенаправления экземпляров и содержит дополнительные функции для поддержки новых функций в последующих выпусках.

Список экземпляров

  • UWP: GetInstances возвращает только экземпляры, которые приложение явно зарегистрировано для потенциального перенаправления.
  • Пакет SDK для приложений Windows: GetInstances возвращает все запущенные экземпляры приложения, использующие API AppInstance, независимо от того, зарегистрирован ли ключ. Это может включать текущий экземпляр. Если вы хотите, чтобы текущий экземпляр был включен в список, вызовите.AppInstance.GetCurrent Отдельные списки поддерживаются для разных версий одного приложения, а также экземпляров приложений, запускаемых различными пользователями.

Регистрация ключей

Каждый экземпляр приложения с несколькими экземплярами может зарегистрировать произвольный ключ с помощью FindOrRegisterForKey метода. Ключи не имеют никакого смысла; приложения могут использовать ключи в любой форме или способе.

Экземпляр приложения может задать ключ в любое время, но для каждого экземпляра допускается только один ключ; При задании нового значения перезаписывается предыдущее значение.

Экземпляр приложения не может задать ключ тому же значению, что и другой экземпляр, уже зарегистрированный. Попытка зарегистрировать существующий ключ приведет к FindOrRegisterForKey возврату экземпляра приложения, который уже зарегистрировал этот ключ.

  • UWP: экземпляр должен зарегистрировать ключ для включения в список, возвращаемый из GetInstances.
  • Пакет SDK для приложений Windows: регистрация ключа отделяется от списка экземпляров. Экземпляру не нужно регистрировать ключ для включения в список.

Отмена регистрации ключей

Экземпляр приложения может отменить регистрацию ключа.

  • UWP: если экземпляр отменяет регистрацию ключа, он больше недоступен для перенаправления активации и не включен в список экземпляров, возвращаемых из GetInstances.
  • Пакет SDK для приложений Windows: экземпляр, который отменил регистрацию ключа, по-прежнему доступен для перенаправления активации и по-прежнему включен в список экземпляров, возвращенных из GetInstances.

Целевые объекты перенаправления экземпляров

Несколько экземпляров приложения могут активировать друг друга, процесс с именем "перенаправление активации". Например, приложение может реализовать одностановку, только инициализировав себя, если другие экземпляры приложения не найдены при запуске, а вместо этого перенаправление и выход, если другой экземпляр существует. Приложения с несколькими экземплярами могут перенаправлять активации в соответствии с бизнес-логикой этого приложения. При перенаправлении активации в другой экземпляр он использует обратный вызов этого экземпляра Activated , тот же обратный вызов, который используется во всех других сценариях активации.

  • UWP: только экземпляры, зарегистрированные ключом, могут быть целевым объектом для перенаправления.
  • Пакет SDK для приложений Windows: любой экземпляр может быть целевым объектом перенаправления, независимо от того, имеет ли он зарегистрированный ключ.

Поведение после перенаправления

  • UWP: перенаправление — это операция терминала; приложение завершается после перенаправления активации, даже если перенаправление завершилось сбоем.

  • Пакет SDK для приложений Windows: в пакете SDK для приложений Windows перенаправление не является операцией терминала. Это частично отражает потенциальные проблемы при произвольном прекращении приложения Win32, которое, возможно, уже выделило некоторую память, но также позволяет поддерживать более сложные сценарии перенаправления. Рассмотрим много экземплярное приложение, в котором экземпляр получает запрос на активацию при выполнении большого количества ресурсов ЦП. Это приложение может перенаправить запрос активации на другой экземпляр и продолжить обработку. Этот сценарий невозможен, если приложение было завершено после перенаправления.

Запрос на активацию можно перенаправить несколько раз. Экземпляр A может перенаправляться на экземпляр B, который, в свою очередь, может перенаправляться на экземпляр приложений C. Приложения пакета SDK для приложений Для Windows, которые используют эту функцию, должны защищаться от циклов активации. Это зависит от приложения, чтобы определить, как обрабатывать циклическое перенаправление в зависимости от того, что имеет смысл для рабочих процессов, поддерживаемых приложением.

События активации

Для обработки повторной активации приложение может зарегистрировать событие Активации.

Примеры

Обработка активаций

В этом примере показано, как приложение регистрирует и обрабатывает Activated событие. Когда оно получает Activated событие, это приложение использует аргументы событий для определения типа действия, вызванного активацией, и отвечает соответствующим образом.

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

    // Initialize the Windows App SDK framework package for unpackaged apps.
    HRESULT hr{ MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion) };
    if (FAILED(hr))
    {
        OutputFormattedDebugString(
            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;
    }

    if (DecideRedirection())
    {
        return 1;
    }

    // Connect the Activated event, to allow for this instance of the app
    // getting reactivated as a result of multi-instance redirection.
    AppInstance thisInstance = AppInstance::GetCurrent();
    auto activationToken = thisInstance.Activated(
        auto_revoke, [&thisInstance](
            const auto& sender, const AppActivationArguments& args)
        { OnActivated(sender, args); }
    );

    // Carry on with regular Windows initialization.
    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);
    }

    MddBootstrapShutdown();
    return (int)msg.wParam;
}

void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
    int const arraysize = 4096;
    WCHAR szTmp[arraysize];
    size_t cbTmp = arraysize * sizeof(WCHAR);
    StringCbPrintf(szTmp, cbTmp, L"OnActivated (%d)", activationCount++);

    ExtendedActivationKind kind = args.Kind();
    if (kind == ExtendedActivationKind::Launch)
    {
        ReportLaunchArgs(szTmp, args);
    }
    else if (kind == ExtendedActivationKind::File)
    {
        ReportFileArgs(szTmp, args);
    }
}

Логика перенаправления на основе типа активации

В этом примере приложение регистрирует обработчик события активации, а также проверка для события активации, чтобы решить, следует ли перенаправить активацию в другой экземпляр.

Для большинства типов активаций приложение продолжает процесс регулярной инициализации. Однако если активация была вызвана открытием связанного типа файла, и если другой экземпляр этого приложения уже открыт, текущий экземпляр перенаправит активацию в существующий экземпляр и завершит работу.

Это приложение использует регистрацию ключей для определения открытых файлов в экземплярах. Когда экземпляр открывает файл, он регистрирует ключ, содержащий это имя файла. Затем другие экземпляры могут проверять зарегистрированные ключи и искать определенные имена файлов и регистрировать себя в качестве экземпляра этого файла, если другой экземпляр еще не имеет.

Обратите внимание, что, хотя регистрация ключа является частью API жизненного цикла приложения в пакете SDK для приложений Windows, содержимое ключа указывается только в самом приложении. Приложению не нужно регистрировать имя файла или другие значимые данные. Однако это приложение решило отслеживать открытые файлы с помощью ключей на основе конкретных потребностей и поддерживаемых рабочих процессов.

bool DecideRedirection()
{
    // 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");

    // Find out what kind of activation this is.
    AppActivationArguments args = AppInstance::GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind();
    if (kind == ExtendedActivationKind::Launch)
    {
        ReportLaunchArgs(L"WinMain", args);
    }
    else if (kind == ExtendedActivationKind::File)
    {
        ReportFileArgs(L"WinMain", args);

        try
        {
            // This is a file activation: here we'll get the file information,
            // and register the file name as our instance key.
            IFileActivatedEventArgs fileArgs = args.Data().as<IFileActivatedEventArgs>();
            if (fileArgs != NULL)
            {
                IStorageItem file = fileArgs.Files().GetAt(0);
                AppInstance keyInstance = AppInstance::FindOrRegisterForKey(file.Name());
                OutputFormattedMessage(
                    L"Registered key = %ls", keyInstance.Key().c_str());

                // If we successfully registered the file name, we must be the
                // only instance running that was activated for this file.
                if (keyInstance.IsCurrent())
                {
                    // Report successful file name key registration.
                    OutputFormattedMessage(
                        L"IsCurrent=true; registered this instance for %ls",
                        file.Name().c_str());
                }
                else
                {
                    keyInstance.RedirectActivationToAsync(args).get();
                    return true;
                }
            }
        }
        catch (...)
        {
            OutputErrorString(L"Error getting instance information");
        }
    }
    return false;
}

Произвольное перенаправление

Этот пример расширяется в предыдущем примере, добавив более сложные правила перенаправления. Приложение по-прежнему выполняет открытый файл проверка из предыдущего примера. Однако, когда предыдущий пример всегда создаст новый экземпляр, если он не перенаправлялся на основе открытого файла проверка, в этом примере добавляется концепция "повторного использования" экземпляра. Если найден повторно используемый экземпляр, текущий экземпляр перенаправляется на повторно используемый экземпляр и завершает работу. В противном случае он регистрируется как повторно используемый и продолжается с его нормальной инициализацией.

Опять же, обратите внимание, что концепция экземпляра, доступного для повторного использования, не существует в API жизненного цикла приложения; он создается и используется только в самом приложении.

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    // Initialize COM.
    winrt::init_apartment();

    AppActivationArguments activationArgs =
        AppInstance::GetCurrent().GetActivatedEventArgs();

    // Check for any specific activation kind we care about.
    ExtendedActivationKind kind = activationArgs.Kind;
    if (kind == ExtendedActivationKind::File)
    {
        // etc... as in previous scenario.
    }
    else
    {
        // For other activation kinds, we'll trawl all instances to see if
        // any are suitable for redirecting this request. First, get a list
        // of all running instances of this app.
        auto instances = AppInstance::GetInstances();

        // In the simple case, we'll redirect to any other instance.
        AppInstance instance = instances.GetAt(0);

        // If the app re-registers re-usable instances, we can filter for these instead.
        // In this example, the app uses the string "REUSABLE" to indicate to itself
        // that it can redirect to a particular instance.
        bool isFound = false;
        for (AppInstance instance : instances)
        {
            if (instance.Key == L"REUSABLE")
            {
                isFound = true;
                instance.RedirectActivationToAsync(activationArgs).get();
                break;
            }
        }
        if (!isFound)
        {
            // We'll register this as a reusable instance, and then
            // go ahead and do normal initialization.
            winrt::hstring szKey = L"REUSABLE";
            AppInstance::FindOrRegisterForKey(szKey);
            RegisterClassAndStartMessagePump(hInstance, nCmdShow);
        }
    }
    return 1;
}

Оркестрация перенаправления

В этом примере снова добавляется более сложное поведение перенаправления. Здесь экземпляр приложения может зарегистрировать себя в качестве экземпляра, обрабатывающего все активации определенного типа. Когда экземпляр приложения получает Protocol активацию, он сначала проверка для экземпляра, который уже зарегистрирован для обработки Protocol активаций. Если он находит его, он перенаправляет активацию в этот экземпляр. Если нет, текущий экземпляр регистрируется для Protocol активации, а затем применяет дополнительную логику (не отображается), которая может перенаправить активацию по какой-либо другой причине.

void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
    const ExtendedActivationKind kind = args.Kind;

    // For example, we might want to redirect protocol activations.
    if (kind == ExtendedActivationKind::Protocol)
    {
        auto protocolArgs = args.Data().as<ProtocolActivatedEventArgs>();
        Uri uri = protocolArgs.Uri();

        // We'll try to find the instance that handles protocol activations.
        // If there isn't one, then this instance will take over that duty.
        auto instance = AppInstance::FindOrRegisterForKey(uri.AbsoluteUri());
        if (!instance.IsCurrent)
        {
            instance.RedirectActivationToAsync(args).get();
        }
        else
        {
            DoSomethingWithProtocolArgs(uri);
        }
    }
    else
    {
        // In this example, this instance of the app handles all other
        // activation kinds.
        DoSomethingWithNewActivationArgs(args);
    }
}

В отличие от версии RedirectActivationToUWP, реализация пакета SDK для приложений Windows для RedirectActivationToAsync требует явного передачи аргументов событий при перенаправлении активаций. Это необходимо, так как в то время как UWP тесно управляет активацией и может гарантировать, что правильные аргументы активации передаются в правильные экземпляры, версия пакета SDK для приложений Windows поддерживает множество платформ и не может полагаться на специальные функции UWP. Одним из преимуществ этой модели является то, что приложения, использующие пакет SDK для приложений Windows, имеют возможность изменять или заменять аргументы, которые будут переданы целевому экземпляру.

Перенаправление без блокировки

Большинство приложений хотят перенаправить как можно раньше, прежде чем выполнять ненужную инициализацию. Для некоторых типов приложений логика инициализации выполняется в потоке STA, который не должен быть заблокирован. Метод AppInstance.RedirectActivationToAsync является асинхронным, и вызывающее приложение должно ожидать завершения метода, в противном случае перенаправление завершится ошибкой. Однако ожидание асинхронного вызова блокирует STA. В таких ситуациях вызовите RedirectActivationToAsync в другом потоке и задайте событие после завершения вызова. Дождитесь этого события с помощью неблокирующих API, таких как CoWaitForMultipleObjects. Ниже приведен пример C# для приложения WPF.

private static bool DecideRedirection()
{
    bool isRedirect = false;

    // Find out what kind of activation this is.
    AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind;
    if (kind == ExtendedActivationKind.File)
    {
        try
        {
            // This is a file activation: here we'll get the file information,
            // and register the file name as our instance key.
            if (args.Data is IFileActivatedEventArgs fileArgs)
            {
                IStorageItem file = fileArgs.Files[0];
                AppInstance keyInstance = AppInstance.FindOrRegisterForKey(file.Name);

                // If we successfully registered the file name, we must be the
                // only instance running that was activated for this file.
                if (keyInstance.IsCurrent)
                {
                    // Hook up the Activated event, to allow for this instance of the app
                    // getting reactivated as a result of multi-instance redirection.
                    keyInstance.Activated += OnActivated;
                }
                else
                {
                    isRedirect = true;

                    // Ensure we don't block the STA, by doing the redirect operation
                    // in another thread, and using an event to signal when it has completed.
                    redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
                    if (redirectEventHandle != IntPtr.Zero)
                    {
                        Task.Run(() =>
                        {
                            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
                            SetEvent(redirectEventHandle);
                        });
                        uint CWMO_DEFAULT = 0;
                        uint INFINITE = 0xFFFFFFFF;
                        _ = CoWaitForMultipleObjects(
                            CWMO_DEFAULT, INFINITE, 1, 
                            new IntPtr[] { redirectEventHandle }, out uint handleIndex);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error getting instance information: {ex.Message}");
        }
    }

    return isRedirect;
}

Отмена регистрации для перенаправления

Приложения, которые зарегистрировали ключ, могут отменить регистрацию этого ключа в любое время. В этом примере предполагается, что текущий экземпляр ранее зарегистрировал ключ, указывающий, что у него был определенный файл, то есть последующие попытки открыть этот файл будут перенаправлены на него. При закрытии файла необходимо удалить ключ, содержащий имя файла.

void CALLBACK OnFileClosed(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    AppInstance::GetCurrent().UnregisterKey();
}

Предупреждение

Хотя ключи автоматически отменяются при завершении процесса, условия гонки возможны, когда другой экземпляр мог инициировать перенаправление в завершенный экземпляр до отмены регистрации экземпляра. Чтобы устранить эту возможность, приложение может использовать UnregisterKey для ручной отмены регистрации ключа перед его завершением, что дает приложению возможность перенаправить активации в другое приложение, которое не находится в процессе выхода.

Сведения об экземпляре

Класс Microsoft.Windows.AppLifeycle.AppInstance представляет один экземпляр приложения. В текущей предварительной версии включает только методы и свойства, AppInstance необходимые для поддержки перенаправления активации. В последующих выпусках будет расширено включение других методов и свойств, AppInstance относящихся к экземпляру приложения.

void DumpExistingInstances()
{
    for (AppInstance const& instance : AppInstance::GetInstances())
    {
        std::wostringstream sStream;
        sStream << L"Instance: ProcessId = " << instance.ProcessId
            << L", Key = " << instance.Key().c_str() << std::endl;
        ::OutputDebugString(sStream.str().c_str());
    }
}