コンポーネントの拡張機能
C++/CX のツアー
コード サンプルのダウンロード
最初の Windows ストア アプリを作成する準備はできていますか。それとも、HTML/JavaScript、C#、または Visual Basic を使って Windows ストア アプリを作成したことがあるので、興味は C++ の話題に移っているでしょうか。
Visual C++ コンポーネントの拡張機能 (C++/CX) を使って、Windows ランタイム (WinRT) が提供するコントロールやライブラリの豊富なセットを C++ コードに組み込んで、これまでのスキルを新たなレベルに引き上げることができます。さらに、Direct3D を使えば、Windows ストアでひと際目を引くアプリになります。
C++/CX と聞くと、新しい言語を学ばなければならないと考えがちですが、実際には、標準ではない言語要素としては ^ 修飾子や ref new キーワードなど、ほんの一部を扱うだけです。さらに、このような要素を使用するのはアプリの境界、つまり Windows ランタイムを操作する必要があるときだけです。移植可能な ISO C++ は、アプリの中心で機能し続けます。おそらく最も優れているのは、C++/CX が完全にネイティブ コードだという点です。構文は C++/共通言語基盤 (CLI) と似ていますが、開発者が望まない限り、アプリには CLR が取り入れられません。
テスト済みの既存の C++ コードがある場合や、単に C++ の柔軟性とパフォーマンスを利用したいだけであれば、C++/CX の新しい言語全体を学習する必要はありません。今回は、Windows ストア アプリを構築する際に C++/CX の言語拡張が他とどのように異なるか、またどのようなときに C++/CX を利用すべきかを説明します。
C++/CX を選ぶ理由
どのようなアプリにも独自の要件があります。これは、すべての開発者の技能と能力が異なるのと同じです。C++、HTML/JavaScript、または Microsoft .NET Framework を使って Windows ストア アプリを作成しても問題ありませんが、次のように C++ を選ぶ理由もいくつかあります。
- C++ が好きで、スキルもある
- 作成済みでテスト済みのコードを活用したい
- Direct3D や C++ AMP などのライブラリを使って、ハードウェアの能力を引き出したい
答えは 1 つである必要はなく、言語を混在させたり、組み合わせることもできます。たとえば、Bing マップ トリップ オプティマイザーのサンプル (https://msdn.microsoft.com/ja-jp/library/windows/apps/hh699893(v=VS.110).aspx) を作成したときは、HTML と JavaScript を使って UI を定義し、C++ を使ってバックグラウンド処理を実行しました。バックグラウンド処理では、巡回セールスマンの問題を解決しています。WinRT C++ コンポーネントで並列パターン ライブラリ (PPL) (https://msdn.microsoft.com/ja-jp/library/windows/apps/dd492418.aspx) を使用し、アルゴリズムを利用可能なすべての CPU を使って並列実行し、全体のパフォーマンスを向上しました。これは JavaScript だけで実行すると大変なことになります。
C++/CX の動作方法
すべての Windows ストア アプリの中心は Windows ランタイムです。そして Windows ランタイムの中心は、アプリケーション バイナリ インターフェイス (ABI) です。WinRT ライブラリは、Windows メタデータ (.winmd) ファイルを使ってメタデータを定義します。.winmd ファイルは利用できるパブリック型を表し、ファイル フォーマットは .NET Framework アセンブリで使用するフォーマットに似ています。C++ コンポーネントでは、.winmd ファイルはメタデータのみを含み、実行可能なコードは別のファイルに含めます。これが当てはまるのは、Windows に含まれている WinRT コンポーネントです (.NET Framework の言語では、.NET Framework アセンブリと同様に、.winmd ファイルにコードとメタデータの両方を含みます)。このメタデータは、MSIL 逆アセンブラ (ILDASM) または他の CLR メタデータ リーダーで確認できます。図 1は、多くの基本 WinRT 型を含む Windows.Foundation.winmd の内容を ILDASM で示したものです。
図 1 ILDASM で Windows.Foundation.winmd を確認する
ABI は、COM のサブセットを使って構築され、Windows ランタイムが複数の言語を操作できるようにします。WinRT API を呼び出すため、.NET Framework と JavaScript には、それぞれの言語環境固有のプロジェクションが必要です。たとえば、基盤となる WinRT の文字列型の HSTRING は、.NET では System.String、JavaScript では String オブジェクト、C++/CX では Platform::String ref クラスとして表されます。
C++ は直接 COM を操作できますが、C++/CX は次の方法でこの操作を簡略化しています。
- 自動参照カウント。WinRT オブジェクトは参照回数がカウントされ、多くの場合、ヒープ領域に割り当てられます (使用する言語は無関係です)。オブジェクトは参照カウントがゼロになると破棄されます。C++/CX の利点は、参照カウントが自動的で統一されていることです。^ 構文が、この両方を可能にします。
- 例外処理。C++/CX は、エラー コードではなく、例外によってエラーを示します。基盤となる COM HRESULT の値は、WinRT の例外型に変換されます。
- WinRT API を利用するための使いやすく、パフォーマンスが高い構文。
- 新しい WinRT 型を作成するための使いやすい構文。
- 型変換を実行し、イベントや他のタスクを操作する使いやすい構文。
重要なのは、C++/CX は C++/CLI 構文を借用するものの、純粋なネイティブ コードを生成することです。Windows ランタイム C++ テンプレート ライブラリ (WRL) を使って Windows ランタイムを操作することもできますが、これについては後で紹介します。C++/CX を使用したら、このメリットを理解していただけると思います。C++/CX を使用したら、ネイティブ コードのパフォーマンスを利用し、ネイティブ コードを制御できるようになります。COM について学習する必要はありません。Windows ランタイムを操作するコードが簡潔になっているため、アプリを際立たせる中心的なロジックに集中できるようになります。
C++/CX は、/ZW コンパイラ オプションで有効になります。このスイッチは、Visual Studio を使って Windows ストア プロジェクトを作成する際に自動的に設定されます。
三目並べゲーム
新しい言語を学習する最良の方法は、その言語を使って実際に何かを構築することです。C++/CX の最も一般的な部分を実演するため、今回は三目並べ (育った場所によっては、「まるばつゲーム」や「エックスズ アンド オーズ」とも呼ばれます) を実行する Windows ストア アプリを作成しました。
このアプリには、Visual Studio の新しいアプリケーション (XAML) テンプレートを使用します。プロジェクトの名前は TicTacToe にします。このプロジェクトでは、UI の定義に XAML を使用しますが、ここでは XAML にあまり注目しません。XAML について調べるには、2012 年の Windows 8 Special Issue における Andy Rich の記事「C++/CX と XAML の概要」(msdn.microsoft.com/magazine/jj651573、英語) を参照してください。
また、アプリのロジックを定義する WinRT コンポーネントを作成するのに、Windows ランタイム コンポーネントのテンプレートを使います。コードを再利用できるよう、コンポーネント プロジェクトを別に作成します。その結果、C#、Visual Basic、C++ と XAML とを組み合わせて使用するすべての Windows ストア アプリから中核となるゲーム ロジックを使用できるようになります。
図 2に、アプリの外観を示します。
図 2 TicTacToe アプリ
Hilo C++ プロジェクトに取り組んだ際 (https://msdn.microsoft.com/ja-jp/library/windows/apps/jj160316.aspx)、モデル - ビュー - ビューモデル (MVVM:Model-View-ViewModel) パターンのとりこになりました。MVVM は、アプリの外観 (ビュー) を、基盤となるデータ (モデル) から切り離すのに役立つアーキテクチャ パターンです。ビュー モデルは、ビューとモデルを結び付けます。三目並べゲームでは MVVM を完全には使用しませんでしたが、データ バインドを使って UI をアプリのロジックから切り離すことで、アプリが作成しやすく、読みやすく、将来のメンテナンスが容易になることがわかります。Hilo C++ プロジェクトで MVVM を使った方法の詳細については、https://msdn.microsoft.com/ja-jp/library/windows/apps/jj160324.aspxを参照してください。
アプリを WinRT コンポーネントに接続するため、TicTacToe プロジェクトのプロパティ ページ ダイアログから TicTacToeLibrary プロジェクトへの参照を追加します。
単に参照を設定するだけで、TicTacToe プロジェクトは、TicTacToeLibrary プロジェクトのすべてのパブリック C++/CX 型にアクセスできるようになります。#include ディレクティブを指定したり、その他何かを行う必要はありません。
TicTacToe UI を作成する
前述のように、XAML についてそれほど詳しく説明するつもりはありませんが、スコアの表示領域、メインのプレイ領域、次のゲームの設定領域を縦方向にレイアウトしています (付属のコード ダウンロードの MainPage.xaml ファイルで XAML を確認できます)。ここでも、データ バインドを非常に広く使用しています。
図 3 に、MainPage クラス (MainPage.h) の定義を示します。
図 3 MainPage クラスの定義
#pragma once
#include "MainPage.g.h"
namespace TicTacToe
{
public ref class MainPage sealed
{
public:
MainPage();
property TicTacToeLibrary::GameProcessor^ Processor
{
TicTacToeLibrary::GameProcessor^ get() { return m_processor; }
}
protected:
virtual void OnNavigatedTo(
Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
private:
TicTacToeLibrary::GameProcessor^ m_processor;
};
}
それでは、MainPage.g.h には何があるのでしょう。.g.h ファイルには、コンパイラが生成した XAML ページの部分クラス定義が含まれます。基本的には、3 つの部分定義は、x:Name 属性を持つ XAML 要素に必要な基本クラスとメンバー変数を定義します。MainPage.g.h を次に示します。
namespace TicTacToe
{
partial ref class MainPage :
public ::Windows::UI::Xaml::Controls::Page,
public ::Windows::UI::Xaml::Markup::IComponentConnector
{
public:
void InitializeComponent();
virtual void Connect(int connectionId, ::Platform::Object^ target);
private:
bool _contentLoaded;
};
}
partial キーワードは、型宣言を複数のファイルに分散できるようにするため重要です。今回の場合、MainPage.g.h にはコンパイラが生成した部分が、MainPage.h には追加定義が含まれます。
MainPage の宣言の public キーワードと ref class キーワードに注意します。C++/CX と C++ の違いの 1 つは、クラス アクセシビリティの概念です。.NET プログラマーの方はよくご存知かもしれませんが、クラス アクセシビリティは、型またはメソッドからメタデータを認識できるかどうか、つまり外部コンポーネントからアクセスできるかどうかを意味します。C++/CX 型は、パブリックにもプライベートにもできます。パブリックは MainPage クラスにモジュールの外部 (たとえば、Windows ランタイムや他の WinRT コンポーネント) からアクセスできることを意味します。プライベート型には、モジュール内部からしかアクセスできません。プライベート型は、パブリック メソッドで C++ 型を使用する自由度を高めます。これはパブリック型では不可能です。今回の場合、MainPage クラスをパブリックにして、XAML からアクセスできるようにしています。プライベート型の例は、後で取り上げます。
ref class キーワードは、C++ 型ではなく WinRT 型であることをコンパイラに指示します。ref クラスはヒープ領域に割り当てられ、有効期間が参照カウントで表されます。ref 型は参照回数がカウントされるため、有効期間は決まっています。ref 型オブジェクトの最後の参照が解放されたら、デストラクターが呼び出され、オブジェクトのメモリが解放されます。これを .NET と比べてみると、.NET では、有効期間は明確に決まっておらず、メモリの解放にはガベージ コレクションが使用されます。
ref 型のインスタンスを作成するときは、通常、^ (ハットと発音します) 修飾子を使用します。^ 修飾子は C++ ポインター (*) と似ていますが、コードを挿入してオブジェクトの参照カウントを自動的に管理し、参照カウントがゼロに達したときにオブジェクトを削除するようにコンパイラに指示します。
Plain Old Data (POD) 構造を作成するには、値クラスまたは値構造体を使用します。値型はサイズが固定で、フィールドのみで構成されます。ref 型とは異なり、プロパティはありません。Windows::Foundation::DateTime と Windows::Foundation::Rect は、WinRT の値の型の 2 つの例です。値型のインスタンスを作成するときは、^ 修飾子を使用しません。
Windows::Foundation::Rect bounds(0, 0, 100, 100);
MainPage は sealed 宣言されていることにも注意します。sealed キーワードは、C++11 の final キーワードと似ており、それ以上型が派生されないようにします。パブリック コンストラクターを持つすべての public ref 型も sealed 宣言する必要があるので、MainPage をシールしています。これは、ランタイムが言語に依存しないようにするためです。すべての言語が継承を理解するわけではありません (JavaScript など)。
次に、MainPage のメンバーについて説明します。m_processor メンバー変数 (GameProcessor クラスは WinRT コンポーネント プロジェクトで定義しています。この型については後で説明します) は、単に MainPage クラスがシールされており、派生クラスが使用する可能性がないことからプライベートです (また、一般に、データ メンバーはカプセル化を強制できるようにするときはプライベートにします)。OnNavigatedTo メソッドが保護されているのは、MainPage の派生元の Windows::UI::Xaml::Controls::Page クラスが、このメソッドを protected 宣言しているためです。コンストラクターと Processor プロパティは、XAML からアクセスする必要があるので、どちらもパブリックにします。
public、protected 、および private の各アクセス指定子については既によくご存知でしょう。C++/CX でも意味は C++ と同じです。internal および他の C++/CX 指定子については、https://msdn.microsoft.com/ja-jp/library/windows/apps/hh969551.aspxを参照してください。internal の例については、後で紹介します。
ref クラスは、public セクションと protected セクションに、パブリックにアクセスできる型 (つまり、プリミティブ型、public ref、またはパブリックの値型) のみを含みます。これとは対照的に、C++ 型は、メソッド シグネチャとローカル関数変数にメンバー変数として ref 型を含むことがあります。次に、Hilo C++ プロジェクトの例を示します。
std::vector m_createdFiles;
Hilo チームは、このプライベート メンバー変数に Platform::Collections::Vector ではなく std::vector を使用しています。これは、クラス外部にコレクションを公開しないためです。std::vector を使用することで、可能な限り C++ コードを使用し、その意図を明確にするのに役立ちます。
次は、MainPage コンストラクターです。
MainPage::MainPage() : m_processor(ref
new TicTacToeLibrary::GameProcessor())
{
InitializeComponent();
DataContext = m_processor;
}
ref new キーワードを使って、GameProcessor オブジェクトのインスタンスを作成します。new の代わりに ref new を使っているのは WinRT 参照型オブジェクトを作成するためです。関数内でオブジェクトを作成するときは、C++ auto キーワードを使えば、型名の指定や ^ の使用の必要性が少なくなります。
auto processor = ref new TicTacToeLibrary::GameProcessor();
TicTacToe ライブラリを作成する
TicTacToe ゲームのライブラリ コードには、C++ と C++/CX が混在しています。このアプリでは、作成およびテスト済みの既存の C++ コードがあると想定しています。このコードを直接組み込み、C++/CX コードは内部の実装を XAML に接続するためにのみ追加しました。言い換えると、2 つの世界をつなげるためにのみ C++/CX を使いました。ライブラリのいくつか重要な部分を紹介し、まだ説明していない C++/CX の機能を取り上げましょう。
GameProcessor クラスは、UI のデータ コンテキストの役割を果たします (MVVM に詳しい方は、ビュー モデルを考えてください)。このクラスを宣言する際、BindableAttribute と WebHostHiddenAttribute の 2 つの属性を使用します (.NET と同様、属性を宣言する際 "Attribute" の部分は省略できます)。
[Windows::UI::Xaml::Data::Bindable]
[Windows::Foundation::Metadata::WebHostHidden]
public ref class GameProcessor sealed : public Common::BindableBase
BindableAttribute は、Windows ランタイムに型がデータ バインドをサポートしていることを伝えるメタデータを生成します。これにより、型のすべてのパブリック プロパティが XAML コンポーネントに認識されるようになります。BindableBase から派生して、バインドの実行に必要な機能を実装します。BindableBase は JavaScript ではなく XAML で使用されることを意図しているため、WebHostHiddenAttribute (https://msdn.microsoft.com/ja-jp/library/windows/apps/windows.foundation.metadata.webhosthiddenattribute.aspx) 属性を使用します。規則に従って、GameProcessor クラスもこの属性でマークし、基本的に JavaScript からは認識されないようにしました。
GameProcessor のプロパティを、public セクションと internal セクションに分割しました。public プロパティは XAML に公開され、internal プロパティはライブラリ内の他の型と関数にのみ公開されます。この区別により、コードの目的がより明確になります。
1 つの一般的なプロパティの使用パターンは、コレクションと XAML とのバインドです。
property Windows::Foundation::Collections::IObservableVector^ Cells
{
Windows::Foundation::Collections::IObservableVector^ get()
{ return m_cells; }
}
このプロパティは、グリッドに表示するセルのモデル データを定義します。セルの値が変化すると、XAML が自動的に更新されます。プロパティの型は IObservableVector で、Windows ランタイムとの完全な相互運用性を可能にするため C++/CX 専用に定義されたいくつかの型の 1 つです。Windows ランタイムは、言語に依存しないコレクション インターフェイスを定義し、各言語が独自の方法でインターフェイスを実装します。C++/CX の Platform::Collections 名前空間は、コレクション インターフェイスの具体的な実装を提供する Vector や Map などの型を提供します。そのため、Cells プロパティを IObservableVector として宣言しても、C++/CX 固有の Vector オブジェクトによってこのプロパティが戻されます。
Platform::Collections::Vector^ m_cells;
では、標準の型やコレクションではなく、Platform::String コレクションや Platform::Collections コレクションを使うのはどのような場合でしょう。たとえば、データを格納するのに std::vector と Platform::Collections::Vector のどちらを使う必要があるでしょう。経験則として、主に Windows ランタイムで作業する予定の場合は Platform 機能を使用し、内部コードまたは計算を集中的に実行するコードの場合は、std::wstring や std::vector のような標準型を使用します。また、必要なときはいつでも Vector と std::vector の間で簡単に変換できます。std::vector から Vector を作成したり、to_vector を使用して Vector から std::vector を作成したりできます。
std::vector more_numbers =
Windows::Foundation::Collections::to_vector(result);
2 つのベクター型の間でマーシャリングする際にはコピーが必要になるため、どの型が適しているかを十分検討します。
もう 1 つの一般的なタスクは、std::wstring と Platform::String の間での変換です。方法は次のとおりです。
// Convert std::wstring to Platform::String.
std::wstring s1(L"Hello");
auto s2 = ref new Platform::String(s1.c_str());
// Convert back from Platform::String to std::wstring.
// String::Data returns a C-style string, so you don’t need
// to create a std::wstring if you don’t need it.
std::wstring s3(s2->Data());
// Here's another way to convert back.
std::wstring s4(begin(s2), end(s2));
GameProcessor クラスの実装 (GameProcessor.cpp) には、2 つの興味深い点があります。1 つは、標準の C++ のみを使って checkEndOfGame 関数を実装している点です。これは、作成およびテスト済みの既存の C++ コードを組み込む方法を示すポイントの 1 つです。
もう 1 つは、非同期プログラミングの使用です。攻守を切り替えるとき、PPL タスク クラスを使ってバックグラウンドでコンピューター プレイヤーを処理します (図 4参照)。
図 4 PPL タスク クラスを使ってバックグラウンドでコンピューター プレイヤーを処理する
void GameProcessor::SwitchPlayers()
{
// Switch player by toggling pointer.
m_currentPlayer = (m_currentPlayer == m_player1) ? m_player2 : m_player1;
// If the current player is computer-controlled, call the ThinkAsync
// method in the background, and then process the computer's move.
if (m_currentPlayer->Player == TicTacToeLibrary::PlayerType::Computer)
{
m_currentThinkOp =
m_currentPlayer->ThinkAsync(ref new Vector(m_gameBoard));
m_currentThinkOp->Progress =
ref new AsyncOperationProgressHandler([this](
IAsyncOperationWithProgress^ asyncInfo, double value)
{
(void) asyncInfo; // Unused parameter
// Update progress bar.
m_backgroundProgress = value;
OnPropertyChanged("BackgroundProgress");
});
// Create a task that wraps the async operation. After the task
// completes, select the cell that the computer chose.
create_task(m_currentThinkOp).then([this](task previousTask)
{
m_currentThinkOp = nullptr;
// I use a task-based continuation here to ensure this continuation
// runs to guarantee the UI is updated. You should consider putting
// a try/catch block around calls to task::get to handle any errors.
uint32 move = previousTask.get();
// Choose the cell.
m_cells->GetAt(move)->Select(nullptr);
// Reset the progress bar.
m_backgroundProgress = 0.0;
OnPropertyChanged("BackgroundProgress");
}, task_continuation_context::use_current());
}
}
.NET プログラマーであれば、タスクとそのメソッドを C# の async と await の C++ 版と考えるでしょう。タスクはすべての C++ プログラムで使用できますが、C++/CX コード全体で使用して Windows ストア アプリを高速かつ滑らかに保ちます。Windows ストア アプリにおける非同期プログラミングの詳細については、Artur Laksberg の 2012 年 2 月の記事「PPL を使用した C++ での非同期プログラミング」(msdn.microsoft.com/magazine/hh781020) と、MSDN ライブラリの記事 (msdn.microsoft.com/library/hh750082、英語) を参照してください。
Cell クラスは、ゲーム ボードの 1 つのセルをモデル化します。このクラスでは、イベントと弱参照という 2 つ考え方をデモします。
TicTacToe プレイ領域のグリッドは、Windows::UI::Xaml::Controls::Button コントロールで構成しています。ボタン コントロールは Click イベントを発生しますが、コマンドのコントラクトを定義する ICommand オブジェクトを定義することで、ユーザー入力に応答することもできます。Click イベントの代わりに ICommand インターフェイスを使用して、Cell オブジェクトが直接応答できるようにします。XAML では、セルを定義するボタンの Command プロパティを Cell::SelectCommand プロパティにバインドします。
Hilo DelegateCommand クラスを使って、ICommand インターフェイスを実装します。DelegateCommand には、コマンドが発行されたときに呼び出す関数と、コマンドを発行できるかどうかを判断するオプションの関数があります。次に、セルごとにコマンドを設定する方法を示します。
m_selectCommand = ref new DelegateCommand(
ref new ExecuteDelegate(this, &Cell::Select), nullptr);
XAML プログラミングを行う際に事前に定義されているイベントをよく使用しますが、独自のイベントを定義することもできます。今回は、Cell オブジェクトが選択されたときに発生するイベントを作成しました。GameProcessor クラスはこのイベントを処理して、ゲームの終了をチェックし、必要に応じてプレイヤーの攻守を切り替えます。
イベントを作成するには、まずデリゲート型を作成する必要があります。デリゲート型は、関数ポインターまたは関数オブジェクトと考えます。
delegate void CellSelectedHandler(Cell^ sender);
次に、Cell オブジェクトごとに 1 つイベントを作成します。
event CellSelectedHandler^ CellSelected;
以下に、GameProcessor クラスがセルごとにイベントをサブスクライブする方法を示します。
for (auto cell : m_cells)
{
cell->CellSelected += ref new CellSelectedHandler(
this, &GameProcessor::CellSelected);
}
^ とメンバー関数へのポインター (PMF) から作成されたデリゲートには、^ オブジェクトへの弱参照のみが含まれ、循環参照を引き起こしません。
次に、選択されたときに Cell オブジェクトがイベントを発生させる方法を示します。
void Cell::Select(Platform::Object^ parameter)
{
(void)parameter;
auto gameProcessor =
m_gameProcessor.Resolve();
if (m_mark == L'\0' && gameProcessor != nullptr &&
!gameProcessor->IsThinking &&
!gameProcessor->CanCreateNewGame)
{
m_mark = gameProcessor->CurrentPlayer->Symbol;
OnPropertyChanged("Text");
CellSelected(this);
}
}
このコードの Resolve 呼び出しの目的は何でしょう。GameProcessor クラスには Cell オブジェクトのコレクションが含まれますが、それぞれの Cell オブジェクトが親 GameProcessor にアクセスできるようにする必要があります。Cell に親への強参照 (GameProcessor^) が含まれていると、循環参照が引き起こされます。循環参照は、オブジェクトが解放されない原因になります。これは、相互の関連付けにより、両方のオブジェクトが常に参照を 1 つ以上持つようになるためです。これを避けるには、Platform::WeakReference メンバー変数を作成し、Cell コンストラクターからこの変数を設定します (有効期間管理とどのオブジェクトが何を含むかについてよく考えます)。
Platform::WeakReference m_gameProcessor;
WeakReference::Resolve を呼び出すときにオブジェクトがもう存在しなければ nullptr が返されます。GameProcessor が Cell オブジェクトを所持しているため、GameProcessor オブジェクトは常に有効であると想定します。
TicTacToe ゲームの例では、新しいゲーム ボードを作成するたびに循環参照をなくすことができますが、一般には、コードの管理が困難になる場合があるため、循環参照をなくす必要がないようにします。そのため、親子関係があり、子から親にアクセスする必要がある場合は弱参照を使用します。
インターフェイスを操作する
人間のプレイヤーとコンピューター プレイヤーを区別するため、IPlayer インターフェイスを HumanPlayer と ComputerPlayer という具体的な実装で作成しました。GameProcessor クラスには、それぞれのプレイヤー用に用意した 2 つの IPlayer オブジェクトと、現在のプレイヤーへの追加参照を保持します。
IPlayer^ m_player1;
IPlayer^ m_player2;
IPlayer^ m_currentPlayer;
図 5 に、IPlayer インターフェイスを示します。
図 5 IPlayer インターフェイス
private interface class IPlayer
{
property PlayerType Player
{
PlayerType get();
}
property wchar_t Symbol
{
wchar_t get();
}
virtual Windows::Foundation::IAsyncOperationWithProgress^
ThinkAsync(Windows::Foundation::Collections::IVector^ gameBoard);
};
IPlayer インターフェイスはプライベートなのに、なぜ単に C++ クラスを使わなかったのでしょう。正直に言うと、インターフェイスの作成方法と、メタデータに発行されないプライベート型の作成方法を示すためです。再利用可能なライブラリを作成する場合は、IPlayer をパブリック インターフェイスとして宣言し、他のアプリから使用できるようにします。それ以外の場合は、C++ にこだわり、C++/CX インターフェイスは使用しません。
ComputerPlayer クラスは、バックグラウンドでミニマックスのアルゴリズムを実行して ThinkAsync を実装します (この実装については、付属のコード ダウンロードの ComputerPlayer.cpp ファイルを参照してください)。
ミニマックスは、三目並べのようなゲームの人工知能コンポーネントを作成する際によく使われるアルゴリズムです。ミニマックスの詳細については、Stuart Russell と Peter Norvig の『Artificial Intelligence: A Modern Approach』(Prentice Hall、2010 年) を参照してください。
Russell と Norvig のミニマックス アルゴリズムを採用し、PPL を使って並列実行しました (コード ダウンロードの minimax.h を参照してください)。今回は純粋な C++11 を使ってアプリのプロセッサに負荷のかかる部分を作成するすばらしい機会でした。まだコンピューターに勝っておらず、コンピューター対コンピューターのゲームでもコンピューターが自身に勝つのも見ていません。これでは、おもしろいゲームとは言えないのを認めます。そこで、次に皆さんの行動が必要です。ゲームで勝てるようにするために、ロジックを追加します。これを行う基本的な方法は、コンピューターが不定期にランダムな選択を行うようにすることです。より洗練された方法としては、コンピューターが不定期に最適でない動作を意図的に行うようにします。ゲームに勝てるようにするため、ゲームの難易度を調整する UI にスライダー コントロールを追加します (難易度が下がるほど、コンピューターは最適でない動作か、少なくともランダムの動作を選択するようになります)。
HumanPlayer クラスでは、ThinkAsync は何もすることがないので、Platform::NotImplementedException をスローします。これには、まず IPlayer::Player プロパティをテストする必要がありますが、これによってタスクを節約できます。
IAsyncOperationWithProgress^
HumanPlayer::ThinkAsync(IVector^ gameBoard)
{
(void) gameBoard;
throw ref new NotImplementedException();
}
WRL
C++/CX で必要なことを実行できないか、直接 COM を操作することを望む場合に強力なツールがあります。それが WRL です。たとえば、Microsoft メディア ファンデーション用のメディア拡張を作成する際、COM インターフェイスと WinRT インターフェイスを両方実装するコンポーネントを作成する必要があります。C++/CX ref クラスが実装できるのは WinRT インターフェイスのみなので、メディア拡張を作成するには WRL を使用する必要があります。これは、WRL が COM インターフェイスと WinRT インターフェイスの実装を両方サポートしているためです。WRL プログラミングの詳細については、https://msdn.microsoft.com/ja-jp/library/vstudio/hh438466.aspxを参照してください。
さらに先へ
最初は C++/CX の拡張機能について懸念がありましたが、すぐに自分の一部のようになりました。Windows ストア アプリを迅速に作成し、最新の C++ のイディオムを使用できるため、気に入っています。C++ 開発者の方は、少なくともこれらを試してみることを強くお勧めします。
ここで紹介したのは、C++/CX コードを作成する際に生じるいくつかの一般的なパターンのみです。C++ と XAML を使った写真アプリである Hilo は、まだまだ進化の可能性を秘めています。Hilo C++ プロジェクトの作業を楽しみましたし、実際に新しいアプリを作成する際に何度も参照しています。これについては、https://msdn.microsoft.com/ja-jp/library/windows/apps/jj160326.aspxを参照することをお勧めします。
Thomas Petchelは、マイクロソフトの開発部門のシニア プログラミング ライターとして働いています。過去 8 年間を Visual Studio チームで過ごし、開発者向けのドキュメントとコード サンプルを作成してきました。
この記事のレビューに協力してくれた技術スタッフの Michael Blome (マイクロソフト) と James McNellis (マイクロソフト) に心より感謝いたします。
Michael Blome は、マイクロソフトに 10 年以上勤務しており、Visual C++、DirectShow、C# 言語リファレンス、LINQ、および .NET Framework における並列プログラミングについての MSDN ドキュメントを書いては書き直す、果てしない仕事に取り組んできました。
James McNellis は、C++ を愛好しており、マイクロソフトの Visual C++ チームのソフトウェア開発者として、すばらしい C と C++ のライブラリを構築しています。スタック オーバーフローについて多くの貢献を残し、ツイッターは @JamesMcNellis (英語) からフォローでき、jamesmcnellis.com(英語) からオンラインで見つけられます。