Maio de 2018

Volume 33, Número 5

Plataforma Universal do Windows - Fechando as Lacunas entre UWP-Win32

Por Andrew Whitechapel

Um dos temas importantes para a atualização mais recente do Windows foi preencher as lacunas entre o Windows UWP (plataforma Universal) e modelos de aplicativo Win32 tradicionais. Como parte desse esforço, a Microsoft introduziu três principais aprimoramentos:

  • Várias instâncias
  • Aplicativos UWP do console
  • Acesso mais amplo do sistema de arquivos

Os recursos estão relacionados, mas muito independentes. Ou seja, você pode criar um aplicativo de instância múltipla é um aplicativo em janelas regular ou um aplicativo de console e ele pode ou pode não precisará mais ampla de sistema de arquivos de acesso. Igualmente, você pode criar um aplicativo de console não, várias instâncias regular que tem acesso amplo do sistema de arquivos. Uma restrição é que um aplicativo de console deve ser configurado para dar suporte a várias instâncias.

Várias instâncias

No Win32, Linux e outros ambientes de modelo de aplicativo, várias instâncias tem sido o padrão. No UWP, contrasta, o padrão sempre foi uma única instância — e, na verdade, várias instâncias não era suportado em todos os até agora.

Sem várias instâncias, alguns aplicativos têm classificados para uma arquitetura de várias janelas em vez disso, que normalmente envolve um trabalho significativo — e resulta em complexidade e fragilidade. É necessário gastar muito esforço no gerenciamento de janelas, em vez de nos concentrarmos no seus requisitos de domínio. Baseada em várias janelas único processo também é prejudicada preocupações com confiabilidade: Se a instância única falhar, ele traz seu Windows; Isso não é verdade para várias instâncias, onde cada instância é executada como um processo separado.

No modelo de instância única, o usuário pode ativar um aplicativo de várias maneiras: por meio de um toque de bloco no início; por meio de uma ativação de URL ou protocolo; clicando duas vezes em um arquivo com uma extensão que está registrado para o aplicativo; e assim por diante. A primeira ativação (de qualquer tipo) inicia o aplicativo. Depois disso, qualquer ativações subsequentes simplesmente chamam na instância de execução do aplicativo, o aplicativo pode manipular, substituindo o método OnActivated.

O novo recurso de várias instâncias permite que os aplicativos UWP trabalhar com aplicativos Win32: Se uma instância de um aplicativo está em execução e uma solicitação de ativação subsequente é recebida, a plataforma não chamar para ativar a instância existente. Em vez disso, ele criará uma nova instância em um processo separado.

Como esse recurso é amplamente direcionado pela paridade do Win32, inicialmente tem suporte apenas na área de trabalho e IoT. Dois níveis de suporte para várias instâncias foram introduzidos:

  • Aplicativo UWP de várias instâncias: Isso é para o caso mais simples, onde o aplicativo apenas deseja declarar que deve ser instanciado várias.
  • Redirecionamento de várias instâncias aplicativo UWP: Isso é para o caso complexo, onde quer que o aplicativo ser instanciado várias, mas ele também deseja ter uma palavra exatamente como cada instância é ativada.

Para ambos os casos, um modelo de projeto do Visual Studio for fornecido, conforme mostrado na Figura 1.

Novos modelos de projeto para aplicativos de instância múltipla
Figura 1 novos modelos de projeto para aplicativos de instância múltipla

Para o caso mais simples, o modelo de projeto gera o código que é praticamente idêntico ao código de modelo de aplicativo em branco. É a única diferença no uso do atributo SupportsMultipleInstances no manifesto do aplicativo. Há duas declarações adicionais: A primeira é para os namespaces XML desktop4 e iot2 na parte superior do manifesto:

xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2"
  IgnorableNamespaces="uap mp desktop4 iot2">

A segunda adiciona o atributo no elemento < Application >:

