Создание и размещение расширения приложения

В этой статье показано, как создать расширение приложения Для Windows 10 и разместить его в приложении. Расширения приложений поддерживаются в приложениях UWP и упакованных классических приложениях.

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

  • Скачайте и распакуйте пример кода расширения математики.
  • В Visual Studio 2019 откройте MathExtensionSample.sln. Задайте для типа сборки x86 (сборка>Configuration Manager, а затем измените platform на x86 для обоих проектов).
  • Развертывание решения: сборка>развертывания решения.

Введение в расширения приложений

В Windows 10 расширения приложений предоставляют функциональные возможности, аналогичные функциям подключаемых модулей, надстроек и надстроек на других платформах. Расширения приложений появились в юбилейном обновлении Windows 10 (версия 1607, сборка 10.0.14393).

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

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

При создании основного приложения для расширения вы создаете возможность разработать экосистему на основе вашего приложения, в рамках которой другие разработчики могут улучшить ваше приложение в таких направлениях, на которые вы, возможно, не рассчитывали или не имели ресурсов. Взгляните на расширения Microsoft Office, Visual Studio, браузера и т. п. Они расширяют изначальные возможности этих приложений. Расширения могут увеличить полезность и долговечность приложения.

В целом, для настройки отношения расширения приложения необходимо сделать следующее.

  1. Объявить приложение в качестве основного приложения для расширений.
  2. Объявить приложение в качестве расширения.
  3. Выбрать способ реализации расширения — в качестве службы приложения, фоновой задачи или иной вариант.
  4. Определить способ обмена данными между основным приложением и его расширениями.
  5. Использовать API-интерфейс Windows.ApplicationModel.AppExtensions в основном приложении для доступа к расширениям.

Давайте узнаем, как это сделать. Для этого ознакомимся с примером кода расширения Math Extension, который реализует гипотетический калькулятор, в который можно добавить новые функции с помощью расширений. В Microsoft Visual Studio 2019 загрузите MathExtensionSample.sln из примера кода.

Пример кода расширения Math Extension

Объявление приложения в качестве основного приложения для расширений

Чтобы приложение определяло себя как основное приложение для расширений, необходимо объявить элемент <AppExtensionHost> в соответствующем файле Package.appxmanifest. Чтобы узнать, как это сделать, см. файл Package.appxmanifest в проекте MathExtensionHost.

Package.appxmanifest в проекте MathExtensionHost

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
            <uap3:Extension Category="windows.appExtensionHost">
                <uap3:AppExtensionHost>
                  <uap3:Name>com.microsoft.mathext</uap3:Name>
                </uap3:AppExtensionHost>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

Обратите внимание на xmlns:uap3="http://..." и наличие uap3 в IgnorableNamespaces. Эти элементы необходимы, так как мы используем пространство имен uap3.

<uap3:Extension Category="windows.appExtensionHost"> идентифицирует это приложение как узел расширения.

Элемент Name в <uap3:AppExtensionHost> представляет собой имя контракта расширения. Если в расширении указано то же имя контракта расширения, основное приложение сможет его найти. Традиционно мы рекомендуем создавать имя контракта расширения на основе имени приложения или издателя, чтобы избежать возможных конфликтов с другими именами контрактов расширения.

Можно определить несколько основных приложений и расширений в одном приложении. В этом примере мы объявим одно основное приложение. Расширение определяется в другом приложении.

Объявление приложения в качестве расширения

Чтобы приложение определяло себя как расширение приложения, необходимо объявить элемент <uap3:AppExtension> в соответствующем файле Package.appxmanifest. Чтобы узнать, как это сделать, откройте файл Package.appxmanifest в проекте MathExtension.

Package.appxmanifest в проекте MathExtension:

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  IgnorableNamespaces="uap uap3 mp">
  ...
    <Applications>
      <Application Id="App" ... >
        ...
        <Extensions>
          ...
          <uap3:Extension Category="windows.appExtension">
            <uap3:AppExtension Name="com.microsoft.mathext"
                               Id="power"
                               DisplayName="x^y"
                               Description="Exponent"
                               PublicFolder="Public">
              <uap3:Properties>
                <Service>com.microsoft.powservice</Service>
              </uap3:Properties>
              </uap3:AppExtension>
          </uap3:Extension>
        </Extensions>
      </Application>
    </Applications>
    ...
