2018 年 5 月

第 33 卷,第 5 期

通用 Windows 平台 - 缩小 UWP-Win32 的差距

通过Andrew Whitechapel

一种重要的主题的最新的 Windows 更新已被关闭通用 Windows 平台 (UWP) 和传统的 Win32 应用程序模型之间的间隙。作为此工作的一部分,Microsoft 引入了三个主要增强功能:

  • 多实例化
  • 控制台 UWP 应用
  • 更广泛的文件系统访问

功能是相关但很大程度上相互独立。也就是说,可以创建多实例化应用程序,正则开窗应用或控制台应用程序-和它可能会或可能不需要更广泛的文件系统访问。同样,你可以创建的常规的非控制台、 非多实例应用程序具有广泛的文件系统访问权限。一个限制是控制台应用程序必须将配置为支持多个实例。

多实例化

在 Win32、 Linux 和其他应用程序模型环境中,多实例化一直都是默认值。在 UWP,形成鲜明对比,默认值进行始终了单实例存储-和事实上,多实例化不支持到目前为止。

而无需多实例化某些应用程序,采用了,但多窗口化体系结构相反,这通常需要进行大量工作 — 和复杂性和脆弱性中的结果。你必须花费大量中管理你的 windows,而不是将重点放在你的域要求的工作量。单过程多窗口从可靠性问题会受到影响:如果单个实例崩溃,将关闭所有窗口;不是如此为多实例化,每个实例在其中运行作为单独的进程。

在单实例模型中,用户可以激活的应用中通过多种方式: 通过磁贴点击中启动。通过 URL 或协议激活;通过双击已注册到应用程序; 扩展名的文件等等。(任何类型) 的第一次激活启动应用。之后,任何后续激活只需调用应用程序,应用程序可以通过重写 OnActivated 方法来处理正在运行的实例。

使用新的多实例化功能,UWP 应用,以便像 Win32 应用:如果应用程序的实例正在运行,并且收到的后续激活请求时,该平台不会调用激活现有实例。相反,它将在单独的进程中创建的新实例。

因为此功能很大程度上由 Win32 奇偶校验,它最初仅支持桌面和 IoT。引入了两个级别的多实例化的支持:

  • 多实例 UWP 应用:这是最简单的情况下,其中应用程序只是想声明,它应为多实例化的。
  • 多实例重定向 UWP 应用:这是用于复杂的情况下,其中应用程序的目标是多实例化,但它还想要有发言权中完全每个实例的激活方式。

有关这两种情况下,Visual Studio 项目模板提供,如中所示图 1

多实例应用于新项目模板
多实例应用于图 1 新项目模板

对于简单的情况下,项目模板生成空白应用程序模板代码几乎相同的代码。唯一的区别是在应用程序清单中的 SupportsMultipleInstances 属性使用。有两个其他声明:第一个适用于清单顶部 desktop4 和 iot2 XML 命名空间:

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

第二个将属性添加 < 应用程序 > 元素:

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

如果你在更新现有应用程序代码,而不是生成新的应用程序,你可以只需请手动将这些条目添加到清单。完成连接后,可以生成应用程序还可以启动多个实例。与此清单条目,每次激活你的应用程序-是否从磁贴点击或任何其他激活协定支持的应用程序,如文件关联或协议启动 — 每次激活将导致在单独的实例。就是这么简单。

多实例重定向

