WinUI3でPackaged/Unpackagedの両方に対応するための方策
はじめに
WinUI3のアプリケーションには、MSIXでアプリを配布するPackaged方式と、直接起動できる.exe
形式で配布可能なUnpackaged方式の2つの方式があります。
アプリをどちらの方式にも対応させる場合、Packaged/Unpackaged形式を切り替えてビルドやテストを行う必要が出てきます。また、それぞれの形式によって処理を分岐させたいケースもあるでしょう。
本記事ではこれらを実現する方策を、WinUI-GalleryやTemplateStudioのソースコードを参考にしながら紹介します。
なお、説明で使用しているプロジェクトは、Visual Studioが用意している下記のテンプレートプロジェクトから作成した直後のものです。
本記事で作成したプロジェクトを確認したい場合は、GitHubリポジトリを参照してください。
(前知識) Unpackaged形式にするには
前提として、WinUI3の新規プロジェクトを作成すると既定でPackaged形式になっています。これをUnpackaged形式にするためには.csproj
に以下のプロパティ設定が最低限必要になります。
<WindowsPackageType>None</WindowsPackageType>
これを設定すると、launchSettings.json
にUnpackaged形式向けの起動プロファイルも自動的に追記されます。
{
"profiles": {
"PackageTypeCheck (Package)": {
"commandName": "MsixPackage"
},
"PackageTypeCheck (Unpackaged)": {
"commandName": "Project"
}
}
}
このプロファイルを切り替えるだけで両形式とも起動できると楽なのですが、実際には先ほどのWindowsPackageType
が効いてくるので、Packaged形式で実行しようとしてもエラーが出ます。
パッケージ形式の切り替え方法
実際に両形式でパブリッシュされている良例が、MSがリリースしているWinUI-Gallery(WinUI3コンポーネントのギャラリー)です。
.csproj
を見てみると、構成(DebugやRelease等)にUnpackaged
版も追加することでPropertyGroup
を切り替えていることが分かります。
オリジナルの.csproj
では他にも色々とコンディショナルなPropertyGroup
が定義されていますが、ここではWindowsPackageType
の付け替え一点に絞って紹介します。
ソリューション構成の追加
Visual Studioのメニューバーで構成マネージャーを選択します。
アクティブソリューション構成のドロップダウンから新規作成を選択し、設定のコピー元をDebug
とした状態でDebug-Unpackaged
という構成を作成します。
Release
についても同様にRelease-Unpackaged
を作成します。
プロジェクトファイルにPropertyGroupを追加
作成した構成に基づいて、.csproj
で下記ロジックを実現できるようにします。
- Packaged形式かどうかを定義する
Packaged
というプロパティ(任意名で可)をtrue
で追加する。 -
Configuration
がDebug-Unpackaged
またはRelease-Unpackaged
の場合、Packaged
をfalse
にする。 -
Packaged
がtrue
の場合-
EnableMsixTooling
をtrue
にする。
-
-
Packaged
がfalse
の場合-
EnableMsixTooling
をfalse
にする。 -
WindowsPackageType
をNone
にする。
-
実際の.csproj
は下記のようになります。
なお、Configurations
やPublish
関連のプロパティは構成追加の時点で自動追記されているはずです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
- <EnableMsixTooling>true</EnableMsixTooling>
<Nullable>enable</Nullable>
+ <Configurations>Debug;Release;Debug-Unpackaged;Release-Unpackaged</Configurations>
+ <Packaged>true</Packaged>
+ <Packaged Condition="'$(Configuration)' == 'Debug-Unpackaged' Or '$(Configuration)' == 'Release-Unpackaged'">false</Packaged>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Packaged)' != 'true'">
+ <EnableMsixTooling>false</EnableMsixTooling>
+ <WindowsPackageType>None</WindowsPackageType>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Packaged)' == 'true'">
+ <EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>
<ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
+ <PublishReadyToRun Condition="'$(Configuration)'=='Debug-Unpackaged'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
+ <PublishTrimmed Condition="'$(Configuration)'=='Debug-Unpackaged'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>
構成を切り替えて実行
以上の設定後、起動プロファイルに合わせて構成マネージャーを選択することで、Packaged/Unpackagedを切り替えて実行できるようになります。
Unpackaged形式
Packaged形式
パッケージ形式による実装分岐(プリプロセッサ)
WinUIEx(WinUI3のWindowを拡張したライブラリ)のサンプルコードなんかではこの方法が取られています。
パッケージ形式に基づいて.csproj
で特定の定数を定義し、#if
ディレクティブで分岐させる方法です。
.csproj
で定数を定義
ここでは例としてPackaged形式の時にPACKAGED
という定数を定義します。
<PropertyGroup Condition="'$(Packaged)' == 'true'">
<EnableMsixTooling>true</EnableMsixTooling>
+ <DefineConstants>PACKAGED</DefineConstants>
</PropertyGroup>
WinUIExのExampleコードの場合、どんな定義か
WindowsPackageType
がNone
の時にUNPACKAGED
定数を定義しています。
<DefineConstants Condition="'$(WindowsPackageType)'=='None'">UNPACKAGED;$(DefineConstants)</DefineConstants>
#ifで分岐
下記の具合で分岐させることができます。Visual Studioでは実行されない#if
ブロックはグレイの文字列で表示されますが、構成を切り替えると自動的にエディタの表示も切り替わります。
using Microsoft.UI.Xaml;
namespace PackageTypeCheck;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if PACKAGED
// Packaged app
MyText.Text = "Packaged App";
#else
// Unpackaged app
MyText.Text = "Unpackaged App";
#endif
}
}
パッケージ形式による実装分岐(ランタイム)
WinUI-GalleryとTemplateStudioどちらもこの方式を採用しており、ヘルパクラスとして提供されています。本項ではこれらの実装は行わず、それぞれのプロジェクトの実装を簡単に紹介するにとどめます。
動作原理
Windows APIには呼び出し元のパッケージ情報を取得する関数(後述)が定義されています。この関数は、非パッケージのプロセスから呼び出された場合、APPMODEL_ERROR_NO_PACKAGE
(15700)というリターンコードを返す仕様になっています[1]。
このリターンコードを確認することで、ランタイムでもパッケージ形式を判断できるようになります。
WinUI-Gallery
api-ms-win-appmodel-runtime-l1-1-1.dll
を介してGetCurrentPackageId関数を呼び出し、返り値が15700か判断しています。
TemplateStudio
kernel32.dll
を介してGetCurrentPackageFullName関数を呼び出し、返り値が15700か判断しています。
Discussion