Partilhar via


Instanciação de aplicativos com a API de ciclo de vida do aplicativo

O modelo de instanciação de um aplicativo determina se várias instâncias do processo do seu aplicativo podem ser executadas ao mesmo tempo. A API do ciclo de vida do aplicativo no SDK do Aplicativo Windows fornece uma maneira de controlar quantas instâncias do seu aplicativo podem ser executadas ao mesmo tempo e redirecionar ativações para outras instâncias quando necessário.

Este artigo descreve como usar a API do ciclo de vida do aplicativo para controlar a instanciação do aplicativo em seus aplicativos WinUI.

Pré-requisitos

Para usar a API de ciclo de vida do aplicativo em aplicativos WinUI 3:

Aplicativos de instância única

Os aplicativos são de instância única se houver apenas um processo principal em execução de cada vez. A tentativa de iniciar uma segunda instância de um aplicativo de instância única normalmente resulta na ativação da janela principal da primeira instância. Observe que isso só se aplica ao processo principal. Os aplicativos de instância única podem criar vários processos em segundo plano e ainda ser considerados de instância única.

Os aplicativos WinUI são multiinstâncias por padrão, mas têm a capacidade de se tornar instância única, decidindo no momento da inicialização se devem criar uma nova instância ou ativar uma instância existente.

O aplicativo Fotos da Microsoft é um bom exemplo de um aplicativo WinUI de instância única. Quando você iniciar o Fotos pela primeira vez, uma nova janela será criada. Se tentares iniciar a aplicação Fotos novamente, a janela existente será ativada.

Para obter um exemplo de como implementar uma única instanciação em um aplicativo WinUI 3 com C#, consulte Criar um aplicativo WinUI de instância única.

Aplicativos com várias instâncias

Os aplicativos são com várias instâncias se o processo principal puder ser executado várias vezes simultaneamente. A tentativa de iniciar uma segunda instância de um aplicativo com várias instâncias cria um novo processo e uma nova janela principal.

Tradicionalmente, os aplicativos não empacotados são com várias instâncias por padrão, mas podem implementar a instância única quando necessário. Normalmente, isso é feito usando um único mutex com nome para indicar se uma aplicação já está em execução.

O Bloco de Notas é um bom exemplo de um aplicativo com várias instâncias. Cada vez que você tentar iniciar o Bloco de Notas, uma nova instância do Bloco de Notas será criada, independentemente de quantas instâncias já estão em execução.

Como a instanciação do SDK de Aplicativo Windows difere da instanciação da UWP

O comportamento de instanciação no SDK do Aplicativo Windows é baseado no modelo, classe da UWP, mas com algumas diferenças importantes:

Classe AppInstance

  • UWP: A classe Windows.ApplicationModel.AppInstance é focada exclusivamente em cenários de redirecionamento de instância.
  • SDK do Aplicativo Windows: A classe Microsoft.Windows.AppLifeycle.AppInstance oferece suporte a cenários de redirecionamento de instância e contém funcionalidade adicional para oferecer suporte a novos recursos em versões posteriores.

Lista de instâncias

  • UWP: GetInstances retorna apenas as instâncias que o aplicativo registrou explicitamente para possível redirecionamento.
  • SDK de Aplicativo do Windows: GetInstances retorna todas as instâncias em execução do aplicativo que estão usando a API AppInstance, independentemente de terem ou não registrado uma chave. Isso pode incluir a instância atual. Se desejar que a instância atual seja incluída na lista, chame AppInstance.GetCurrent. Listas separadas são mantidas para diferentes versões do mesmo aplicativo, bem como instâncias de aplicativos iniciados por usuários diferentes.

Chaves de registo

Cada instância de um aplicativo com várias instâncias pode registrar uma chave arbitrária por meio do FindOrRegisterForKey método. As chaves não têm significado inerente; Os aplicativos podem usar as teclas da forma que desejarem.

Uma instância de um aplicativo pode definir sua chave a qualquer momento, mas apenas uma chave é permitida para cada instância; A definição de um novo valor substitui o valor anterior.

Uma instância de um aplicativo não pode definir sua chave com o mesmo valor que outra instância já registrou. A tentativa de registrar uma chave existente resultará no FindOrRegisterForKey retorno da instância do aplicativo que já registrou essa chave.

  • UWP: Uma instância deve registrar uma chave para ser incluída na lista retornada de GetInstances.
  • SDK do Aplicativo Windows: o registro de uma chave é dissociado da lista de instâncias. Uma instância não precisa registrar uma chave para ser incluída na lista.

