C++/WinRTの始め方

## 概要

C++ にある程度慣れている人向けに C++/WinRT を紹介する。
開発環境を整え、簡単なプログラムを実行する。

## C++/WinRT とは

- Windows 10 のネイティブアプリを作るためのフレームワーク
- C++/CX のような独自言語を用いない C++17 のライブラリとして表現される
    - WRL という Template Library もあったらしいが、 C++/WinRT に取って代わられた
- 小さく、ランタイムが速いのがウリ

C++ で表現されたネイティブなライブラリってことは MFC みたいなもんね、と言ってしまえばそれまでだが、ユニバーサル Windows プラットフォーム (UWP) に対応しているのが大きく異なる。PPI (DPI) の異なる複数のディスプレイの間でウィンドウを移動してもきちんと追従する。

あとは MFC みたいなマクロによるおまじないが登場しないのはとても良い。人間が書くべきコードは大幅に減り、とても簡単に、そしてコードが見やすくなった。ただ、人間が書いていないというだけであって裏で自動生成されているコードはある。Qt や Unity の IL2CPP みたいなやり口だ。

## 開発環境の準備

はやる気持ちを抑えてまずは環境を整える。

[Visual Studio のサポート、C++/WinRT、XAML では、VSIX 拡張機能と、NuGet パッケージ](https://docs.microsoft.com/ja-jp/windows/uwp/cpp-and-winrt-apis/intro-to-using-cpp-with-winrt#visual-studio-support-for-cwinrt-xaml-the-vsix-extension-and-the-nuget-package) を読めば全て書いてあるが、タイトルからわかる通り翻訳がめちゃくちゃなので英語で読みたくない人のために最低限をここに記す。

以下を用意する:

- Windows 10 (1803以降)
- Visual Studio 2017 (可能な限り最新)
- C++/WinRT VSIX

### Windows 10

1. スタートメニューから **設定**→**更新とセキュリティ**→**開発者向け** から **開発者モード** になっておくこと

### Visual Studio 2017

1. Visual Studio Installer を起動
1. から Community, Professional, Enterprise のいずれかをインストールする
1. **ワークロード** は **ユニバーサル Windows プラットフォーム開発** を選択
    - 右の **インストールの詳細** から **C++ ユニバーサル Windows プラットフォーム ツール** にチェックを入れる
1. インストール

![ワークロード](https://qiita-image-store.s3.amazonaws.com/0/46178/43879feb-ecde-06ae-653d-0df28ecb53ae.jpeg)


### C++/WinRT VSIX

VSIX は新規プロジェクトを作成する際の雛形を提供する。すでに存在するプロジェクトには必要ない。

1. Visual Studio 2017 を起動
1. メニューの **ツール**→**機能拡張と更新プログラム...**を開く
1. 左から**オンライン**→**Visual Studio Marketplace** を開き、右上の検索フィールドから `C++/WinRT` で検索して **ダウンロード** を実行
1. Visual Studio 2017 を一度終了すると VSIX インストーラーが起動する

## プロジェクトの新規作成

ここまで出来てようやくプロジェクトの作成だ。

1. Visual Studio 2017 を起動
1. メニューの **ファイル**→**新規作成**→**プロジェクト...**
1. **Visual C++**→**Windows Universal**→**Blank App (C++/WinRT)** を選択して **OK**
    - 環境によっては何故か **Visual C++**→**テスト**→**Blank App (C++/WinRT)** に出現することもあった
1. **最小バージョン** は **Windows 10, version 1803 (10.0; ビルド 17134)** にする

![最小バージョン](https://qiita-image-store.s3.amazonaws.com/0/46178/112ab3ec-a0f5-3281-38fa-1adcb8472287.png)

## 実行してみる

### 実行

メニューからビルドして実行してみよう。`Click Me` と書かれたボタンが出てきて、クリックするとボタンが `Clicked` に変化する。

### レイアウト

この UI のレイアウトは _MainPage.xaml_ に入っている。開いてみるとすごく縮小されたレイアウトが出てきて面食らうのだが、非常に大きなスクリーンで表示していることになっていることが原因。左上のプルダウンから **4" IoT Device** など小さなスクリーンを選ぶとだいぶ見やすくなる。

下には XML で同じ内容が表示されており、こちらを編集しても即座に上の絵に反映される。

![MainPage.xaml](https://qiita-image-store.s3.amazonaws.com/0/46178/ee330235-7b04-d3b4-aa38-c2ce71056261.png)

コントロールは **ツールボックス** ウィンドウに並んでおり、ここから新たなコントロールを配置することができる。Visual Basic や MFC 時代を知っていると好き勝手並べたくなるのだが、昨今携帯端末から大型ディスプレイまで様々な画素数、PPI のディスプレイがあるため基本的には自動レイアウトになっている。XML をみると分かるが `Button` の親にある `StackPanel` というやつがそれ。

![ツールボックス](https://qiita-image-store.s3.amazonaws.com/0/46178/7fea421f-24b2-9d44-0526-affc4393c1d7.png)

イベントは `Button` に記述されている `Click="ClickHandler"` によって `ClickHandler` メソッドが呼ばれることになる。

```xml:MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
```

### 実装

_MainPage.xaml_ にぶら下がっている _MainPage.cpp_ に実装がある。`MyProperty` は32ビットのプロパティを持たせているだけで何もしていないので、実質 `MainPage` コンストラクタと `ClickHandler` だけだ。

## 改造してみる

クリップボードの中身を監視して表示するプログラムに変えてみよう。

手順としては次の通り:

1. レイアウトに TextBlock を追加する
1. ClickHandler を削除する
1. クリップボードのハンドラを追加する

### レイアウトに TextBlock を追加する

_MainPage.xaml_ の `StackPanel` を次のように書き換える:

```xml:MainPage.xaml
<StackPanel>
    <TextBlock x:Name="clipboardText" Text="コピーするとここに表示される"/>
</StackPanel>
```

これによりボタンは消滅する。

### ClickHandler を削除する

レイアウトからボタンを削除したため _MainPage.h_ と _MainPage.cpp_ から `ClickHandler` を削除する。

### クリップボードのハンドラを追加する

クリップボードは [winrt::Windows:ApplicationModel::DataTransfer::Clipboard](https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.datatransfer.clipboard) で操作できる。

ヘッダは名前空間ごとに存在するので、 `DataTransfer` 名前空間を取り込むために _pch.h_ に次の1行を追加する:

```c++:pch.h
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
```

_MainPage.cpp_ の `using namespace` が並ぶ位置に `DataTransfer` 名前空間を、ついでにこの後使う `IAsyncAction` のために `Foundation` 名前空間も追加する:

```c++:MainPage.cpp
using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::ApplicationModel::DataTransfer; // 追加
using namespace Windows::Foundation; // 追加
```

_MainPage.cpp_ の `MainPage::MainPage()` を次のように書き換える:

```c++:MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    // イベントハンドラの登録
    Clipboard::ContentChanged([this](IInspectable const, IInspectable const) -> IAsyncAction
    {
        DataPackageView content = Clipboard::GetContent();
        // クリップボードに入っているのはテキストか
        if (content.Contains(StandardDataFormats::Text())
        {
            // clipboardText に表示
            hstring text = co_await content.GetTextAsync();
            clipboardText().Text(text);
        }
    });
}
```

クリップボードからテキストを取得するためには `GetTextAsync` という非同期関数を呼び出すため、非同期待ちの `co_await` を使用している。`co_await` を呼び出すとこのラムダ関数はコルーチンになるため、戻り値の型が `IAsyncAction` になる。ラムダ関数の引数も参照にしたくなるが非同期になるため値渡しにする必要がある。

WinRT を使用すると `IAsyncAction`, `IAsyncOperation`, `co_await` があちこちで登場するようになる。ここはややこしいので C++/WinRT のドキュメントを読んで欲しいが、ボタンを押した後の処理で登場した `void MainPage::ClickHandler()` のようなやつも、中で非同期処理を行う場合は戻り値の型が `IAsyncAction MainPage::ClickHandler()` のように変化するのがポイント。

### 全体像

```xml:MainPage.xaml
<Page
    x:Class="BlankApp1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BlankApp1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel>
        <TextBlock x:Name="clipboardText" Text="コピーするとここに表示される"/>
    </StackPanel>
</Page>
```
```c++:pch.h
#pragma once
#include <windows.h>
#include <unknwn.h>
#include <restrictederrorinfo.h>
#include <hstring.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.ApplicationModel.Activation.h>
#include <winrt/Windows.UI.Xaml.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#include <winrt/Windows.UI.Xaml.Interop.h>
#include <winrt/Windows.UI.Xaml.Markup.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
```
```c++:MainPage.cpp
#include "pch.h"
#include "MainPage.h"

using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::ApplicationModel::DataTransfer;
using namespace Windows::Foundation;

namespace winrt::BlankApp1::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();

        // イベントハンドラの登録
        Clipboard::ContentChanged([this](IInspectable const, IInspectable const) -> IAsyncAction
        {
            DataPackageView content = Clipboard::GetContent();
            // クリップボードに入っているのはテキストか
            if (content.Contains(StandardDataFormats::Text()))
            {
                // clipboardText に表示
                hstring text = co_await content.GetTextAsync();
                clipboardText().Text(text);
            }
        });
    }

    int32_t MainPage::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void MainPage::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }
}
```

### 実行

ビルドして実行してみよう。ウィンドウには `コピーするとここに表示される` と表示されているはずだ。

この状態で、メモ帳などを使ってクリップボードにテキストをコピーすると、うまくいけば表示がコピーした文字列に変わる。

## 感想

Windows の C++ プログラミングに慣れていると、あまりの簡単さに驚く。プログラムも従来のものより非常に短く簡潔である。
しかし箱庭から出るにはどのようにしたらいいのかが分からない。`Microsoft.Data.Sqlite` のような `Windows` 名前空間以外のものはどのように使うのか。地道に COM で操作するのか。

長らく Windows プログラミングの座は C# に奪われていたが、これだけ簡単になれば多少は揺り戻しがあるかもしれない。いうても C++ そのものの分かりづらさ、例えば文法エラーが何を言っているのか分からないという問題があり、主力になることはないと思う、と言い訳した上で、いい時代になったと喜びたい。C++ の簡単なツールにちょっと GUI を付けたいなら C++/WinRT という選択肢ができた。

## 参照

* [C++/WinRT](https://docs.microsoft.com/ja-jp/windows/uwp/cpp-and-winrt-apis/)