Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

30 Jul 2020

UWP文档笔记:控制台应用以及多实例

Create a Universal Windows Platform console app

从Win10 1803开始,可以基于C++/WinRT或C++/CX来创建控制台程序。控制台程序可以通过Universal C Runtime来使用printf或者getchar这些函数。UWP控制台程序也可以被发布到Microsoft Store。可以在App list里面体现,可以在开始菜单有磁贴。可以从开始菜单或者命令行加载这些UWP控制台程序。

可以从市集上下载在Visual Studio中创建UWP控制台程序的模板。

模板会添加一个Program.cpp文件:

#include "pch.h"

using namespace winrt;

// This example code shows how you could implement the required main function
// for a Console UWP Application. You can replace all the code inside main
// with your own custom code.

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

    // Keep the console window alive in case you want to see console output when running from within Visual Studio
	  wprintf(L"Press 'Enter' to continue: ");
    getchar();
}

UWP控制台程序可以访问的文件系统范围是其所执行的目录以及该目录的子目录。可以这样是因为Package.appxmanifest中添加了AppExecutionAlias扩展。这个扩展给UWP控制台程序赋了一个别名,让不用添加到系统路径也可以在命令行被找到并运行。

模板也Package.appxmanifest中添加了SupportsMultipleInstances这个能力,所以可以运行多个UWP Console应用。此外,还添加了Subsystem="console"这个能力。

值得注意的是UWP控制台程序只能在desktop4或者iot2平台有效:

<Package
  ...
  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 uap5 desktop4 iot2">
  ...
  <Applications>
    <Application Id="App"
	  ...
      desktop4:Subsystem="console" 
      desktop4:SupportsMultipleInstances="true" 
      iot2:Subsystem="console" 
      iot2:SupportsMultipleInstances="true"  >
      ...
      <Extensions>
          <uap5:Extension 
            Category="windows.appExecutionAlias" 
            Executable="YourApp.exe" 
            EntryPoint="YourApp.App">
            <uap5:AppExecutionAlias desktop4:Subsystem="console">
              <uap5:ExecutionAlias Alias="YourApp.exe" />
            </uap5:AppExecutionAlias>
          </uap5:Extension>
      </Extensions>
    </Application>
  </Applications>
    ...
</Package>

一些考虑:

  • 只能使用C++来创建UWP控制台程序,并且只支持Desktop以及IoT
  • UWP控制台程序不能创建窗口,不能使用MessageBox()或者Location()等窗口系统的API。
  • UWP控制台程序不能使用或者提供幕后任务
  • 除了Command-Line activation以外,UWP控制台程序不支持其他的激活方式,比如file association, protocol association等等
  • UWP控制台应用支持多实例,但是不支持多实例重选
  • Win32 and COM APIs for UWP apps列举了UWP控制台应用中可用Win32接口

Create a multi-instance Universal Windows App

从Win10 1803开始, UWP app可以支持多实例。也就是说当需要加载一个App实例的时候,不会重用已有的实例,而是在新的进程中加载新实例。

Javascript支持多实例,但是不支持多实例重定向。所以AppInstance在JavaScript应用中没有太大用处。

Visual Studio提供了一个多实例的模板https://marketplace.visualstudio.com/items?itemName=AndrewWhitechapelMSFT.MultiInstanceApps。这个模板支持Multi-instance UWP app以及Multi-instance Redirection UWP app。后者加载的时候可以选择加载新实例或者重用旧实例。

两者都在package.appxmanifest中添加了SupportsMultipleInstances标注。注意这个标注只在desktop4 或者iot2类型的项目中有效。

例子:

<Package
  ...
  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">
  ...
  <Applications>
    <Application Id="App"
      ...
      desktop4:SupportsMultipleInstances="true"
	  iot2:SupportsMultipleInstances="true">
      ...
    </Application>
  </Applications>
   ...
</Package>

Multi-instance Redirection UWP app项目模板会添加一个program.cs文件,内容如下:

public static class Program
{
    // This example code shows how you could implement the required Main method to
    // support multi-instance redirection. The minimum requirement is to call
    // Application.Start with a new App object. Beyond that, you may delete the
    // rest of the example code and replace it with your custom code if you wish.

    static void Main(string[] args)
    {
        // First, we'll get our activation event args, which are typically richer
        // than the incoming command-line args. We can use these in our app-defined
        // logic for generating the key for this instance.
        IActivatedEventArgs activatedArgs = AppInstance.GetActivatedEventArgs();

        // If the Windows shell indicates a recommended instance, then
        // the app can choose to redirect this activation to that instance instead.
        if (AppInstance.RecommendedInstance != null)
        {
            AppInstance.RecommendedInstance.RedirectActivationTo();
        }
        else
        {
            // Define a key for this instance, based on some app-specific logic.
            // If the key is always unique, then the app will never redirect.
            // If the key is always non-unique, then the app will always redirect
            // to the first instance. In practice, the app should produce a key
            // that is sometimes unique and sometimes not, depending on its own needs.
            string key = Guid.NewGuid().ToString(); // always unique.
                                                    //string key = "Some-App-Defined-Key"; // never unique.
            var instance = AppInstance.FindOrRegisterInstanceForKey(key);
            if (instance.IsCurrentInstance)
            {
                // If we successfully registered this instance, we can now just
                // go ahead and do normal XAML initialization.
                global::Windows.UI.Xaml.Application.Start((p) => new App());
            }
            else
            {
                // Some other instance has registered for this key, so we'll 
                // redirect this activation to that instance instead.
                instance.RedirectActivationTo();
            }
        }
    }
}

Main()在OnLaunched和OnActivated之前运行,让你决定激活哪个实例。 AppInstance.RecommendedInstance是shell提供,提示青睐的实例信息。通过一个key来判断已有实例是否存在。

一些说明

  • 异进程幕后任务支持多实例。通常情况下,每个新的触发器会产生一个新的幕后任务实例(技术上这些实例跑在同一个进程)
  • 同进程幕后任务不支持多实例
  • 幕后音频任务不支持多实例
  • 多实例可以使用不同的名字来触发同一个幕后任务。
  • AppSerives为每一个connection加载一个单独的实例,在多实例情况下也是如此。

一些考虑

  • 前面提过,只有在desktop以及IoT项目中才支持多实例
  • 为了避免资源竞争,多实例必须分片或者同步资源访问,比如设置、本地存储、以及一些其他资源。像mutex、semaphore、event这些传统机制都可以使用
  • 如果应用声明了SupportsMultipleInstances,那么扩展就不需要声明。如果对appservice或者幕后任务以外的扩展声明SupportsMultipleInstances,但是宿主App却没有声明,那么这会产生一个错误信息
  • 应用可以通过ResourceGroup 把多个幕后任务划归到同一个宿主。这个和多实例有冲突,因为多实例默认每次激活对应一个单独的宿主。

例子: https://github.com/Microsoft/AppModelSamples/tree/master/Samples/BananaEdit

(完)

comments powered by Disqus