Cancelar o registro de chaves

Uma instância de um aplicativo pode cancelar o registro de sua chave.

  • UWP: Quando uma instância cancela o registro de sua chave, ela não está mais disponível para redirecionamento de ativação e não está incluída na lista de instâncias retornadas de GetInstances.
  • SDK do Aplicativo Windows: uma instância que cancelou o registro de sua chave ainda está disponível para redirecionamento de ativação e ainda está incluída na lista de instâncias retornadas de GetInstances.

Destinos de redirecionamento de instância

Várias instâncias de um aplicativo podem ativar umas às outras, um processo chamado "redirecionamento de ativação". Por exemplo, um aplicativo pode implementar uma única instanciação inicializando a si mesmo apenas se nenhuma outra instância do aplicativo for encontrada na inicialização e, em vez disso, redirecionar e sair se outra instância existir. Os aplicativos com várias instâncias podem redirecionar ativações quando apropriado, de acordo com a lógica de negócios desse aplicativo. Quando uma ativação é redirecionada para outra instância, ela usa o retorno de chamada dessa Activated instância, o mesmo retorno de chamada usado em todos os outros cenários de ativação.

  • UWP: Somente instâncias que registraram uma chave podem ser um destino para redirecionamento.
  • SDK de Aplicativo Windows: qualquer instância pode ser um destino de redirecionamento, independentemente de ter ou não uma chave registrada.

Comportamento pós-redirecionamento

  • UWP: O redirecionamento é uma operação terminal; O aplicativo é encerrado após o redirecionamento da ativação, mesmo que o redirecionamento tenha falhado.

  • SDK de Aplicativo Windows: no SDK de Aplicativo Windows, o redirecionamento não é uma operação de terminal. Isso em parte reflete os problemas potenciais em encerrar arbitrariamente um aplicativo Win32 que pode já ter alocado alguma memória, mas também permite o suporte de cenários de redirecionamento mais sofisticados. Considere um aplicativo com várias instâncias em que uma instância recebe uma solicitação de ativação enquanto executa uma grande quantidade de trabalho intensivo de CPU. Esse aplicativo pode redirecionar a solicitação de ativação para outra instância e continuar seu processamento. Esse cenário não seria possível se o aplicativo fosse encerrado após o redirecionamento.

Uma solicitação de ativação pode ser redirecionada várias vezes. A instância A pode redirecionar para a instância B, que, por sua vez, pode redirecionar para a instância C. Os aplicativos do SDK de aplicativos do Windows que aproveitam essa funcionalidade devem proteger contra o redirecionamento circular - se C redireciona para A no exemplo acima, há um loop de ativação infinito potencial. Cabe ao aplicativo determinar como lidar com o redirecionamento circular, dependendo do que faz sentido para os fluxos de trabalho suportados pelo aplicativo.

Eventos de ativação

Para lidar com a reativação, o aplicativo pode se registrar para um evento Ativado.

Exemplos

Gestão de ativações

Este exemplo demonstra como um aplicativo se registra e manipula um evento Activated. Quando recebe um Activated evento, este aplicativo usa os argumentos de evento para determinar que tipo de ação causou a ativação e responde adequadamente.

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);
    }
}

Lógica de redirecionamento baseada no tipo de ativação

Neste exemplo, o aplicativo registra um manipulador para o evento Activated e também verifica se há args de evento de ativação para decidir se deseja redirecionar a ativação para outra instância.

Para a maioria dos tipos de ativações, o aplicativo continua com seu processo de inicialização regular. No entanto, se a ativação foi causada por um tipo de arquivo associado que está sendo aberto e se outra instância deste aplicativo já tiver o arquivo aberto, a instância atual redirecionará a ativação para a instância existente e sairá.

Este aplicativo usa o registro de chave para determinar quais arquivos estão abertos em quais instâncias. Quando uma instância abre um arquivo, ela registra uma chave que inclui esse nome de arquivo. Outras instâncias podem então examinar as chaves registradas e procurar nomes de arquivos específicos, e registrar-se como instância desse arquivo se nenhuma outra instância já tiver.

Observe que, embora o registro da chave em si faça parte da API do ciclo de vida do aplicativo no SDK do Aplicativo Windows, o conteúdo da chave é especificado apenas no próprio aplicativo. Um aplicativo não precisa registrar um nome de arquivo ou quaisquer outros dados significativos. Este aplicativo, no entanto, decidiu rastrear arquivos abertos através de chaves com base em suas necessidades particulares e fluxos de trabalho suportados.

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;
}