</Package>

И снова обратите внимание на строку xmlns:uap3="http://..." и наличие uap3 в IgnorableNamespaces. Эти элементы необходимы, так как мы используем пространство имен uap3.

<uap3:Extension Category="windows.appExtension"> определяет это приложение как расширение.

Значения атрибутов <uap3:AppExtension> выглядят следующим образом.

attribute Описание Обязательно
имя; Это имя контракта расширения. Если оно соответствует элементу Name, объявленному в основном приложении, это приложение сможет найти данное расширение. ✔️
Идентификатор Служит уникальным идентификатором этого расширения. Так как сразу несколько расширений могут использовать одно и то же имя контракта расширения (например, приложение для рисования с поддержкой нескольких расширений), различать их можно с помощью идентификатора. Основные приложения расширений могут использовать идентификатор для получения предположительных сведений о типе расширения. Например, одно расширение может предназначаться для классических приложений, а другое — для мобильных, при этом различаться они будут идентификаторами. Также для этого можно использовать элемент Properties, как описано ниже. ✔️
Отображаемое имя Этот атрибут можно использовать в основном приложении, чтобы определить расширение для пользователя. Она может запрашиваться из новой системы управления ресурсами (ms-resource:TokenName) для локализации и может использовать ее. Локализованное содержимое загружается из пакета расширения приложения, а не из основного приложения.
Описание Этот атрибут можно использовать в основном приложении, чтобы описать расширение для пользователя. Она может запрашиваться из новой системы управления ресурсами (ms-resource:TokenName) для локализации и может использовать ее. Локализованное содержимое загружается из пакета расширения приложения, а не из основного приложения.
PublicFolder Имя папки, относящейся к корневому каталогу пакета, в которой можно обмениваться содержимым с основным приложением расширения. Традиционно в качестве имени используется "Public", но можно использовать любое имя, совпадающее с именем папки в расширении. ✔️

<uap3:Properties> — необязательный элемент, содержащий пользовательские метаданные, которые узлы могут считывать во время выполнения. В примере кода расширение реализовано как служба приложения, поэтому основному приложению нужен способ получения имени службы приложения для ее вызова. Имя службы приложений определяется в элементе <Service> , который мы определили (мы могли бы назвать его любым способом). Основное приложение в примере кода находит это свойство во время выполнения, чтобы получить имя службы приложения.

Выбор способа реализации расширения.

В сеансе Build 2016, посвященном расширениям приложений, демонстрируется, как использовать общую папку, доступ к которой предоставлен основному приложению и расширениям. В этом примере расширение реализуется с помощью файла JavaScript, хранящегося в общедоступной папке, которую вызывает узел. Преимущества этого подхода заключаются в том, что он прост, не требует компиляции и поддерживает создание целевой страницы по умолчанию, которая предоставляет инструкции для расширения и ссылку на страницу основного приложения в Microsoft Store. Подробные сведения см. в разделе Пример кода расширения приложения в Build 2016. В частности, обратите внимание на проект InvertImageExtension и InvokeLoad() в ExtensionManager.cs в проекте ExtensibilitySample.

В этом примере для реализации расширения используется служба приложения. Службы приложений обладают следующими преимуществами.

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

Код службы основного приложения

Ниже представлен код основного приложения, вызывающий службу приложения расширения.

ExtensionManager.cs в проекте MathExtensionHost

public async Task<double> Invoke(ValueSet message)
{
    if (Loaded)
    {
        try
        {
            // make the app service call
            using (var connection = new AppServiceConnection())
            {
                // service name is defined in appxmanifest properties
                connection.AppServiceName = _serviceName;
                // package Family Name is provided by the extension
                connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

                // open the app service connection
                AppServiceConnectionStatus status = await connection.OpenAsync();
                if (status != AppServiceConnectionStatus.Success)
                {
                    Debug.WriteLine("Failed App Service Connection");
                }
                else
                {
                    // Call the app service
                    AppServiceResponse response = await connection.SendMessageAsync(message);
                    if (response.Status == AppServiceResponseStatus.Success)
                    {
                        ValueSet answer = response.Message as ValueSet;
                        if (answer.ContainsKey("Result")) // When our app service returns "Result", it means it succeeded
                        {
                            return (double)answer["Result"];
                        }
                    }
                }
            }
        }
        catch (Exception)
        {
             Debug.WriteLine("Calling the App Service failed");
        }
    }
    return double.NaN; // indicates an error from the app service
}

