C++ で WinUI 3 を始める (preview 2)

公開:2020/10/06
更新:2020/10/06
4 min読了の目安(約4300字TECH技術記事

はじめに

お試し投稿を兼ねて前から試そうとしててやってなかった WinUI 3 を触ってみました。

WinUI とは

Windows 向けの UI ライブラリ・・・ってそのまんまですが。

私の理解としては、 Fluent Design System を実現するためのライブラリで、下回りとして Windows Runtime ベースの UI ライブラリ (これ正式にはなんて言うのかよくわからないのですが、以降は UWP API とします) を利用しているもの、ということです。

UWP API の UI コンポーネントは必ずしも Fluent Design そのものを直接実現しているわけではないので Fluent Design に沿うには自前で実装する必要が出てくる部分もあります。 WinUI を利用することでその手間をある程度省くことができると思われます。また、 Windows のバージョンアップにより UWP API 自体に新しいコンポーネントが追加された場合も旧バージョンの Windows でも利用できるようにする互換機能も用意されているそうです。

WinUI は Win32 デスクトップもサポートしていますが、 UWP API を基盤としているのには変わらないので Windows 7/8.1 で動作するわけではありません。それでは Win32 アプリとするメリットがないように感じますが、 Win32 とした場合は頒布にストアアプリの制約を受けずに自由に頒布できるようになるメリットがあります。

WinUI 2 では "Xaml Islands" という仕組み (UWP コンポーネントを Win32 ハンドル上にホストする) で Win32 から利用できるようにしているようです。

WinUI 3 では明確に "Win32 デスクトップ" で開発ができるようになりましたが、内部的にどういう仕組み (利用している API など) になっているのか GitHub のコード からではよくわかりませんでした (現時点では公開コードが WinUI 3 ではなさそう?) 。この辺は後々改めて確認したいところです。

Win32 デスクトップアプリに UWP コンポーネントを組み込んで利用するには下記が参考になります。

WinUI 3 の導入

に書いてある通りですが、 VS2019 16.7.2 以降をインストールし、

からプロジェクトテンプレートをダウンロードしてきてインストールします。

すると新規プロジェクトで WinUI を選択できるようになるので C++ の "Blank App, Packaged (WinUI in Desktop)" で開始します。

コードを書く

WinUI は UWP API をベースとしているので、 C++ で開発する場合は C++/WinRT を利用します。

C++/WinRT は VS2012 で導入された C++/CX の後継として発表されたライブラリで、 C++/CX と異なり C++ 言語自体は標準に準拠した (独自拡張をしない) まま Windows Runtime に対応したものです。 WinUI 自体も C++/WinRT で開発されています。

C++/WinRT で Windows Runtime のクラスを実装する場合、 .idl ファイルに雛形を定義し、それに則った形で C++ のクラスを実装する必要があります。これは Windows Rutime では型情報を "Windows Metadata (winmd)" に書き出す必要があるためです。ここでいう IDL は Microsoft 独自の "MIDL" で、現在は v3 となっています。

IDL を記述してコンパイルするとベースとなるテンプレートコードが生成されるので、それを元にコードを書いていきます。 WinUI で新規プロジェクトを開始すると App クラスと MainWindow クラスが登録された状態から始まるのでそれの改変から始めるとよいでしょう。

  • XAML のコードも C++ のコードに変換される
    • 名前をつけたコントロールはその名前のプロパティとしてアクセスできるように生成される
  • 外部公開対象のコードで定義するプロパティ、メソッドは MIDL に記述し、その実装を C++ で記述する

といった感じで、書き方は大分異なりますが、 C# + XAML とまあまあ似た感じで書けるようになっています。

お試しとして、テンプレート生成後の MainWindow.xaml の StackPanel を次のように書き替えました。

  <StackPanel
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Orientation="Horizontal">
    <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    <ColorPicker x:Name="colorPicker" />
  </StackPanel>

myButton_Click イベントハンドラは次のようにしました。

void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    wchar_t tmp[256];
    auto color = colorPicker().Color();
    swprintf_s(tmp, L"%d,%d,%d,%d", color.R, color.G, color.B, color.A);

    myButton().Content(box_value(tmp));
}

ボタンを押すとボタンのテキストが ColorPicker の設定値に書き変わるようになりました。

おわりに

WinUI 3 の導入に関しては C# でも C++ でも変わりないですが C++ は C++/WinRT がそもそも扱いにくいなあという感じがします。正直 C++/CX の方が分かりやすいと思う (都合のよい形に独自拡張しているので当然でしょうが) のですが、 C++ の独自拡張を排除し、 Windows Runtime 固有表現を MIDL に分離したのは "標準に準拠する" ためにとるしかない選択だったと思います (独自拡張を押し通せるような風潮ではなくなってきたのと、 VC++ 以外のコンパイラに展開できない) 。

個人的に Windows Runtime サポートは MIDL ではなく C# でできるようにした方がいいんじゃないんですかねという気がしないでもないです (C# の定義から C++ のコードを生成する) 。 MIDL でいくなら IDE 対応が全くないのをまずなんとかしてほしいですね。 MIDL が扱いやすくなってくれればやる気が出てくるかも・・・