サービス、拡張機能、パッケージでアプリを拡張する

Windowsには、アプリが他のアプリに機能を提供したり、サード パーティのアドインを使用したりするテクノロジがいくつか用意されています。この記事では、Windows アプリ SDK デスクトップ アプリで使用できる機能拡張オプションを比較します。

機能拡張オプションの概要

テクノロジ Description パッケージ ID が必要 最小 OS
アプリ サービス を介したアプリ間の要求/応答通信 AppServiceConnection はい Windows 10 1607
アプリの拡張機能 プラグイン モデル - ホスト アプリが拡張機能パッケージからコンテンツを検出する はい Windows 10 1607
パッケージ拡張機能 uap17:PackageExtension による、より広範なパッケージ レベルでの拡張性 はい ウィンドウズ11
省略可能なパッケージ メイン アプリを補完する追加のコンテンツ パッケージ はい Windows 10 1709
リソース パッケージ 市場別に分離された言語、スケール、アクセシビリティの資産 はい ウィンドウズ10

適切なテクノロジの選択

次の場合にアプリ サービスを使用する

  • 個別のアプリ間で双方向の通信が必要です。
  • コンシューマー アプリは要求を送信し、応答を待機します。
  • API に似たインターフェイスを他のアプリに公開する場合。

例: 他のアプリがテキストを翻訳するために呼び出すことができる翻訳サービス。

次の場合にアプリ拡張機能を使用する

  • アプリには、サード パーティがコンテンツ、テーマ、またはアドインを提供するプラグイン モデルが必要です。
  • 拡張機能は、インストールされているパッケージから実行時に検出されます。
  • 拡張機能は、実行可能コードではなく、データまたは構成を提供します (コード実行ではアプリ サービスを使用する必要があります)。

例: インストールされている拡張機能パッケージからフィルター パックを検出するイメージ エディター。

パッケージ拡張機能を使用する場合

  • Windows 11では、より広範なパッケージ レベルの拡張性が必要です。
  • 拡張機能は、 PublicFolder モデルで許可されているよりも多くのパッケージ コンテンツにアクセスする必要があります。

オプション パッケージを使用する場合

  • 個別のパッケージとして配布される追加コンテンツ (DLC、Premium 機能) があります。
  • コンテンツは同じ発行元によって作成されます。

アーキテクチャのパターン

拡張機能検出対応の App Service

アプリ拡張機能とアプリ サービスを組み合わせて、完全なプラグイン アーキテクチャを作成します。

  1. ホスト アプリでは、 AppExtensionCatalog を使用して、インストールされている拡張機能を検出します。
  2. 各拡張機能は、その機能を記述するプロパティを宣言します。
  3. ユーザーが拡張機能をアクティブ化すると、ホスト アプリは双方向通信のために拡張機能のアプリ サービスに接続します。
┌─────────────────┐      ┌──────────────────┐
│   Host app       │      │  Extension app    │
│                  │      │                   │
│ AppExtension     │◄────►│ AppExtension      │
│   Catalog        │      │   declaration     │
│                  │      │                   │
│ AppService       │◄────►│ AppService        │
│   Connection     │      │   provider        │
└─────────────────┘      └──────────────────┘

コンテンツ拡張機能のみ

拡張機能が静的コンテンツ (テーマ、テンプレート、データ ファイル) を提供するより単純なシナリオの場合:

  1. ホスト アプリは、 AppExtensionCatalogを介して拡張機能を検出します。
  2. 拡張機能の PublicFolderからファイルを読み取ります。
  3. アプリ サービスは必要ありません。

UWP 拡張機能との違い

ここで説明する拡張テクノロジは、UWP の場合と同じように、Windows アプリ SDKデスクトップ アプリでも同様に機能します。MSIX パッケージ ID という 1 つの要件があります。 すべての拡張機能は、宣言用のパッケージ マニフェストと検出用のパッケージ カタログに依存します。

デスクトップ アプリがパッケージ化されていない場合、これらの拡張機能テクノロジを使用することはできません。 次のような代替アプローチを検討してください。

  • COM ベースのプラグイン インターフェイス
  • ファイル システム ベースの拡張機能の検出
  • 名前付きパイプまたはその他の IPC メカニズム

パッケージ化されていないアプリのファイル ベースのプラグイン検出

パッケージ化されていない WinUI 3 アプリの場合は、.NETのAssemblyLoadContextを使用してプラグイン システムを実装し、既知のフォルダーから拡張機能を読み込むことができます。

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

Warning

検証なしでディスクからアセンブリを読み込むと、セキュリティ上のリスクがあります。 運用環境では、読み込む前にアセンブリ署名 (Authenticode など) を確認し、プラグイン ディレクトリの ACL アクセス許可を制限し、特権を減らして別のプロセスでプラグインを実行することを検討します。

ホストとプラグインの両方が参照する別のアセンブリで共有インターフェイス コントラクトを定義します。

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

Note

isCollectible: trueAssemblyLoadContextを使用すると、実行時にプラグインをアンロードできます。 この方法では、デスクトップ アプリで MEF (Managed Extensibility Framework) によって発生する可能性があるバージョン管理の問題を回避できます。