XCopyでデプロイ可能なWinUI3のミニマムプロジェクトを作る
最近業務で利用する社内ツールをWinUI3で実装してみたりしました。なかなか勝手がわからないところもあり苦戦しましたが、新しいフレームワークを触るのは楽しいですね。
さてWinUI3のプロジェクトを新規作成手順ですが、通常はVisual Studioにテンプレートをインストールして、それを選択し作成しましょうと案内されます。いろいろいい塩梅に調整されたプロジェクトが作成されるのですが、いかんせん情報量が多すぎて何が何やらとなります。
社内ツールを作る程度なら、発行したバイナリをフォルダごとコピペする配布形式で十分です。そこでどこまで構成すればコピペでデプロイ可能な最小のWinUI3プロジェクトになるのか確認してみました。
まずはHelloWorld
最小のC#プロジェクトといえばHelloWorldするだけのコンソールアプリでしょう。というわけで以下のようなMinWinUI.csproj
とProgram.cs
を作成します。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
namespace MinWinUI;
static class Program
{
public static void Main() => Console.WriteLine("Hello world!");
}
当然ながらこのまま実行すればHelloWorldが表示されます。ここからいろいろパーツを足してWinUI3プロジェクトに仕立てていきましょう。
WinUI利用をプロジェクトファイルに明示
WPF、WinFormsでは各GUIフレームワークの利用を明示するUseWPF
、UseWindowsForms
要素をtrue
に指定しますが、WinUI3でも同様にUseWinUI
要素を指定します。
あとGUIアプリなので、OutputType
もWPFなどと同様WinExe
に変更しておきます。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
(中略)
<UseWinUI>true</UseWinUI>
</PropertyGroup>
</Project>
この時点でコンソールがアタッチされなくなるため、ビルドはできてもHelloWorldは表示されません。
Windows App SDKの参照をプロジェクトファイルに明示
前述の作業ではWinUI使いますと宣言しただけで、必要なアセンブリは一切指定していません。次はWinUI3に必要な各ファイルをNugetから参照します。やることととしては以下のようにMicrosoft.WindowsAppSDK
を参照に指定するだけです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
(中略)
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230313.1" />
</ItemGroup>
</Project>
Visual StudioのテンプレートからWinUI3プロジェクトを新規作成すると、Microsoft.Windows.SDK.BuildTools
というパッケージも参照していますが、これはMicrosoft.WindowsAppSDK
も参照しているため、プロジェクトファイルに明示しなくても自動的に依存関係から参照されます。
TFMとPlatformの指定
このままビルドをするとTargetPlatformが~といった旨のエラーが表示されるので、それを指定しましょう。TargetFramework
要素をnet7.0-windows10.0.19041.0
に変更し、Platforms
要素も追加します。対象はx86
,x64
,arm64
から適当なのをチョイスしましょう。
これでVisual Studioをでデバッグを実行しようとすると、最初に作成したコンソールプロジェクトではPlatform=AnyCPUのデバッグ構成しか作成されていないため、構成マネージャーが~と言われてエラーがでると思います。指示通り構成マネージャーから追加したPlatforms
に対応した構成を作成追加してください。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
(中略)
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<Platforms>x64</Platforms>
(中略)
</PropertyGroup>
</Project>
Appクラスを作成
ここまでプロジェクトの設定だけでしたが、ようやくコードを書きます。
WPFやってる人にはおなじみのApplication
クラスを継承したApp
クラスを定義します。今回は最小を目指すということでXAML側の定義を書きません。なのでそのままApplicaton
クラスを継承したC#のコードだけを書きます。OnLaunched
メソッドのオーバーライドでWindow
クラスのインスタンスを作成し、Activate
メソッドで表示させるように指定します。WPFなどではウインドウを表示させるときはその名の通りShow
メソッドを呼び出していましたが、WinUI3ではActivate
メソッドのようです。
using Microsoft.UI.Xaml;
namespace MinWinUI;
public class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
new Window { Title = "Hello world!" }.Activate();
}
}
定義した上記のAppクラスをMain
メソッドから呼び出します。
Main
メソッドにSTA属性を付与し、Application.Start
メソッドからメインスレッドのコンテキストを確保しておいてから、App
クラスをnew
するだけです。
今回はApp
クラスのXAML定義を省略していますが、実はXAML定義を作成するとWinUI3プロジェクトでは自動的にMain
メソッドが生成されます(WPFとWinFormsではユーザーが自分で作ったエントリーポイントを明示することができましたが、WinUIではできないようです)。このコードはその自動的に生成されたエントリーポイントをそのまま写経しただけです。
自動生成されたエントリーポイントだと必要なアセンブリのチェックなども行ってくれるので、実際のコードではApp
クラスのXAML定義をちゃんとやりましょう。
using Microsoft.UI.Xaml;
using Microsoft.UI.Dispatching;
using WinRT;
namespace MinWinUI;
static class Program
{
[STAThread]
public static void Main()
{
ComWrappersSupport.InitializeComWrappers();
Application.Start(p =>
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
}
最後の調整
さてこれでデバッグを実行するとようやくWinUIのウインドウが表示され……ません。TypeInitializationException
が発生します。必要なDLLが不足しているようなので、WindowsAppSDKSelfContained
を有効にして、DLLも吐き出してもらうようにします。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>
</Project>
これでようやくデバッグ実行できたと思います。HelloWorldなウインドウが表示されました(私のPCがダークテーマなのでBackgroundが真っ黒なウインドウ)。
あとはコピペで配布できるように発行できるかどうか確認しておきます。以下のコマンドで必要なファイル全部入りの発行を行えます。
dotnet publish -c Release -r win-x64 --sc -p:Platform=x64
で、実行すると必要なファイルを吐き出すためのビルドタスクがないといった旨のエラーがでると思います。というわけでEnableMsixTooling
を指定します。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>
</Project>
これでようやく発行も通りました。出力されたpublishフォルダをサンドボックス環境で実行したところ無事ウインドウが表示されました。
最終的にビルドを通したプロジェクトファイルを以下に書いておきます。コメントアウトしていますが、MSのドキュメントによるとWindowsPackageType
の指定も必要なはず……なのですがWindow
クラスを表示するだけなら未指定でも動いてしまいました。ちゃんと確認していないのですが、実際には必要なはず……です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<Platforms>x64</Platforms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWinUI>true</UseWinUI>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<EnableMsixTooling>true</EnableMsixTooling>
<!--<WindowsPackageType>None</WindowsPackageType>-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230313.1" />
</ItemGroup>
</Project>
using Microsoft.UI.Xaml;
namespace MinWinUI;
public class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
new Window { Title = "Hello world!" }.Activate();
}
}
using Microsoft.UI.Xaml;
using Microsoft.UI.Dispatching;
using WinRT;
namespace MinWinUI;
static class Program
{
[STAThread]
public static void Main()
{
ComWrappersSupport.InitializeComWrappers();
Application.Start(p =>
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
}
Discussion