Это обычный код для вызова службы приложения. Подробные инструкции по реализации и вызову службы приложения см. в разделе Создание и использование службы приложения.

Следует отметить процедуру определения имени службы приложения, которую требуется вызвать. Поскольку у основного приложения нет сведений о реализации расширения, расширение должно предоставить имя его службы приложения. В примере кода расширение объявляет имя службы приложений в своем файле в элементе <uap3:Properties> :

Package.appxmanifest в проекте MathExtension

    ...
    <uap3:Extension Category="windows.appExtension">
      <uap3:AppExtension ...>
        <uap3:Properties>
          <Service>com.microsoft.powservice</Service>
        </uap3:Properties>
        </uap3:AppExtension>
    </uap3:Extension>

Можно определить собственный код XML в элементе <uap3:Properties>. В этом случае мы определяем имя службы приложения, чтобы основное приложение использовало его при вызове расширения.

Когда основное приложение загружает расширение, подобный код извлекает имя службы из свойств, определенных в файле Package.appxmanifest расширения:

Update() в Файле ExtensionManager.cs в проекте MathExtensionHost

...
var properties = await ext.GetExtensionPropertiesAsync() as PropertySet;

...
#region Update Properties
// update app service information
_serviceName = null;
if (_properties != null)
{
   if (_properties.ContainsKey("Service"))
   {
       PropertySet serviceProperty = _properties["Service"] as PropertySet;
       this._serviceName = serviceProperty["#text"].ToString();
   }
}
#endregion

Имя службы приложения хранится в _serviceName, поэтому основное приложение может использовать его для вызова службы приложения.

Для вызова службы приложения также необходимо имя семейства пакета, содержащего эту службу приложения. К счастью, API расширения приложения предоставляет следующие сведения, полученные в строке: connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;

Определение способа обмена данными между основным приложением и его расширением

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

В этом примере протоколом для аргументов является ValueSet , содержащий пары ключевых значений с именем "Arg" + номер аргумента, например и Arg1Arg2. Основное приложение передает все аргументы классу ValueSet, после чего расширение использует нужные ему аргументы. Если расширение может вычислить результат, основное приложение ожидает от него возврата ValueSet, чтобы получить ключ с именем Result, который содержит значение расчета. Если этот ключ отсутствует, основное приложение делает вывод, что расширению не удалось выполнить расчет.

Код службы приложения расширения

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

Система вызывает метод OnBackgroundActivate() при активации службы приложения. Этот код настраивает обработчики событий на обработку фактического вызова службы приложения при его поступлении (OnAppServiceRequestReceived()), а также для обработки вспомогательных событий, таких как получение объекта отсрочки, обрабатывающего событие отмены или закрытия.

App.xaml.cs в проекте MathExtension.

protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    base.OnBackgroundActivated(args);

    if ( _appServiceInitialized == false ) // Only need to setup the handlers once
    {
        _appServiceInitialized = true;

        IBackgroundTaskInstance taskInstance = args.TaskInstance;
        taskInstance.Canceled += OnAppServicesCanceled;

        AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
        _appServiceDeferral = taskInstance.GetDeferral();
        _appServiceConnection = appService.AppServiceConnection;
        _appServiceConnection.RequestReceived += OnAppServiceRequestReceived;
        _appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
    }
}

Код, выполняющий функции расширения, находится в OnAppServiceRequestReceived(). Эта функция вызывается при вызове службы приложения для выполнения расчета. Она извлекает необходимые значения из класса ValueSet. Если возможно выполнить расчет, результат под ключом с именем Result помещается в класс ValueSet, который возвращается основному приложению. Как мы уже знаем, согласно определенному протоколу обмена данными между этим основным приложением и его расширениями, наличие ключа Result свидетельствует об успехе, а его отсутствие — о неудаче.

App.xaml.cs в проекте MathExtension.