<Application
  Id="App8" Executable="$targetnametoken$.exe" EntryPoint="App8.App"
  desktop4:SupportsMultipleInstances="true"
  iot2:SupportsMultipleInstances="true">

Se você estiver atualizando o código de aplicativo existente em vez de gerar um novo aplicativo, você pode simplesmente adicione manualmente essas entradas para o manifesto. Depois de fazer isso, você pode criar seu aplicativo e iniciar várias instâncias. Com isso manifesto de entrada, toda vez que seu aplicativo está ativado, se um toque de bloco ou qualquer outra ativação contrato suporta o aplicativo, como associação de arquivo ou protocolo de inicialização — cada ativação resultará em uma instância separada. Simples assim.

Redirecionamento de várias instâncias

Para a maioria dos aplicativos, tudo o que você precisa fazer é adicionar a entrada de manifesto, mas para um aplicativo que deseja um maior grau de controle sobre suas ativações de instância, você pode usar o segundo modelo. Isso adiciona as mesmas entradas manifestos exatas e também adiciona um arquivo adicional (Program.cs para aplicativos em c#) ou Program.cpp para C++ que contém uma função principal padrão, conforme mostrado no Figura 2. Isso usa uma nova classe de AppInstance introduzida nesta versão.

Figura 2 função de principal padrão para o redirecionamento de várias instâncias

static void Main(string[] args)
{
  IActivatedEventArgs activatedArgs = AppInstance.GetActivatedEventArgs();
  if (AppInstance.RecommendedInstance != null)
  {
    AppInstance.RecommendedInstance.RedirectActivationTo();
  }
  else
  {
    uint number = CryptographicBuffer.GenerateRandomNumber();
    string key = (number % 2 == 0) ? "even" : "odd";
    var instance = AppInstance.FindOrRegisterInstanceForKey(key);
    if (instance.IsCurrentInstance)
    {
      global::Windows.UI.Xaml.Application.Start((p) => new App());
    }
    else
    {
      instance.RedirectActivationTo();
    }
  }
}

A primeira coisa que a função faz é capturar os argumentos de ativação dessa instância. Provavelmente, o aplicativo usaria as informações contidas nesses argumentos como parte de sua lógica de redirecionamento.

Em alguns cenários, a plataforma pode indicar uma instância recomendada. Nesse caso, você pode redirecionar essa ativação a essa instância em vez disso, se desejar, usando o método AppInstance.RedirectActivationTo. Em outras palavras, o aplicativo pode optar por permitir esta solicitação de ativação para ser redirecionado a uma instância existente. Se o redirecionamento, em seguida, a instância de destino está ativada e seu método OnActivated é invocado, e essa nova instância será encerrada.

Se a plataforma não indica uma instância preferida, você vá em frente e compor uma chave. O exemplo de código compõe uma chave de um número aleatório, mas normalmente você substituir esse código e compor uma chave com base em lógica definida pelo aplicativo. Normalmente, isso seria baseado nos argumentos de ativação recuperados anteriormente. Por exemplo, se os argumentos de ativação do tipo FileActivatedEventArgs, o aplicativo pode usar o nome de arquivo especificado como parte da chave. Depois que a chave compostas, passá-lo para o método FindOrRegisterInstanceForKey, que retorna um objeto de AppInstance que representa uma instância deste aplicativo. Para determinar qual instância para retornar, o método faz duas coisas:

  • Ele procura por uma instância existente do aplicativo que já registrou essa chave.
  • Se nenhuma instância existente já registrou essa chave, ele registra desta instância atual com essa chave.

Se você registrou com êxito esta instância, você pode simplesmente vá em frente e fazer a inicialização normal do aplicativo. Para um aplicativo XAML, isso significa chamar Application.Start com uma nova instância da classe App. Se alguma outra instância já registrou essa chave, você pode redirecionar essa ativação para essa instância em vez disso e permitir que essa instância encerrar. Por exemplo, considere um aplicativo que edita arquivos. Se o usuário abriu Foo.doc para edição no aplicativo e, em seguida, tenta abrir Foo.doc novamente, o aplicativo poderá redirecionar a ativação de segundo para a instância que já tenha aberto Foo.doc — e impedir que o usuário abrir o mesmo arquivo em várias instâncias. A lógica para decidir se deve ou não de redirecionamento e qual instância para selecionar como o destino de redirecionamento, é totalmente definido pelo aplicativo.

Para aplicativos XAML, o método Main é normalmente gerado automaticamente e ocultos do desenvolvedor. Esse comportamento é suprimido no modelo de "várias instâncias com o redirecionamento". Se você estiver atualizando um aplicativo existente, você pode suprimir principal padrão adicionando DISABLE_XAML_GENERATED_MAIN à lista de símbolos de compilação condicional nas propriedades de compilação para o aplicativo. Em alguns tipos de aplicativo — por exemplo, um aplicativo C++ DirectX — a função principal não está oculto. Além disso, o uso de um aplicativo DirectX das novas APIs segue os mesmos padrões do exemplo de XAML.

Observe que apenas um aplicativo pode usar os métodos GetActivatedEventArgs e RedirectActivationTo durante principal; Se eles são chamados em qualquer lugar, eles falham. Isso ocorre porque, se você quiser fazer parte de redirecionamento de ativação, você precisa fazer isso muito antecipado da vida útil do processo de aplicativo e certamente antes de qualquer windows é criado.

Por outro lado, você pode usar as propriedades e métodos de AppInstance restantes a qualquer momento. Em particular, você pode usar FindOrRegisterInstanceForKey para atualizar a chave para a instância atual, sempre que precisar. Por exemplo, se a chave foi baseada em um nome de arquivo e fechar esse arquivo mais tarde, você atualizaria o registro de chave nesse momento. Você também pode usar o método de cancelamento de registro para cancelar o registro completamente se por algum motivo que você não quiser que essa instância específica para fazer parte de redirecionamento de ativação. Além disso, a qualquer momento, você pode usar o método AppInstance.GetInstances para obter uma lista de todas as instâncias registradas do seu aplicativo, incluindo as chaves, para que você pode ponderar sobre seu estado.

Considerações adicionais

Várias instâncias é um aprimoramento principal e a versão inicial aborda apenas os principais cenários. Especificamente, o suporte está incluído para um aplicativo de primeiro plano de várias instâncias, os aplicativos de console e a maioria das tarefas de fora de processo em segundo plano, incluindo serviços de aplicativos. No entanto, há sem suporte nesta versão para tarefas ApplicationTrigger ou quaisquer tarefas de plano de fundo em processo.

Durante o desenvolvimento, a Microsoft gasto testando uma ampla gama de aplicativos de armazenamento existentes para ver como executaria quando instanciado várias um tempo considerável. Desse ponto, a Microsoft aprendeu que aplicativos se enquadram em três categorias:

  • Aplicativos que não têm nenhum motivo para ser instanciado múltiplas. Esses aplicativos simplesmente não aceitar o recurso.
  • Aplicativos que desejam ser instanciado em vários e continuam a funcionar corretamente sem quaisquer alterações de código. Esses aplicativos simplesmente podem optar por várias instâncias e chamá-lo feito.
  • Aplicativos que desejam ser instanciado várias, mas precisam trabalhar para permitir as diferenças no modelo de execução.

Os problemas comuns com aplicativos na terceira categoria é que eles usam algum recurso central — talvez um cache, ou um banco de dados ou outro arquivo — e quando única instância eles já foram com segurança supondo que o aplicativo tem acesso exclusivo para esse recurso. Depois que eles aceitar em várias instâncias, pode haver várias instâncias, a tentativa de acessar o recurso. Nesse cenário, o aplicativo precisa trabalhar para sincronizar o acesso, o bloqueio de leituras e gravações e assim por diante — em outras palavras, a sincronização normal problemas que necessitam de aplicativos Win32 tradicional considerar.

Como um exemplo óbvio, considere o uso de armazenamento local do aplicativo. Este é um exemplo de um recurso em que o acesso é restrito em uma base de pacote, não uma base de processo — e logicamente todas as instâncias de um aplicativo compartilham o mesmo pacote. Embora cada instância do aplicativo está em execução como um processo separado, todos eles usarão o mesmo armazenamento local e as configurações, conforme representado pela API do ApplicationData.Current. Se você estiver executando operações de acesso de dados no armazenamento local, você deve considerar como se proteger contra conflitos. Uma opção é usar arquivos de instância exclusivo, onde as operações de uma instância não podem entrar em conflito com qualquer outro. Como alternativa, se você quiser usar um arquivo comum entre várias instâncias, você deve bloquear e desbloquear o acesso ao arquivo adequadamente. Você pode usar os mecanismos padrão como um Mutex nomeado para isso.

Console de aplicativos UWP

A capacidade de criar um aplicativo de console sem cabeçalho, é outro intervalo óbvio no cenário de UWP. Em outros ambientes e Win32, você pode criar uma ferramenta de linha de comando que usa a janela do console para entrada e saída. Por isso, adicionamos esse suporte também. Novamente, há um novo modelo de projeto do Visual Studio, e assim como acontece com instanciado por vários aplicativos, isso gera entradas de manifesto adicionais. Esse recurso também é restrito para área de trabalho e IoT – não menos porque somente os SKUs realmente tem janelas do console no momento. Os mesmos namespaces XML são declarados. O elemento < Application > inclui atributos SupportsMultipleInstances e o subsistema, com um conjunto de subsistema "Console". Aplicativos de console devem ser instanciado várias — este é o modelo esperado para mover de aplicativos de console Win32 tradicionais de aplicativos. Além disso, o aplicativo inclui um AppExecutionAlias — e isso também tem o novo atributo de subsistema, conforme mostrado na Figura 3.

Figura 3 entradas de manifesto adicionais para um aplicativo de Console

<Application Id="App"
  Executable="$targetnametoken$.exe"
  EntryPoint="App9.App"
  desktop4:Subsystem="console"
  desktop4:SupportsMultipleInstances="true"
  iot2:Subsystem="console"
  iot2:SupportsMultipleInstances="true">
...
  <Extensions>
    <uap5:Extension
      Category="windows.appExecutionAlias"
      Executable="App9.exe"
      EntryPoint="App9.App">
      <uap5:AppExecutionAlias
         desktop4:Subsystem="console" 
        iot2:Subsystem="console">
        <uap5:ExecutionAlias Alias="App9.exe"/>
      </uap5:AppExecutionAlias>
    </uap5:Extension>
  </Extensions>
</Application>

Você pode alterar o valor do Alias para algo apropriado para seu aplicativo. Novamente, com várias instâncias, a geração de código inclui um arquivo Program.cs ou Program.cpp. O código gerado fornece um exemplo de como você pode implementar a função principal necessária, conforme mostrado no exemplo de C++ Figura 4. Você pode substituir todo o código dentro do principal com seu próprio código personalizado.

Figura 4 modelo gerado código para uma função de principal do aplicativo de Console

int __cdecl main()
{
  // You can get parsed command-line arguments from the CRT globals.
  wprintf(L"Parsed command-line arguments:\n");
  for (int i = 0; i < __argc; i++)
  {
    wprintf(L"__argv[%d] = %S\n", i, __argv[i]);
  }
  wprintf(L"Press Enter to continue:");
  getchar();
}

Depois que você criou e implantou o aplicativo, você pode executá-lo de um prompt de comando regular, janela do PowerShell ou Windows-R, conforme mostrado no Figura 5. Observe que como o aplicativo usa a janela do console, se não espera criar todas as outras janelas — e, na verdade, isso não é suportado. Em vez disso, o aplicativo agora pode usar todos os APIs System.Console, além de várias APIs Win32 tradicional que agora foram adicionados à lista aprovados especificamente para dar suporte a aplicativos de console.

Executar um aplicativo UWP do Console na linha de comando
Figura 5 executando um aplicativo UWP do Console na linha de comando

Com esse recurso, você pode criar, por fim, aplicativos de linha de comando do console que tirar proveito dos benefícios do UWP, incluindo a embalagem de APPX, armazene a publicação, fácil atualizações e assim por diante.

Acesso mais amplo do sistema de arquivos

Até agora, um aplicativo UWP somente foi capaz de acessar determinadas pastas específicas, como a biblioteca de imagens e a biblioteca de músicas — e, em seguida, somente se o aplicativo declara como recursos em seu manifesto. Além disso, o aplicativo poderia obter acesso a qualquer outro lugar no sistema de arquivos, gerando uma caixa de diálogo FilePicker e avisar o usuário para escolher um local, que concede as permissões do aplicativo.

Agora, o terceiro dos principais recursos adicionados para paridade Win32 aumentam o nível de acesso do sistema de arquivos para aplicativos UWP. Isso foi feito de duas maneiras, incluindo:

  • Acesso implícito ao diretório de trabalho atual.
  • Acesso amplo do sistema de arquivos de entrada por um recurso restrito.

Qualquer aplicativo UWP (um aplicativo em janelas regular ou um aplicativo de console) que declara um AppExecutionAlias agora é concedido acesso implícito a arquivos e pastas no diretório de trabalho atual e para baixo, quando ele é ativado em uma linha de comando. O diretório de trabalho atual é de qualquer local no sistema de arquivos o usuário optar por executar o AppExecutionAlias. Isso foi discutido por um longo tempo, como o modelo UWP sempre foi muito cuidado ao conceder acesso de sistema de arquivos para aplicativos. Em equilíbrio, foi decidido que o usuário optar por executar o aplicativo de uma localização específica é equivalente ao usuário escolher um local em uma caixa de diálogo FilePicker, em termos de concessão de permissões.

É importante observar que o aplicativo tem exatamente as mesmas permissões de arquivo que o usuário que está executando o aplicativo, pode ainda haver arquivos ou pastas de aplicativo não pode acessar, porque o usuário não pode acessá-los, ou. Por exemplo, se o usuário não pode ver um arquivo oculto quando executam um comando dir, o aplicativo também não será capaz de ver esse arquivo oculto.

Para tirar proveito desse recurso, você pode codificar a substituição OnActivated para procurar CommandLineActivatedEventArgs. Isso incluirá o CurrentDirectoryPath, que nesse caso será o local do sistema de arquivos do qual o usuário executou o AppExecutionAlias. Figura 6 mostra um exemplo; aqui, o aplicativo extrai o diretório atual e o transmite para MainPage.

Figura 6 substituindo OnActivated para ativação de linha de comando

protected override void OnActivated(IActivatedEventArgs args)
{
  switch (args.Kind)
  {
    case ActivationKind.CommandLineLaunch:
      CommandLineActivatedEventArgs cmdLineArgs =
         args as CommandLineActivatedEventArgs;
      CommandLineActivationOperation operation = cmdLineArgs.Operation;
      string activationPath = operation.CurrentDirectoryPath;
      Frame rootFrame = Window.Current.Content as Frame;
      if (rootFrame == null)
      {
        rootFrame = new Frame();
        Window.Current.Content = rootFrame;
      }
      rootFrame.Navigate(typeof(MainPage), activationPath);
      Window.Current.Activate();
      break;
  }
}

Você pode codificar sua substituição MainPage OnNavigatedTo para recuperar esse caminho de NavigationEventArgs a entrada, conforme mostrado no Figura 7. Neste exemplo, o aplicativo está inicializando um StorageFolder desse caminho e, em seguida, criando um controle TreeView para os arquivos e pastas para baixo aqui.

Figura 7 a criação de uma árvore de sistema de arquivos do diretório de trabalho atual

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
  string activationPath = e.Parameter as string;
  argumentsText.Text = activationPath;
  fileTreeView.RootNodes.Clear();
  try
  {
    StorageFolder folder =
       await StorageFolder.GetFolderFromPathAsync(activationPath);
    if (folder != null)
    {
      TreeViewNode rootNode = new TreeViewNode() { Content = folder.Name };
      IReadOnlyList<StorageFolder> folders = await folder.GetFoldersAsync();
      GetDirectories(folders, rootNode);
      fileTreeView.RootNodes.Add(rootNode);
    }
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.Message);
  }
}

