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

Windows предоставляет несколько технологий, которые позволяют приложению предоставлять функциональные возможности другим приложениям или использовать сторонние надстройки. В этой статье сравниваются доступные параметры расширяемости для Windows App SDK классических приложений.

Общие сведения о параметрах расширяемости

Технология Description Требуется идентификатор пакета Минимальная ОС
Службы приложений Взаимодействие между приложениями по модели «запрос-ответ» AppServiceConnection Да Windows 10 1607
Расширения приложений Модель подключаемого модуля — ведущее приложение обнаруживает содержимое из пакетов расширений Да Windows 10 1607
Расширения пакетов Более широкая расширяемость на уровне пакета с помощью uap17:PackageExtension Да Windows 11
Дополнительные пакеты Дополнительные пакеты содержимого, дополняющие основное приложение Да Windows 10 1709
Пакеты ресурсов Языковые ресурсы, ресурсы для масштабирования и доступности, разделённые по рынкам Да Windows 10

Выбор правильной технологии

Используйте службы приложений, когда

  • Требуется двустороннее взаимодействие между отдельными приложениями.
  • Клиентское приложение отправляет запрос и ожидает ответа.
  • Вы хотите предоставить интерфейс, похожий на API, другим приложениям.

Пример: служба перевода, которую другие приложения могут вызывать для перевода текста.

Используйте расширения приложений, когда

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

Пример: редактор изображений, который обнаруживает пакеты фильтров из установленных пакетов расширений.

Используйте расширения пакетов, когда

  • Для Windows 11 требуется более широкая расширяемость на уровне пакета.
  • Расширениям требуется доступ к большему содержимому пакета, чем PublicFolder позволяет модель.

Используйте необязательные пакеты, когда

  • У вас есть дополнительное содержимое (DLC, премиум-функции), распределенное в виде отдельных пакетов.
  • Содержимое создается тем же издателем.

Шаблоны архитектуры

Служба приложений с обнаружением расширений

Объединение расширений приложений со службами приложений для полной архитектуры подключаемых модулей:

  1. Ваше основное приложение использует AppExtensionCatalog для обнаружения установленных расширений.
  2. Каждое расширение объявляет свойства, описывающие его возможности.
  3. Когда пользователь активирует расширение, ведущее приложение подключается к службе приложений расширения для двустороннего взаимодействия.
┌─────────────────┐      ┌──────────────────┐
│   Host app       │      │  Extension app    │
│                  │      │                   │
│ AppExtension     │◄────►│ AppExtension      │
│   Catalog        │      │   declaration     │
│                  │      │                   │
│ AppService       │◄────►│ AppService        │
│   Connection     │      │   provider        │
└─────────────────┘      └──────────────────┘

Только расширение содержимого

Для более простых сценариев, когда расширения предоставляют статическое содержимое (темы, шаблоны, файлы данных):

  1. Хост-приложение обнаруживает расширения через AppExtensionCatalog.
  2. Он считывает файлы из расширения PublicFolder.
  3. Служба приложений не требуется.

Различия от расширяемости UWP

Технологии расширяемости, описанные здесь, работают так же, как и в классических приложениях Windows App SDK, как и в UWP, с одним требованием: удостоверение пакета MSIX. Все возможности расширения опираются на манифест пакета для объявления и на каталог пакетов для обнаружения.

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

  • Интерфейсы подключаемых модулей на основе COM
  • Обнаружение расширений на основе файловой системы
  • Именованные каналы или другие механизмы межпроцессного взаимодействия

Обнаружение плагинов на основе файлов для приложений без пакета

Для неупакованных приложений WinUI 3 можно реализовать систему подключаемых модулей с помощью AssemblyLoadContext в .NET для загрузки расширений из определённой папки:

public class PluginLoader
{
    private readonly string _pluginDirectory;

    public PluginLoader(string pluginDirectory)
    {
        _pluginDirectory = pluginDirectory;
    }

    public IEnumerable<T> LoadPlugins<T>() where T : class
    {
        if (!Directory.Exists(_pluginDirectory))
            yield break;

        foreach (var dll in Directory.GetFiles(_pluginDirectory, "*.dll"))
        {
            var context = new PluginLoadContext(dll);
            var assembly = context.LoadFromAssemblyPath(Path.GetFullPath(dll));

            foreach (var type in assembly.GetTypes()
                .Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract))
            {
                if (Activator.CreateInstance(type) is T plugin)
                    yield return plugin;
            }
        }
    }
}

// Custom AssemblyLoadContext to isolate plugin dependencies
public class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;

    public PluginLoadContext(string pluginPath) : base(isCollectible: true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var path = _resolver.ResolveAssemblyToPath(assemblyName);
        return path != null ? LoadFromAssemblyPath(path) : null;
    }
}

Предупреждение

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

Определите общий контракт интерфейса в отдельной сборке, на которую ссылаются как основное приложение, так и подключаемые модули:

// Contoso.App.Contracts (shared assembly)
public interface IPluginExtension
{
    string Name { get; }
    string Description { get; }
    void Execute(IServiceProvider services);
}

Замечание

Использование isCollectible: true в AssemblyLoadContext позволяет выгружать подключаемые модули в процессе выполнения. Этот подход позволяет избежать проблем с версионированием, которые MEF (Managed Extensibility Framework) может вызывать в классических приложениях.