private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    // Get a deferral because we use an awaitable API below (SendResponseAsync()) to respond to the message
    // and we don't want this call to get cancelled while we are waiting.
    AppServiceDeferral messageDeferral = args.GetDeferral();
    ValueSet message = args.Request.Message;
    ValueSet returnMessage = new ValueSet();

    double? arg1 = Convert.ToDouble(message["arg1"]);
    double? arg2 = Convert.ToDouble(message["arg2"]);
    if (arg1.HasValue && arg2.HasValue)
    {
        returnMessage.Add("Result", Math.Pow(arg1.Value, arg2.Value)); // For this sample, the presence of a "Result" key will mean the call succeeded
    }

    await args.Request.SendResponseAsync(returnMessage);
    messageDeferral.Complete();
}

Управление расширениями

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

Microsoft Store предоставляет расширения в виде пакетов. Класс AppExtensionCatalog находит установленные пакеты с расширениями, соответствующие имени контракта расширения основного приложения, и предоставляет события, происходящие при установке или удалении пакета расширения приложения, относящегося к основному приложению.

В примере кода класс ExtensionManager (определен ExtensionManager.cs в проекте MathExtensionHost) содержит логику для загрузки расширений и реагирования на установку и удаление пакета расширения.

Конструктор ExtensionManager использует AppExtensionCatalog для поиска расширений приложений в системе, имеющих то же имя контракта расширения, что и основное приложение.

ExtensionManager.cs в проекте MathExtensionHost project.

public ExtensionManager(string extensionContractName)
{
   // catalog & contract
   ExtensionContractName = extensionContractName;
   _catalog = AppExtensionCatalog.Open(ExtensionContractName);
   ...
}

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

Класс Extension (определен в ExtensionManager.cs в проекте MathExtensionHost) был создан для предоставления примеру кода доступа к идентификатору, описанию и логотипу расширения, а также к связанным с приложением сведениям, например включил ли пользователь расширение.

Если расширение загружено (см. Load() в ExtensionManager.cs), значит состояние пакета в порядке, а мы получили его идентификатор, логотип, описание и общую папку (в данном примере не используется и указана, только чтобы показать процедуру ее получения). Сам пакет расширения не загружается.

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

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

Пример пользовательского интерфейса вкладки расширений

При удалении расширения система предлагает пользователю подтвердить удаление пакета, содержащего данное расширение (и, возможно, другие расширения). С согласия пользователя пакет удаляется, а ExtensionManager удаляет расширение в удаленном пакете из списка расширений, доступных основному приложению.

Пользовательский интерфейс удаления

Отладка расширений и основных приложений

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

  1. Загрузите проект основного приложения в одном экземпляре Visual Studio.
  2. Загрузите расширение в другом экземпляре Visual Studio.
  3. Запустите основное приложение в отладчике.
  4. Запустите приложение расширения в отладчике. (Если вам требуется развернуть расширение, а не отладить его, для тестирования события установки пакета основного приложения выберите Сборка > Развертывание решения).

Теперь вы сможете достичь точек останова в основном приложении и его расширении. Если начать отладку самого приложения расширения, отобразится пустое окно приложения. Если вы не хотите видеть пустое окно, вы можете изменить параметры отладки для проекта расширения, чтобы не запускать приложение, а отлаживать его при запуске (щелкните правой кнопкой мыши проект расширения, Свойства>Отладка> выберите Не запускать, но отлаживать мой код при запуске). Вам по-прежнему потребуется начать отладку (F5) проекта расширения. но он будет ожидать, пока узел активирует расширение, а затем ваши точки останова в расширении будут поражены.

Отладка примера кода

В примере кода основное приложение и его расширение расположены в одном и том же решении. Для отладки выполните следующие действия.

  1. Убедитесь, что MathExtensionHost является запускаемым проектом (щелкните правой кнопкой мыши проект MathExtensionHost и выберите пункт Назначить запускаемым проектом).
  2. Разместите точку останова на Invoke в ExtensionManager.cs в проекте MathExtensionHost.
  3. Нажмите клавишу F5, чтобы запустить проект MathExtensionHost.
  4. Разместите точку останова на OnAppServiceRequestReceived в App.xaml.cs в проекте MathExtension.
  5. Начните отладку проекта MathExtension (щелкните правой кнопкой мыши проект MathExtension, отладка Запуск нового экземпляра>), чтобы развернуть его и запустить событие установки пакета на узле.
  6. В приложении MathExtensionHost перейдите на страницу Вычисление и щелкните x^y, чтобы активировать расширение. Точка останова Invoke() достигается первой; при этом можно наблюдать выполнение вызова службы приложения расширения. Затем достигается метод OnAppServiceRequestReceived() в расширении; при этом можно наблюдать, как служба приложения вычисляет результат и возвращает его.

