Windows 앱이 다른 앱에 기능을 제공하거나 타사 추가 기능을 사용할 수 있는 몇 가지 기술을 제공합니다. 이 문서에서는 Windows 앱 SDK 데스크톱 앱에 사용할 수 있는 확장성 옵션을 비교합니다.
확장성 옵션 개요
| 기술 | Description | 패키지 ID 필요 | 최소 OS |
|---|---|---|---|
| App Services | 를 통해 앱 간의 요청/응답 통신 AppServiceConnection |
Yes | Windows 10 1607 |
| 앱 확장 | 플러그 인 모델 - 호스트 앱이 확장 패키지에서 콘텐츠를 검색합니다. | Yes | Windows 10 1607 |
| 패키지 확장 |
uap17:PackageExtension를 통한 더 광범위한 패키지 수준 확장성 |
Yes | 윈도우 11 |
| 선택적 패키지 | 기본 앱을 보완하는 추가 콘텐츠 패키지 | Yes | Windows 10 1709 |
| 리소스 패키지 | 시장별로 구분된 언어, 배율 및 접근성 리소스 | Yes | 윈도우 10 |
올바른 기술 선택
앱 서비스를 사용하는 경우
- 별도의 앱 간에 양방향 통신이 필요합니다.
- 소비자 앱은 요청을 보내고 응답을 기다립니다.
- API와 유사한 인터페이스를 다른 앱에 노출하려고 합니다.
예: 다른 앱에서 텍스트를 번역하기 위해 호출할 수 있는 번역 서비스입니다.
다음과 같은 경우 앱 확장을 사용하십시오
- 앱에는 타사에서 콘텐츠, 테마 또는 추가 기능을 제공하는 플러그 인 모델이 필요합니다.
- 확장은 설치된 패키지에서 런타임에 검색됩니다.
- 확장은 실행 코드가 아닌 데이터 또는 구성을 제공합니다(코드 실행은 앱 서비스를 사용해야 합니다).
예: 설치된 확장 패키지에서 필터 팩을 검색하는 이미지 편집기입니다.
패키지 확장을 사용하는 경우
- Windows 11 더 광범위한 패키지 수준 확장성이 필요합니다.
- 확장은 모델이 허용하는 것보다 더 많은 패키지 콘텐츠에
PublicFolder액세스해야 합니다.
선택적 패키지를 사용할 때
- 별도의 패키지로 배포된 추가 콘텐츠(DLC, 프리미엄 기능)가 있습니다.
- 콘텐츠는 동일한 게시자가 작성합니다.
아키텍처 패턴
확장 검색 기능이 있는 App Service
전체 플러그 인 아키텍처에 대한 앱 확장과 앱 서비스를 결합합니다.
- 호스트 앱은 설치된 확장을 검색하는 데 사용합니다
AppExtensionCatalog. - 각 확장은 해당 기능을 설명하는 속성을 선언합니다.
- 사용자가 확장을 활성화하면 호스트 앱이 양방향 통신을 위해 확장의 앱 서비스에 연결됩니다.
┌─────────────────┐ ┌──────────────────┐
│ Host app │ │ Extension app │
│ │ │ │
│ AppExtension │◄────►│ AppExtension │
│ Catalog │ │ declaration │
│ │ │ │
│ AppService │◄────►│ AppService │
│ Connection │ │ provider │
└─────────────────┘ └──────────────────┘
콘텐츠 확장만
확장이 정적 콘텐츠(테마, 템플릿, 데이터 파일)를 제공하는 간단한 시나리오의 경우:
- 호스트 앱은
AppExtensionCatalog를 통해 확장을 검색합니다. - 확장 프로그램의
PublicFolder에서 파일을 읽습니다. - 앱 서비스가 필요하지 않습니다.
UWP 확장성과의 차이점
여기에 설명된 확장성 기술은 UWP에서와 동일한 방식으로 Windows 앱 SDK 데스크톱 앱에서 작동하며 한 가지 요구 사항인 MSIX 패키지 ID를 사용합니다. 모든 확장성 기능은 선언에 대한 패키지 매니페스트와 검색을 위한 패키지 카탈로그를 사용합니다.
데스크톱 앱이 패키지되지 않은 경우 이러한 확장성 기술을 사용할 수 없습니다. 다음과 같은 다른 방법을 고려합니다.
- 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);
}
비고
AssemblyLoadContext에서 isCollectible: true를 사용하면 런타임에 플러그인을 언로드할 수 있습니다. 이 방법은 데스크톱 앱에서 (관리되는 확장성 프레임워크)에서 발생할 수 있는 MEF 버전 관리 문제를 방지합니다.
관련 콘텐츠
Windows developer