Nova funcionalidade

A segunda maneira mais acesso do sistema de arquivos está sendo fornecido é por meio de um novo recurso restrito. Para usá-la, você deve declarar o namespace XML restrictedcapabilities na parte superior do seu manifesto de aplicativo e incluem broadFileSystemAccess na sua lista de < recursos >:

xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap mp uap5 rescap">
...
  <Capabilities>
    <rescap:Capability Name="broadFileSystemAccess" />
  </Capabilities>

Se você declarar qualquer recurso restrito, isso dispara investigações adicionais no momento em que você enviar seu pacote à loja para publicação. Se o aplicativo é concedido a essa funcionalidade, ele terá o mesmo acesso ao sistema de arquivos que o usuário que está executando o aplicativo. Não apenas a partir do diretório de trabalho atual, mas em todos lugares em que o usuário tem acesso. Um AppExecutionAlias não é necessário se você tiver esse recurso. Como esse é um recurso tão poderoso, Microsoft concederá a funcionalidade somente se o desenvolvedor do aplicativo fornece motivos para a solicitação, uma descrição de como isso será usado e uma explicação de como isso beneficia o usuário.

Se você declarar o recurso de broadFileSystemAccess, você não precisa declarar qualquer um dos recursos de sistema de arquivos com escopo mais restrito (documentos, imagens ou vídeos); Na verdade, um aplicativo não deve declarar broadFileSystemAccess e qualquer um dos outros três recursos no sistema de arquivos.