Реализация отладки расширений в виде службы приложений

Если основное приложение не может подключиться к службе приложения для расширения, убедитесь, что атрибут <uap:AppService Name="..."> соответствует значению элемента <Service>. Если они не совпадают, имя службы, предоставленное основному приложению расширением, не будет соответствовать имени службы реализованного приложения, а основное приложение не сможет активировать расширение.

Package.appxmanifest в проекте MathExtension:

<Extensions>
   <uap:Extension Category="windows.appService">
     <uap:AppService Name="com.microsoft.sqrtservice" />      <!-- This must match the contents of <Service>...</Service> -->
   </uap:Extension>
   <uap3:Extension Category="windows.appExtension">
     <uap3:AppExtension Name="com.microsoft.mathext" Id="sqrt" DisplayName="Sqrt(x)" Description="Square root" PublicFolder="Public">
       <uap3:Properties>
         <Service>com.microsoft.powservice</Service>   <!-- this must match <uap:AppService Name=...> -->
       </uap3:Properties>
     </uap3:AppExtension>
   </uap3:Extension>
</Extensions>   

Контрольный список базовых сценариев для тестирования

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

  • Запустите основное приложение, а затем разверните приложение расширения.
    • Принимает ли основное приложение новые расширения, поступающие в процессе его работы?
  • Запустите приложение расширения, а затем разверните и запустите основное приложение.
    • Принимает ли основное приложение уже существующие расширения?
  • Запустите основное приложение, а затем удалите приложение расширения.
    • Верно ли основное приложение обнаруживает удаление?
  • Запустите основное приложение, а затем обновите приложение расширения до более новой версии.
    • Обнаруживает ли основное приложение изменение и верно ли выгружает прежние версии расширения?

Сложные сценарии для тестирования:

  • Запустите основное приложение, переместите приложение расширения на съемный носитель и извлеките носитель.
    • Обнаруживает ли основное приложение изменение состояния пакета и отключает ли расширение?
  • Запустите основное приложение, а затем повредите приложение расширения (сделайте его недействительным, измените его подпись и т. д.)
    • Обнаруживает ли основное приложение несанкционированные изменения в расширении и принимает ли необходимые меры?
  • Запустите основное приложение, а затем разверните приложение расширения с недопустимым содержимым или свойствами
    • Обнаруживает ли основное приложение недействительное содержимое и принимает ли необходимые меры?

Рекомендации по проектированию

  • Предоставьте пользовательский интерфейс, с помощью которого пользователь может видеть доступные расширения, а также включать и отключать их. Также можно добавить глифы для расширений, которые становятся недоступными из-за перехода пакета в автономный режим и т. д.
  • Покажите пользователю, где получить расширения. Возможно, на странице расширения можно предоставить запрос поиска Microsoft Store, выводящий список расширений, которые можно использовать в вашем приложении.
  • Найдите способ уведомлять пользователя о добавлении и удалении расширений. Можно создать уведомление об установке нового расширения с предложением пользователю включить его. По умолчанию расширения должны быть отключены, чтобы решение оставалось за пользователем.

Отличия расширений приложений от дополнительных пакетов

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

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

Расширения приложений являются независимыми пакетами и могут быть отдельными приложениями. У них не может быть зависимости развертывания от другого приложения. Дополнительные пакеты требуют основной пакет и не могут запуститься без него.

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

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

Комментарии

В этом разделе представлены общие сведения о расширениях приложений. Важнейшие аспекты — создание основного приложения и его объявление таковым в соответствующем файле Package.appxmanifest, создание расширения и его объявление таковым в соответствующем файле Package.appxmanifest, определение способа реализации расширения (например, в виде службы приложения, фоновой задачи или иным способом), определение способа обмена данными между основным приложением и расширениями, а также использование API-интерфейса AppExtensions для доступа к расширениям и управления ими.