对于大多数应用程序,你需要执行操作的只是添加清单的条目,但对于想要更精细的控制其实例激活度应用,你可以使用第二个模板。这会添加与完全相同的相同清单项目,并还将其他文件 (对于 C# 应用程序,Program.cs) 或 Program.cpp c + + 包含标准的 Main 函数中,添加中所示图 2。这将使用此版本中引入的新 AppInstance 类。

图 2 标准的多实例重定向的主函数

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

函数执行的第一件事是获取此实例的激活参数。应用程序可能会使用这些自变量中保存为其重定向逻辑的一部分的信息。

在某些情况下,该平台可能指示建议的实例。如果是这样,你可以重定向此激活到该实例相反,如果你想使用 AppInstance.RedirectActivationTo 方法。换而言之,应用程序可以选择允许此激活请求定向到一个现有实例。如果它未将重定向,然后激活的目标实例并调用其 OnActivated 方法-,终止此新实例。

如果该平台不指示首选的实例,你继续,并构建一个密钥。示例代码编写的密钥随机数字,但通常需要将此代码,并编写基于应用程序定义的逻辑键。通常这将基于之前检索到的激活参数。例如,如果激活参数的类型 FileActivatedEventArgs,应用程序可能会作为键的一部分使用指定的文件名。一旦已组成的密钥,你可以将其传递给 FindOrRegisterInstanceForKey 方法,它返回一个 AppInstance 对象,表示此应用程序的实例。若要确定要返回的实例,该方法,请执行两项操作:

  • 它搜索具有已注册此密钥的应用的现有实例。
  • 如果没有现有的实例具有已注册此密钥,将使用此密钥注册此当前实例。

如果你已成功注册此实例,你可以现在只需继续并执行正常的应用程序初始化。对于 XAML 应用,这意味着调用 Application.Start 与应用程序类的新实例。如果另一个实例已注册此密钥,现在可以将此激活相反,转到该实例中,并允许要终止此实例中。例如,考虑的应用程序编辑文件。如果用户具有 Foo.doc 编辑在应用中,打开,然后尝试再次打开 Foo.doc,应用程序可以选择将第二个激活重定向到已有 Foo.doc 打开的实例,并防止用户在多个实例中打开同一个文件。决定要重定向,以及要选择作为重定向目标的实例的逻辑是完全应用程序定义的。

对于 XAML 应用程序中,Main 方法是正常情况下自动生成和隐藏从开发人员。在"多实例的重定向"模板中取消此行为。如果你在更新现有应用程序,您可以禁止默认 Main 方法将 DISABLE_XAML_GENERATED_MAIN 添加到应用程序的生成属性中的条件编译符号的列表。在某些应用程序类型-例如,c + + DirectX 应用程序-不隐藏文件的 Main 函数中。除了,新的 api 的 DirectX 应用程序的使用遵循如 XAML 示例所示的相同模式。

请注意,应用程序期间 Main; 只能使用的 GetActivatedEventArgs 和 RedirectActivationTo 方法如果这些调用的其他任何位置,则将会失败。这是因为如果你想要参与激活重定向,则需要执行此操作非常早期生活中的应用过程中,以及任何当然之前创建 windows。

另一方面,你可以在任何时候使用剩余 AppInstance 方法和属性。具体而言,可以使用 FindOrRegisterInstanceForKey 需要随时更新当前实例的密钥。例如,如果你的密钥基于一个文件名,并且更高版本关闭此文件,你将在该时间更新你的密钥注册。你还可用于取消注册的方法完全注销如果出于某些原因你不想要参与激活重定向此特定实例。此外,任何时候,你可以使用 AppInstance.GetInstances 方法来获取的应用程序,包括其键,以便你可以推断其状态的所有已注册实例列表。

其他注意事项

多实例化是主要的增强功能,并且初始版本涵盖了仅的主要方案。具体而言,支持是为了让多实例化前台应用程序、 控制台应用和大多数包括应用程序服务的进程外后台任务。但是,没有支持此版本中,ApplicationTrigger 任务或进程内的任何后台任务。

在开发期间,Microsoft 花费相当长的时间测试广泛的现有应用商店应用程序以查看如何,它们将执行多化时。此操作,请从 Microsoft 了解到应用程序分为三大类:

  • 不出于任何原因而需要是多实例化的应用程序。这些应用程序只是不会选择该功能。
  • 想要多实例化,并继续正常工作而无需任何代码更改的应用。这些应用程序可以只需参与到多实例化,并对其进行。
  • 想要多实例化,但需要完成的工作以允许执行模型中存在差异的应用。

与第三个类别中的应用程序的常见问题是他们正在使用某些中央资源-可能是缓存,或数据库或其他文件-和时间是单一实例它们已被安全地假定应用程序具有对该资源独占访问权。一旦他们参与到多实例化时,可能有尝试访问资源的多个实例。在此方案中,应用程序需要进行的工作来同步访问、 锁定的读取和写入,依次类推-换而言之,所有常用的同步问题,传统 Win32 应用程序需要考虑。

作为一个明显的示例,请考虑使用应用程序的本地存储。这是资源的示例,其中将访问约束在包情况下,不是过程基础-和当然应用的所有实例都共享同一个包。尽管作为单独的进程运行的应用程序的每个实例,它们将所有使用相同的本地存储和设置,所表示的 ApplicationData.Current API。如果要在本地存储中执行数据访问操作,应考虑如何防范不冲突。一个选项是使用实例唯一的文件,其中一个实例的操作不能与任何其他的冲突。或者,如果你想要跨多个实例使用的常见文件,应锁定,并相应地解锁文件的访问权限。为此,可以使用标准机制,例如命名的互斥体。

控制台 UWP 应用程序

在 UWP 布局中的另一个明显差距是能够创建无外设的控制台应用程序。在 Win32 和其他环境中,你可以创建一个用于输入和输出的控制台窗口的命令行工具。因此,我们还添加此支持。同样,没有新的 Visual Studio 项目模板,并且与多实例化的应用程序,这将生成清单的其他条目。此功能也仅限于桌面和 IoT-不最少,因为只有这些 Sku 实际上具有控制台窗口稍后再试。声明相同的 XML 命名空间。< 应用程序 > 元素包含具有子系统设置为"控制台"SupportsMultipleInstances 和子系统的属性。控制台应用必须经过多实例化-这是从传统的 Win32 控制台应用移动应用的预期的模型。此外,应用程序包括 AppExecutionAlias-,并且这也有新的子系统属性中所示图 3

图 3 控制台应用程序的其他清单条目

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

可以将别名值更改为适合于你的应用程序。同样,对于多实例化代码生成包括的 Program.cs 或 Program.cpp 文件。提供一种方式可以实现所需的主函数,生成的代码中的 c + + 示例中所示图 4。Main 内的所有代码可以都替换你自己的自定义代码。

图 4 模板生成的代码的控制台应用程序的 Main 函数

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

生成并部署了该应用后,你可以执行它从正则命令提示符、 PowerShell 窗口中或 Windows-R 中所示图 5。请注意,由于应用使用控制台窗口,它不应该创建任何其他 windows-,事实上,这不受支持。相反,所有 System.Console Api,以及许多传统的 Win32 Api 现在已添加到已批准列表专门用于支持控制台应用,现在可以使用应用程序。

从命令行执行控制台 UWP 应用
从命令行执行控制台 UWP 应用的图 5

使用此功能,你可以最后生成命令行控制台应用程序充分利用 UWP,包括 APPX 打包的好处,存储发布,便捷的更新,依此类推。

更广泛的文件系统访问

到目前为止,UWP 应用仅已能够访问某些特定的文件夹,例如图片库和音乐库,然后仅当应用程序声明为其清单中的功能。除此之外,应用程序无法通过引发 FilePicker 对话框并提示用户选择一个位置,这会将应用权限授予有权访问文件系统中的其他任何位置。

现在,Win32 奇偶校验的添加的第三个主要功能增加适用于 UWP 应用的文件系统访问的级别。这是通过两种方式包括:

  • 当前工作目录隐式访问。
  • 封闭的受限功能广泛的文件系统访问。

声明 AppExecutionAlias 任何 UWP 应用 (正则开窗应用或控制台应用程序) 被现在隐式授予访问权限的文件和文件夹中的当前工作目录和下跌,激活从命令行时。当前工作目录是从任何文件系统位置用户选择执行你 AppExecutionAlias。长时间,将会讨论这已,如 UWP 模型一直都是非常谨慎授予对应用的文件系统访问权限。总的来说,因此决定用户选择从特定位置执行应用程序相当于在 FilePicker 对话框中,在授予权限方面选择某一位置的用户。

务必要注意应用程序具有完全相同文件的权限运行应用程序的用户,所以可能仍有文件或文件夹无法访问应用程序,因为用户将无法访问它们,或者。例如,如果用户无法看到一个隐藏的文件,它们执行 dir 命令时,应用程序也不会是能够看到该隐藏的文件。

若要利用此功能,可以编写代码用于寻找 CommandLineActivatedEventArgs 的 OnActivated 替代。这将包括 CurrentDirectoryPath,在这种情况下将用户从其执行你 AppExecutionAlias 文件系统位置。图 6显示了示例; 此处,应用程序提取当前目录,并将其传递到 MainPage。

图 6 重 OnActivated 写命令行的激活

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

中所示,然后无法代码 MainPage OnNavigatedTo 重写,以从传入 NavigationEventArgs,检索此路径图 7。在此示例中,应用程序正在初始化该路径中,从 StorageFolder,然后生成的文件和文件夹从此处向下的树视图控件。

图 7 从当前工作目录中生成的文件系统树

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

新功能

提供更多的文件系统访问权限并且第二种方式是通过新的功能受到限制。若要使用此开关,必须 restrictedcapabilities XML 命名空间声明顶部的应用程序清单中,并在你 < 功能 > 列表中包括 broadFileSystemAccess:

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

如果声明任何受限的功能,这会在提交到发布的存储包的时间触发其他详细审查。如果应用程序授予此功能,它将具有与运行应用程序的用户到文件系统一样进行访问。不只是从当前工作目录但 everywhere 用户具有访问权限。如果你有此功能,则不需要 AppExecutionAlias。由于这是这类功能强大的功能,Microsoft 将授予的功能仅应用开发人员如果提供请求、 如何将使用这的描述和说明如何获得如下益处: 用户有说服力的理由。

如果声明 broadFileSystemAccess 功能,则不需要声明的任何范围更狭窄作用域的文件系统功能 (文档、 图片或视频);事实上,应用程序必须不声明 broadFileSystemAccess 和任何其他三个文件系统功能。

即使应用程序已被授予功能后,此外还有运行时检查,因为就会产生隐私问题的用户。就像其他隐私问题,应用程序将触发第一次使用用户许可提示。如果用户选择要拒绝的权限,应用程序必须能够弹性应对这。用户还可以更改她注意任何时候,方法是: 转到下的隐私列表中设置,相应的文件系统页中所示图 8

在设置中的新文件系统页上
图 8 中的新文件系统页设置

请注意,若要充分利用的当前工作目录访问权限和 broadFileSystemAccess 权限,你的代码必须使用 WinRT Windows.Storage Api 文件处理。

总结

与 UWP 长期策略之一是关闭与更早版本的应用程序技术的空白-尤其是 Win32-以便 UWP 越来越多不同的应用程序类型随时间推移的可行选项。使用支持 true 多实例化、 控制台 UWP 应用,以及更广泛的文件系统访问的介绍,三个更多的大型步骤在执行这一过程。示例代码位于bit.ly/2GtzM3T,找到 Visual Studio 项目模板在bit.ly/2HApmiibit.ly/2FEIAXu


Andrew Whitechapel是在 Microsoft Windows 的除法中,负责通用 Windows 平台的应用程序激活工作流的程序管理器。

衷心感谢以下技术专家对本文的审阅:Jason Holmes、 Tim Kurtzman、 Anis Mohammed Khaja Mohideen


在 MSDN 杂志论坛讨论这篇文章