Mesmo depois que o aplicativo tiver recebido a capacidade, também há uma verificação de tempo de execução, pois isso constitui uma preocupação de privacidade para o usuário. Assim como outras questões de privacidade, o aplicativo irá disparar um prompt de consentimento do usuário no primeiro uso. Se o usuário optar por negar permissão, o aplicativo deve ser resistente a isso. O usuário também pode alterar a mente a qualquer momento, indo para a página de sistema de arquivo relevante na lista de privacidade em configurações, conforme mostrado no Figura 8.

Nova página do sistema de arquivo nas configurações
Figura 8 nova página do arquivo sistema nas configurações

Observe que, para aproveitar o acesso de diretório de trabalho atual e as permissões broadFileSystemAccess, seu código deve usar as APIs do WinRT Windows.Storage para manipulação de arquivos.

Conclusão

Uma das estratégias de longo prazo com o UWP é preencher as lacunas com tecnologias anteriores do aplicativo — especialmente Win32 — para que o UWP é uma opção viável para tipos de aplicativo mais diferentes ao longo do tempo. Com a introdução de suporte para true várias instâncias, aplicativos UWP do console e mais amplo acesso de sistema de arquivos, três etapas grandes mais tem sido feitas nessa jornada. Código de exemplo está disponível em bit.ly/2GtzM3T, e você encontrará os modelos de projeto do Visual Studio em bit.ly/2HApmii e bit.ly/2FEIAXu.


Andrew Whitechapelé gerente de programa na divisão do Microsoft Windows, responsável para o fluxo de trabalho de ativação de aplicativo para a plataforma Universal do Windows.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Jason Holmes, Tim Kurtzman, Anis Mohammed Khaja Mohideen


Discuta esse artigo no fórum do MSDN Magazine