Redirecionamento arbitrário

Este exemplo expande o exemplo anterior adicionando regras de redirecionamento mais sofisticadas. O aplicativo ainda executa a verificação de arquivo aberto do exemplo anterior. No entanto, onde o exemplo anterior sempre criaria uma nova instância se não redirecionasse com base na verificação de arquivo aberto, este exemplo adiciona o conceito de uma instância "reutilizável". Se uma instância reutilizável for encontrada, a instância atual redirecionará para a instância reutilizável e será encerrada. Caso contrário, ele se registra como reutilizável e continua com sua inicialização normal.

Novamente, observe que o conceito de uma instância "reutilizável" não existe na API do ciclo de vida do aplicativo; ele é criado e usado apenas dentro do próprio aplicativo.

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;
}

Orquestração de redirecionamento

Este exemplo novamente adiciona um comportamento de redirecionamento mais sofisticado. Aqui, uma instância de aplicativo pode se registrar como a instância que lida com todas as ativações de um tipo específico. Quando uma instância de uma aplicação recebe uma ativação Protocol, verifica primeiro se há uma instância que já se registou para lidar com ativações Protocol. Se encontrar um, redireciona a ativação para essa instância. Caso contrário, a instância atual se registra para ativações Protocol e, em seguida, aplica lógica adicional (não mostrada) que pode redirecionar a ativação por algum outro motivo.

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);
    }
}

Ao contrário da versão UWP do , a implementação do método RedirectActivationToAsync do SDK de Aplicativos do Windows requer a passagem explícita de argumentos de evento ao redirecionar ativações. Isso é necessário porque, enquanto a UWP controla rigorosamente as ativações e pode garantir que os argumentos de ativação corretos sejam passados para as instâncias corretas, a versão do SDK do Aplicativo Windows oferece suporte a muitas plataformas e não pode depender de recursos específicos da UWP. Um benefício desse modelo é que os aplicativos que usam o SDK de aplicativos do Windows têm a chance de modificar ou substituir os argumentos que serão passados para a instância de destino.

Redirecionamento sem bloqueio

A maioria dos aplicativos desejará redirecionar o mais cedo possível, antes de fazer um trabalho de inicialização desnecessário. Para alguns tipos de aplicativo, a lógica de inicialização é executada em um thread STA, que não deve ser bloqueado. O método AppInstance.RedirectActivationToAsync é assíncrono e o aplicativo de chamada deve aguardar a conclusão do método, caso contrário, o redirecionamento falhará. No entanto, a espera por uma chamada assíncrona bloqueará o STA. Nessas situações, chame RedirectActivationToAsync em outro thread e defina um evento quando a chamada for concluída. Em seguida, aguarde esse evento usando APIs sem bloqueio, como CoWaitForMultipleObjects. Aqui está um exemplo de C# para um aplicativo 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;
}

Cancelar o registro para redirecionamento

Os aplicativos que registraram uma chave podem cancelar o registro dessa chave a qualquer momento. Este exemplo pressupõe que a instância atual havia registrado anteriormente uma chave indicando que tinha um arquivo específico aberto, o que significa que as tentativas subsequentes de abrir esse arquivo seriam redirecionadas para ele. Quando esse arquivo é fechado, a chave que contém o nome do arquivo deve ser excluída.

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

Advertência

Embora as chaves sejam automaticamente canceladas quando o processo termina, são possíveis condições de competição quando outra instância pode ter iniciado um redirecionamento para a instância encerrada antes que a instância encerrada tenha sido cancelada. Para atenuar essa possibilidade, um aplicativo pode usar UnregisterKey para cancelar manualmente o registro de sua chave antes que ela seja encerrada, dando ao aplicativo a chance de redirecionar ativações para outro aplicativo que não está em processo de saída.

Informações da instância

A classe Microsoft.Windows.AppLifeycle.AppInstance representa uma única instância de um aplicativo. Na visualização atual, AppInstance inclui apenas os métodos e propriedades necessários para dar suporte ao redirecionamento de ativação. Em versões posteriores, AppInstance será expandido para incluir outros métodos e propriedades relevantes para uma instância de aplicativo.

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());
    }
}

Criar um aplicativo WinUI de instância única

Microsoft.Windows.AppLifeycle.AppInstance