Создание экземпляров приложений с помощью API жизненного цикла приложения

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

Предварительные требования

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

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

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

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

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

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

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

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

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

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

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

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

Класс Аппинстанце

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примеры

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

В этом примере показано, как приложение регистрируется для события и обрабатывает его 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);
    }
}

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

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

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

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

обратите внимание, что хотя регистрация ключа является частью 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);
    }
}

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

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

Большинству приложений потребуется перенаправление как можно раньше, прежде чем выполнять ненужные операции инициализации. Для некоторых типов приложений логика инициализации выполняется в потоке STA, который не должен блокироваться. Метод Аппинстанце. Редиректактиватионтоасинк является асинхронным, и вызывающее приложение должно ожидать завершения метода, в противном случае перенаправление завершится ошибкой. Однако ожидание асинхронного вызова блокирует STA. В таких ситуациях вызовите Редиректактиватионтоасинк в другом потоке и задайте событие по завершении вызова. Затем дождитесь этого события, используя неблокирующие API-интерфейсы, такие как Коваитформултиплеобжектс. Вот пример 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();
}

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

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

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

Microsoft. Windows. Класс Апплифэйкле. Аппинстанце представляет собой один экземпляр приложения. В текущем предварительном просмотре 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());
    }
}