C++/CX から C++/WinRT への移行
このトピックは、C++/CX プロジェクトのソース コードを C++/WinRT の同等のコードに移植する方法について説明する一連のトピックの最初のものです。
プロジェクトで Windows ランタイム C++ テンプレート ライブラリ (WRL) 型も使用している場合は、「WRL から C++/WinRT への移行」を参照してください。
移植の方法
C++/CX から C++/WinRT への移植は一般的に簡単ですが、並列パターン ライブラリ (PPL) タスクのコルーチンへの移動は 1 つの例外であるということを知っておいてください。 モデルはそれぞれ異なります。 PPL タスクからコルーチンへの自然な 1 対 1 のマッピングはなく、すべてのケースで動作するコードを機械的に移植するための簡単な方法はありません。 移植のこの特定の側面と、2 つのモデル間の相互運用のためのオプションについては、「非同期性、および C++/WinRT と C++/CX 間の相互運用」を参照してください。
開発チームは常に、非同期コードを移植するハードルを超えれば、残りの移植作業は主に機械的であるということを報告しています。
1 パスでの移植
プロジェクト全体を 1 回の動作で移植できる場合、必要な情報はこのトピックだけになります (この後に続く相互運用に関するトピックは不要です)。 まず、いずれかの C++/WinRT プロジェクト テンプレートを使用して、Visual Studio で新しいプロジェクトを作成することをお勧めします (C++/WinRT の Visual Studio のサポートに関する記事を参照してください)。 その後、ソース コード ファイルを新しいプロジェクトに移動し、その際にすべての C++/CX ソース コードを C++/WinRT に移植します。
または、既存の C++/CX プロジェクト内で移植作業を行う場合は、それに C++/WinRT サポートを追加する必要があります。 これを実行する手順については、「C++/CX プロジェクトを取得して C++/WinRT サポートを追加する」を参照してください。 移植が完了するまでには、純粋な C++/CX プロジェクトが純粋なプロジェクト C++/WinRT に変更されます。
注意
Windows ランタイム コンポーネント プロジェクトがある場合は、1 回の動作での移植が唯一のオプションです。 C++ で記述された Windows ランタイム コンポーネント プロジェクトには、すべて C++/CX のソース コードまたはすべて C++/WinRT のソース コードのいずれかが含まれている必要があります。 このプロジェクトの種類でこれらを共存させることはできません。
プロジェクトの段階的な移植
前のセクションで説明したように、Windows ランタイム コンポーネント プロジェクトを除き、コードベースのサイズまたは複雑さによってプロジェクトを段階的に移植する必要がある場合は、C++/CX と C++/WinRT のコードがしばらくの間同じプロジェクト内に共存する移植プロセスが必要になります。 このトピックの参照に加えて、「C++/WinRT と C++/CX 間の相互運用」および「非同期性、および C++/WinRT と C++/CX 間の相互運用」も参照してください。 これらのトピックでは、2 つの言語プロジェクションを相互運用する方法を示す情報とコード例を提供します。
段階的な移植プロセス用にプロジェクトを準備するには、1 つのオプションとして C++/CX プロジェクトに C++/WinRT サポートを追加する方法があります。 これを実行する手順については、「C++/CX プロジェクトを取得して C++/WinRT サポートを追加する」を参照してください。 その後、段階的に移植できます。
別の方法として、いずれかの C++/WinRT プロジェクト テンプレートを使用して、Visual Studio で新しいプロジェクトを作成します (C++/WinRT の Visual Studio のサポートに関する記事を参照してください)。 その後、 そのプロジェクトに C++/CX サポートを追加します。 これを実行する手順については、「C++/WinRT プロジェクトを取得して C++/CX サポートを追加する」を参照してください。 その後、ソース コードのそこへの移動を開始し、その際に "一部の" C++/CX ソース コードを C++/WinRT に移植します。
どちらの場合も、C++/WinRT コードとまだ移植されていない C++/CX コードの間で (両方向で) 相互運用することになります。
注意
C++/CX と Windows SDK の両方で、ルート名前空間 Windows で型を宣言します。 C++/WinRT に投影された Windows 型は Windows 型と同じ完全修飾名を持ちますが、 C++ winrt 名前空間に配置されます。 このように名前空間が異なるため、C++/CX から C++/WinRT への移植を自身のペースで行えます。
XAML プロジェクトの段階的な移植
重要
XAML を使用するプロジェクトでは、XAML ページのすべての種類は、完全に C++/CX であるか、または完全に C++/WinRT である必要があります。 それでも、(model や viewmodel などの) 同じプロジェクト内の XAML ページの種類の "外部" で、C++/CX と C++/WinRT を混在させることができます。
このシナリオで推奨されるワークフローは、新しい C++/WinRT プロジェクトを作成し、ソース コードとマークアップを C++/CX プロジェクトからコピーすることです。 すべての XAML ページの種類が C++/WinRT である限り、[プロジェクト]>[新しい項目の追加...]>[Visual C++]>[空のページ] (C++/WinRT) を使用して新しい XAML ページを追加できます。
または、Windows ランタイム コンポーネント (WRC) を使用して、移植時に XAML C++/CX プロジェクトからコードを除外することもできます。
- 新しい C++/CX WRC プロジェクトを作成し、そのプロジェクトにできるだけ多くの C++/CX コードを移動して、XAML プロジェクトを C++/WinRT に変更することができます。
- または、新しい C++/WinRT の WRC プロジェクトを作成し、XAML プロジェクトは C++/CX のままとし、C++/CX から C++/WinRT への移植と、XAML プロジェクトからコンポーネント プロジェクトへの結果コードの移動を開始できます。
- また、同じソリューション内に C++/CX コンポーネント プロジェクトと C++/WinRT コンポーネント プロジェクトを一緒に用意し、ご利用のアプリケーション プロジェクトから両方を参照し、一方からもう一方に徐々に移植することもできます。 ここでも、同じプロジェクト内で 2 つの言語プロジェクションを使用する方法の詳細については、「C++/WinRT と C++/CX 間の相互運用」を参照してください。
C++/CX プロジェクトを C++/WinRT に移植するための最初の手順
移植方法 (1 回の動作での移植または段階的な移植) に関係なく、最初の手順は、移植のためにプロジェクトを準備することです。 ここでは、開始するプロジェクトの種類とその設定方法について、「移植の方法」で説明した内容を要約します。
- 1 パスでの移植。 いずれかの C++/WinRT プロジェクト テンプレートを使用して Visual Studio で新しいプロジェクトを作成します。 C++/CX プロジェクトからその新しいプロジェクトにファイルを移動し、C++/CX ソース コードを移植します。
- XAML 以外のプロジェクトの段階的な移植。 C++/CX プロジェクトに C++/WinRT サポートを追加することを選択し (「C++/CX プロジェクトを取得して C++/WinRT サポートを追加する」を参照)、段階的に移植できます。 または、新しい C++/WinRT プロジェクトを作成し、それに C++/CX サポートを追加し (「C++/WinRT プロジェクトを取得して C++/CX サポートを追加する」を参照)、ファイルを移動して、段階的に移植することもできます。
- XAML プロジェクトの段階的な移植。 新しい C++/WinRT プロジェクトを作成し、ファイルを移動して、段階的に移植します。 XAML ページの種類は常に、すべて C++/WinRT "または" すべて C++/CX の "いずれか" である必要があります。
このトピックの残りの部分は、選択する移植方法に関係なく適用されます。 これには、ソース コードを C++/CX から C++/WinRT に移植するために必要な技術情報のカタログが含まれています。 段階的に移植する場合は、「C++/WinRT と C++/CX 間の相互運用」および「非同期性、および C++/WinRT と C++/CX 間の相互運用」も参照してください。
ファイルの名前付け規則
XAML マークアップ ファイル
ファイルの発生元 | C++/CX | C++/WinRT |
---|---|---|
開発者の XAML ファイル | MyPage.xaml MyPage.xaml.h MyPage.xaml.cpp |
MyPage.xaml MyPage.h MyPage.cpp MyPage.idl (下記を参照) |
生成された XAML ファイル | MyPage.xaml.g.h MyPage.xaml.g.hpp |
MyPage.xaml.g.h MyPage.xaml.g.hpp MyPage.g.h |
C++/WinRT では *.h
と *.cpp
のファイル名から .xaml
が削除されることに注意してください。
C++/WinRT では、追加の開発者ファイルである Midl ファイル (.idl) が追加されます。 C++/CX ではこのファイルが内部的に自動生成されて、パブリック メンバーと保護されたメンバーのすべてに追加されます。 C++/WinRT では、自分でファイルを追加して作成します。 IDL の使用に関する詳細、コード、およびチュートリアルについては、「XAML コントロール、C++/WinRT プロパティへのバインド」をご覧ください。
「ランタイム クラスを Midl ファイル (.idl) にファクタリングする」も参照してください
ランタイム クラス
C++/CX ではヘッダー ファイルの名前に制限は適用されません。特に小規模なクラスの場合は、複数のランタイムクラス定義を 1 つのヘッダー ファイルに組み入れるのが一般的です。 ただし、C++/WinRT では、各ランタイム クラスに、クラス名に基づいて名前が付けられた独自のヘッダー ファイルがある必要があります。
C++/CX | C++/WinRT |
---|---|
Common.href class A { ... } ref class B { ... } |
Common.idlruntimeclass A { ... } runtimeclass B { ... } |
A.hnamespace implements { struct A { ... }; } |
|
B.hnamespace implements { struct B { ... }; } |
C++/CX ではあまり一般的ではない (ただし合法的ではある) こととして、XAML カスタム コントロールには異なる名前のヘッダー ファイルを使用することがあります。 これらのヘッダー ファイルの名前は、クラス名と一致するように変更する必要があります。
C++/CX | C++/WinRT |
---|---|
A.xaml<Page x:Class="LongNameForA" ...> |
A.xaml<Page x:Class="LongNameForA" ...> |
A.hpartial ref class LongNameForA { ... } |
LongNameForA.hnamespace implements { struct LongNameForA { ... }; } |
ヘッダー ファイルの要件
C++/CX では、ヘッダー ファイルが .winmd
ファイルから内部的に自動生成されるため、特別なヘッダー ファイルを含める必要はありません。 C++/CX では、名前によって使用する名前空間に using
ディレクティブを使用するのが一般的です。
using namespace Windows::Media::Playback;
String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
return item->VideoTracks->GetAt(0)->Name;
}
using namespace Windows::Media::Playback
ディレクティブを使用すると、名前空間プレフィックスなしで MediaPlaybackItem
を書き込むことができます。 item->VideoTracks->GetAt(0)
は Windows.Media.Core.VideoTrack を返すため、Windows.Media.Core
名前空間についても触れました。 ただし、VideoTrack という名前を入力する必要がなかったため、using Windows.Media.Core
ディレクティブは必要ありませんでした。
しかし、C++/WinRT では、名前を付けない場合でも、使用する各名前空間に対応するヘッダー ファイルをインクルードする必要があります。
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!
using namespace winrt;
using namespace Windows::Media::Playback;
winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
return item.VideoTracks().GetAt(0).Name();
}
一方、MediaPlaybackItem.AudioTracksChanged イベントの型が TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs> であっても、そのイベントは使用しなかったため、winrt/Windows.Foundation.Collections.h
をインクルードする必要はありませんでした。
また、C++/WinRT では、XAML マークアップによって使用される名前空間のヘッダー ファイルをインクルードする必要もあります。
<!-- MainPage.xaml -->
<Rectangle Height="400"/>
Rectangle クラスを使用することは、このインクルードを追加する必要があることを意味します。
// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>
ヘッダー ファイルを忘れた場合は、すべての操作が正常にコンパイルされますが、consume_
クラスが見つからないためにリンカー エラーが発生します。
パラメーターの引き渡し
C++/CX ソース コードを記述するときに、ハット (^) が参照する C++/CX 型を関数パラメーターとして渡します。
void LogPresenceRecord(PresenceRecord^ record);
C++/WinRT では、同期関数に既定で const&
パラメーターを使用する必要があります。 これにより、コピーとインター ロック オーバーヘッドが回避されます。 ただし、コルーチンが値によるキャプチャを行い有効期間の問題を回避するように、値渡しを使用する必要があります (詳細については、「C++/WinRT を使用した同時実行と非同期操作」を参照してください)。
void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);
C++/WinRT オブジェクトは、基本的に Windows ランタイムのバッキング オブジェクトへのインターフェイス ポインターを保持する値です。 C++/WinRT オブジェクトをコピーすると、コンパイラはカプセル化されたインターフェイス ポインターをコピーし、その参照カウントをインクリメントします。 コピーの最終的なデストラクションには、参照カウントのデクリメントも含まれます。 そのため、必要な場合にのみ、コピーのオーバーヘッドが発生します。
変数とフィールドの参照
C++/CX ソース コードを記述するときに、ハット (^) 変数を使用して Windows ランタイム オブジェクトを参照し、矢印 (->) 演算子を使用してハット変数を逆参照します。
IVectorView<User^>^ userList = User::Users;
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
...
同等の C++/WinRT コードに移植する場合は、ハットを取り除き、矢印演算子 (->) をドット演算子 (.) に変更することにより、開始できるようになります。 C++/WinRT のプロジェクションが実行された型は値であり、ポインターではありません。
IVectorView<User> userList = User::Users();
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
...
C++/CX ハット参照用の既定のコンストラクターにより、それを null に初期化します。 次に示したのは、正しい型の変数/フィールドを作成するための C++/CX コード例でが、初期化は行われません。 つまり、それによって最初は TextBlock は参照されません。後で参照を割り当てるつもりです。
TextBlock^ textBlock;
class MyClass
{
TextBlock^ textBlock;
};
C++/WinRT での同等のものについては、「初期化の遅延」を参照してください。
Properties
C++/CX 言語拡張機能には、プロパティの概念が含まれています。 C++/CX ソース コードを記述する際、プロパティをフィールドとして扱ってそのプロパティにアクセスできます。 標準 C++ にはプロパティの概念がないため、C++/WinRT では、Get 関数と Set 関数を呼び出します。
次の例では、XboxUserId、UserState、PresenceDeviceRecords、Size はすべてプロパティです。
プロパティからの値の取得
C++/CX でプロパティの値を取得する方法は次のとおりです。
void Sample::LogPresenceRecord(PresenceRecord^ record)
{
auto id = record->XboxUserId;
auto state = record->UserState;
auto size = record->PresenceDeviceRecords->Size;
}
同等の C++/WinRT ソース コードは、プロパティと同じ名前を持ち、パラメーターのない関数を呼び出します。
void Sample::LogPresenceRecord(PresenceRecord const& record)
{
auto id = record.XboxUserId();
auto state = record.UserState();
auto size = record.PresenceDeviceRecords().Size();
}
PresenceDeviceRecords 関数は、それ自体が Size 関数を持つ Windows ランタイム オブジェクトを返します。 返されたオブジェクトは C++/WinRT の投影された型でもあるため、ドット演算子を使用して逆参照し、Size を呼び出します。
新しい値へのプロパティの設定
新しい値へのプロパティの設定は、同様のパターンに従います。 まず、C++/CX で次の操作を行います。
record->UserState = newValue;
C++/WinRT で対応する操作を行うには、プロパティと同じ名前の関数を呼び出し、引数を渡します。
record.UserState(newValue);
クラスのインスタンスの作成
C++/CX オブジェクトは、それに対するハンドル (通常はハット (^) 参照と呼ばれます) を介して操作します。 ref new
キーワードにより新しいオブジェクトを作成します。これにより、RoActivateInstance が呼び出され、ランタイム クラスの新しいインスタンスがアクティブ化されます。
using namespace Windows::Storage::Streams;
class Sample
{
private:
Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};
C++/WinRT オブジェクトは値であるため、スタックに割り当てたり、オブジェクトのフィールドとして割り当てることができます。 C++/WinRT オブジェクトを割り当てるために、ref new
(または new
) は使用しません。 バックグラウンドで RoActivateInstance は引き続き呼び出されています。
using namespace winrt::Windows::Storage::Streams;
struct Sample
{
private:
Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};
リソースを初期化するコストが高い場合は、実際に必要になるまで初期化を遅延するのが一般的です。 既に説明したように、C++/CX ハット参照用の既定のコンストラクターにより、それを null に初期化します。
using namespace Windows::Storage::Streams;
class Sample
{
public:
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer^ m_gamerPicBuffer;
};
同じコードが C++/WinRT に移植されました。 std::nullptr_t コンストラクターの使用に注意してください。 コンストラクターの詳細については、「初期化の遅延」を参照してください。
using namespace winrt::Windows::Storage::Streams;
struct Sample
{
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer m_gamerPicBuffer{ nullptr };
};
既定のコンストラクターによるコレクションへの影響
C++ コレクション型では既定のコンストラクターが使用されるため、意図しないオブジェクトの構築が発生する可能性があります。
シナリオ | C++/CX | C++/WinRT (正しくない) | C++/WinRT (正しい) |
---|---|---|---|
ローカル変数 (最初は空) | TextBox^ textBox; |
TextBox textBox; // Creates a TextBox! |
TextBox textBox{ nullptr }; |
メンバー変数 (最初は空) | class C { TextBox^ textBox; }; |
class C { TextBox textBox; // Creates a TextBox! }; |
class C { TextBox textbox{ nullptr }; }; |
グローバル変数 (最初は空) | TextBox^ g_textBox; |
TextBox g_textBox; // Creates a TextBox! |
TextBox g_textBox{ nullptr }; |
空の参照のベクトル | std::vector<TextBox^> boxes(10); |
// Creates 10 TextBox objects! std::vector<TextBox> boxes(10); |
std::vector<TextBox> boxes(10, nullptr); |
マップに値を設定する | std::map<int, TextBox^> boxes; boxes[2] = value; |
std::map<int, TextBox> boxes; // Creates a TextBox at 2, // then overwrites it! boxes[2] = value; |
std::map<int, TextBox> boxes; boxes.insert_or_assign(2, value); |
空の参照の配列 | TextBox^ boxes[2]; |
// Creates 2 TextBox objects! TextBox boxes[2]; |
TextBox boxes[2] = { nullptr, nullptr }; |
ペア | std::pair<TextBox^, String^> p; |
// Creates a TextBox! std::pair<TextBox, String> p; |
std::pair<TextBox, String> p{ nullptr, nullptr }; |
空の参照のコレクションの詳細
C++/CX (実際には隣接したコンテナー) 内に Platform::Array^ (「Platform::Array^ を移植する」を参照) がある場合はいつでも、それを配列のままにしておくのではなく、C++/WinRT の std::vector に移植するかどうかを選択できます。 std::vector を選択することには利点があります。
たとえば、空の参照の固定サイズ ベクトルを作成するための簡単な方法はありますが (上記の表を参照)、空の参照の "配列" を作成するための簡単な方法はありません。 配列内の要素ごとに、nullptr
を繰り返す必要があります。 値が少なすぎる場合は、追加分が既定で構築されます。
ベクトルの場合は、初期化時に空の参照を入力できます (上の表を参照)。または、初期化後にこのようなコードを使用して空の参照を入力することもできます。
std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.
std::map の例に関する詳細
std::map の []
添字演算子は、次のように動作します。
- キーがマップ内にある場合は、既存の値への参照を返します (これは上書きできます)。
- キーがマップ内にない場合は、キー (移動できる場合は移動済み) と既定で構築された値で構成される新しいエントリをマップ内に作成し、値への参照を返します (これは後で上書きできます)。
つまり、[]
演算子は常にマップ内にエントリを作成します。 これは C#、Java、および JavaScript とは異なります。
基本ランタイム クラスから派生クラスへの変換
派生型のオブジェクトを参照する既知の reference-to-base を使用することは一般的です。 C++/CX では、dynamic_cast
を使用して、reference-to-base を reference-to-derived に "キャスト" します。 dynamic_cast
は、実際には QueryInterface の隠された呼び出しです。 次に示すのは典型的な例です。依存関係プロパティの変更イベントを処理しており、DependencyObject から、依存関係プロパティを所有する実際の型にキャストし直す必要があります。
void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };
if (theControl != nullptr)
{
// succeeded ...
}
}
同等の C++/WinRT コードでは、dynamic_cast
が IUnknown :: try_as 関数 (QueryInterface をカプセル化する) の呼び出しによって置き換えられます。 代わりに IUnknown :: as を呼び出すオプションもあります。このオプションでは、必須のインターフェイス (要求している型の既定のインターフェイス) に対するクエリが返されない場合に例外がスローされます。 次に C++/WinRT コード例を示します。
void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
{
// succeeded ...
}
try
{
BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
// succeeded ...
}
catch (winrt::hresult_no_interface const&)
{
// failed ...
}
}
派生クラス
ランタイム クラスから派生するためには、基底クラスが構成可能である必要があります。 C++/CX ではクラスを構成可能にするために特別な手順を実行する必要はありませんが、C++/WinRT では必要です。 クラスを基底クラスとして使用できるようにすることを示すには、シールされていないキーワードを使用します。
unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
...
}
runtimeclass DerivedPage : BasePage
{
...
}
実装ヘッダー クラスでは、派生クラスの自動生成されたヘッダーをインクルードする前に、基底クラスのヘッダー ファイルをインクルードする必要があります。 そうしないと、"この型は式として不適切に使用されています" などのエラーが表示されます。
// DerivedPage.h
#include "BasePage.h" // This comes first.
#include "DerivedPage.g.h" // Otherwise this header file will produce an error.
namespace winrt::MyNamespace::implementation
{
struct DerivedPage : DerivedPageT<DerivedPage>
{
...
}
}
デリゲートを使用したイベント処理
この場合はデリゲートとしてラムダ関数を使用して、C++/CX でイベントを処理する典型的な例を次に示します。
auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
// Handle the event.
// Note: locals are captured by value, not reference, since this handler is delayed.
});
C++/WinRT で対応するコードは次のとおりです。
auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
// Handle the event.
// Note: locals are captured by value, not reference, since this handler is delayed.
});
ラムダ関数の代わりに、デリゲートを自由関数として実装するか、またはメンバー関数へのポインターとして実装するかを選択できます。 詳細については、「C++/WinRT でのデリゲートを使用したイベントの処理」を参照してください。
バイナリ間ではなく、イベントとデリゲートが内部的に使用される C++/CX コードベースから移植する場合は、winrt::delegate を使用して、C++/WinRT でそのパターンを複製できます。 「パラメーター化されたデリゲート、単純なシグナル、およびプロジェクト内でのコールバック」も参照してください。
デリゲートの取り消し
C++/CX では、-=
演算子を使用して前のイベント登録を取り消します。
myButton->Click -= token;
C++/WinRT で対応するコードは次のとおりです。
myButton().Click(token);
詳細とオプションについては、「登録済みデリゲートの取り消し」を参照してください。
ボックス化とボックス化解除
C++/CX では、スカラーが自動的にオブジェクト内にボックス化されます。 C++/Winrt では、winrt::box_value 関数を明示的に呼び出す必要があります。 どちらの言語でも、明示的にボックス化解除する必要があります。 C++/WinRT を使用したボックス化とボックス化解除に関するページを参照してください。
次の表では、これらの定義を使用します。
C++/CX | C++/WinRT |
---|---|
int i; |
int i; |
String^ s; |
winrt::hstring s; |
Object^ o; |
IInspectable o; |
操作 | C++/CX | C++/WinRT |
---|---|---|
ボックス化 | o = 1; o = "string"; |
o = box_value(1); o = box_value(L"string"); |
ボックス化解除 | i = (int)o; s = (String^)o; |
i = unbox_value<int>(o); s = unbox_value<winrt::hstring>(o); |
C++/CX と C# では、Null ポインターを値型にボックス化解除しようとすると例外が発生します。 C++/WinRT はこれをプログラミング エラーと見なし、クラッシュします。 C++/Winrt では、オブジェクトが想定した型ではないケースに対処したい場合は winrt:: unbox_value_or 関数を使用します。
シナリオ | C++/CX | C++/WinRT |
---|---|---|
既知の整数をボックス化解除する | i = (int)o; |
i = unbox_value<int>(o); |
o が null の場合 | Platform::NullReferenceException |
クラッシュ |
o がボックス化された int でない場合 | Platform::InvalidCastException |
クラッシュ |
int をボックス化解除し、null の場合はフォールバックを使用する。それ以外の場合はクラッシュ | i = o ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
可能であれば int をボックス化解除する。それ以外の場合はフォールバックを使用する | auto box = dynamic_cast<IBox<int>^>(o); i = box ? box->Value : fallback; |
i = unbox_value_or<int>(o, fallback); |
文字列のボックス化とボックス化解除
文字列は、ある点では値型であり、別の点では参照型です。 C++/CX と C++/WinRT では文字列の扱いが異なります。
ABI 型 HSTRING は、参照カウント文字列へのポインターです。 しかし、IInspectable から派生したものではないため、厳密に言えばオブジェクトではありません。 さらに、null の HSTRING は空の文字列を表します。 IInspectable から派生したもの以外のボックス化は、IReference <T> 内にラップすることによって行われ、Windows ランタイムでは標準の実装が PropertyValue オブジェクトの形式で行われます (カスタム型は PropertyType::Othertype として報告されます)。
C++/CX は Windows ランタイム文字列を参照型として表しますが、C++/WinRT は文字列を値型として投影します。 つまり、ボックス化された null 文字列は、どのように表されるかによって異なる表現を持つことができます。
さらに、C++/CS を使用すると、null String^ を逆参照できます。この場合は、文字列 ""
のように動作します。
動作 | C++/CX | C++/WinRT |
---|---|---|
宣言 | Object^ o; String^ s; |
IInspectable o; hstring s; |
文字列型のカテゴリ | 参照の種類 | 値タイプ |
null HSTRING による投影 | (String^)nullptr |
hstring{} |
null と "" が同一かどうか |
はい | はい |
null の有効性 | s = nullptr; s->Length == 0 (有効) |
s = hstring{}; s.size() == 0 (有効) |
オブジェクトに null 文字列を割り当てる場合 | o = (String^)nullptr; o == nullptr |
o = box_value(hstring{}); o != nullptr |
オブジェクトに "" を割り当てる場合 |
o = ""; o == nullptr |
o = box_value(hstring{L""}); o != nullptr |
基本的なボックス化とボックス化解除。
操作 | C++/CX | C++/WinRT |
---|---|---|
文字列をボックス化する | o = s; 空の文字列は nullptr になります。 |
o = box_value(s); 空の文字列は非 null オブジェクトになります。 |
既知の文字列をボックス化解除する | s = (String^)o; null オブジェクトは空の文字列になります。 文字列でない場合は InvalidCastException。 |
s = unbox_value<hstring>(o); null オブジェクトはクラッシュします。 文字列でない場合はクラッシュします。 |
使用可能な文字列をボックス化解除する | s = dynamic_cast<String^>(o); null オブジェクトまたは文字列以外は空の文字列になります。 |
s = unbox_value_or<hstring>(o, fallback); null または文字列以外はフォールバックになります。 空の文字列は保持されます。 |
同時開催操作と非同期操作
並列パターン ライブラリ (PPL) (concurrency::task など) は、C++/CS ハット参照をサポートするように更新されました。
C++/WinRT の場合は、代わりにコルーチンと co_await
を使用する必要があります。 詳細とコード例については、「C++/WinRT を使用した同時開催操作と非同期操作」を参照してください。
XAML マークアップからのオブジェクトの使用
C++/CX プロジェクトでは、XAML マークアップからプライベート メンバーと名前付き要素を使用できます。 ただし、C++/WinRT では、XAML {x:Bind} マークアップ拡張の使用によって使用されるすべてのエンティティが、IDL で公開されている必要があります。
また、ブール値へのバインドにより C++/CX では true
または false
が表示されますが、C++/WinRT では Windows.Foundation.IReference`1<Boolean> が表示されます。
詳細とコード例については、マークアップからのオブジェクトの使用に関するページを参照してください。
C++/WinRT 型への C++/CX Platform 型のマッピング
C++/CX は Platform 名前空間でいくつかのデータ型を提供します。 これらの型は標準 C++ ではないため、Windows ランタイム言語拡張機能を有効にしたとき (Visual Studio プロジェクトのプロパティの [C/C++]>[全般]>[Windows ランタイム拡張機能の使用]>[はい (/ZW)]) にのみ使用できます。 下の表は、Platform 型を C++/WinRT の同等の型に移植するときに役立ちます。 C++/WinRT は標準 C++ であるため、完了後に /ZW
オプションをオフにできます。
C++/CX | C++/WinRT |
---|---|
Platform::Agile^ | winrt::agile_ref |
Platform::Array^ | 「Platform::Agile^ を移植する」を参照してください |
Platform::Exception^ | winrt::hresult_error |
Platform::InvalidArgumentException^ | winrt::hresult_invalid_argument |
Platform::Object^ | winrt::Windows::Foundation::IInspectable |
Platform::String^ | winrt::hstring |
Platform::Agile^ を winrt::agile_ref に移植する
C++/CX の Platform::Agile^ 型は、任意のスレッドからアクセスできる Windows ランタイム クラスとなります。 C++/WinRT の同等の型は winrt::agile_ref です。
C++/CX で、次の操作を行います。
Platform::Agile<Windows::UI::Core::CoreWindow> m_window;
C++/WinRT で、次の操作を行います。
winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;
Platform::Array^ を移植する
C++/CX で配列を使用する必要がある場合、C++/WinRT では任意の隣接したコンテナーを使用できます。 std::vector の使用が適切である理由については、「既定のコンストラクターによるコレクションへの影響」を参照してください。
このため、C++/CX の Platform::Array^ がある場合は常に、移植オプションに、初期化子リスト、std::array、または std::vector の使用を含めます。 詳細およびコード例については、「標準的な初期化子リスト」および「標準的な配列とベクトル」を参照してください。
Platform::Exception^ の winrt::hresult_error への移植
Windows ランタイム API が S_OK HRESULT 以外を返すときに、Platform::Exception^ 型が C++/CX で生成されます。 C++/WinRT の同等の型は winrt::hresult_error です。
C++/WinRT に移植するには、Platform::Exception^ を使用するすべてのコードを winrt::hresult_error を使用するように変更します。
C++/CX で、次の操作を行います。
catch (Platform::Exception^ ex)
C++/WinRT で、次の操作を行います。
catch (winrt::hresult_error const& ex)
C++/WinRT には、これらの例外クラスが用意されています。
例外の種類 | 基底クラス | HRESULT |
---|---|---|
winrt::hresult_error | hresult_error::to_abi の呼び出し | |
winrt::hresult_access_denied | winrt::hresult_error | E_ACCESSDENIED |
winrt::hresult_canceled | winrt::hresult_error | ERROR_CANCELLED |
winrt::hresult_changed_state | winrt::hresult_error | E_CHANGED_STATE |
winrt::hresult_class_not_available | winrt::hresult_error | CLASS_E_CLASSNOTAVAILABLE |
winrt::hresult_illegal_delegate_assignment | winrt::hresult_error | E_ILLEGAL_DELEGATE_ASSIGNMENT |
winrt::hresult_illegal_method_call | winrt::hresult_error | E_ILLEGAL_METHOD_CALL |
winrt::hresult_illegal_state_change | winrt::hresult_error | E_ILLEGAL_STATE_CHANGE |
winrt::hresult_invalid_argument | winrt::hresult_error | E_INVALIDARG |
winrt::hresult_no_interface | winrt::hresult_error | E_NOINTERFACE |
winrt::hresult_not_implemented | winrt::hresult_error | E_NOTIMPL |
winrt::hresult_out_of_bounds | winrt::hresult_error | E_BOUNDS |
winrt::hresult_wrong_thread | winrt::hresult_error | RPC_E_WRONG_THREAD |
各クラスが、(hresult_error 基本クラスを介して) エラーの HRESULT を返す to_abi 関数を提供し、その HRESULT の文字列表現を返す message 関数を提供します。
C++/CX で例外をスローする例を次に示します。
throw ref new Platform::InvalidArgumentException(L"A valid User is required");
また、C++/WinRT での同等のコードは次のとおりです。
throw winrt::hresult_invalid_argument{ L"A valid User is required" };
Platform::Object^ の winrt::Windows::Foundation::IInspectable への移植
すべての C++/WinRT 型と同様に、winrt::Windows::Foundation::IInspectable は値の型です。 その型の変数を null に初期化する方法は次のとおりです。
winrt::Windows::Foundation::IInspectable var{ nullptr };
Platform::String^ の winrt::hstring への移植
Platform::String^ は Windows ランタイム HSTRING ABI 型と同等です。 C++/WinRT では、同等の型は winrt::hstring です。 ただし、C++/WinRT では、std::wstring などの C++ 標準ライブラリのワイド文字列型、およびワイド文字列リテラルを使用して Windows ランタイム API を呼び出すことができます。 詳細とコード例については、「C++/WinRT での文字列の処理」を参照してください。
C++/CX では、Platform::String::Data プロパティにアクセスして、C スタイルの const wchar_t* 配列 (たとえば、それを std::wcout に渡すために) として文字列を取得できます。
auto var{ titleRecord->TitleName->Data() };
C++/WinRT で同じ操作を行うには、hstring::c_str 関数を使用して null で終了する C スタイルの文字列バージョンを取得します。これは std::wstring から取得する場合と同様です。
auto var{ titleRecord.TitleName().c_str() };
文字列を取るか、文字列を返す API の実装に関しては、通常、Platform::String^ を使用する C++/CX コードを変更して、代わりに winrt::hstring を使用します。
文字列を取る C++/CX API の例を次に示します。
void LogWrapLine(Platform::String^ str);
C++/WinRT では、次のようにその API を MIDL 3.0 で宣言できます。
// LogType.idl
void LogWrapLine(String str);
次に、C++/WinRT ツール チェーンにより次のようなソース コードが生成されます。
void LogWrapLine(winrt::hstring const& str);
ToString()
C++/CX 型には Object::ToString メソッドが用意されています。
int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".
C++/WinRT ではこの機能は直接提供されていませんが、代替機能があります。
int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".
また、C++/WinRT では、限られた数の型のための winrt:: to_hstring もサポートしています。 stringify が必要なその他の型のオーバーロードを追加する必要があります。
言語 | Stringify int | Stringify enum |
---|---|---|
C++/CX | String^ result = "hello, " + intValue.ToString(); |
String^ result = "status: " + status.ToString(); |
C++/WinRT | hstring result = L"hello, " + to_hstring(intValue); |
// must define overload (see below) hstring result = L"status: " + to_hstring(status); |
列挙型の stringify の場合は、winrt::to_hstring の実装を指定する必要があります。
namespace winrt
{
hstring to_hstring(StatusEnum status)
{
switch (status)
{
case StatusEnum::Success: return L"Success";
case StatusEnum::AccessDenied: return L"AccessDenied";
case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
default: return to_hstring(static_cast<int>(status));
}
}
}
このような文字列化は、多くの場合、データ バインディングによって暗黙的に使用されます。
<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>
これらのバインディングは、バインドされたプロパティの winrt::to_hstring を実行します。 2 番目の例 (statusenum) の場合は、winrt::to_hstring の独自のオーバーロードを指定する必要があります。そうしないと、コンパイラ エラーが発生します。
文字列の作成
C++/CX と C++/WinRT は、文字列の作成には標準 std::wstringstream に従います。
操作 | C++/CX | C++/WinRT |
---|---|---|
文字列を追加し、null 値を保持する | stream.print(s->Data(), s->Length); |
stream << std::wstring_view{ s }; |
文字列を追加し、最初の null で停止する | stream << s->Data(); |
stream << s.c_str(); |
結果を抽出する | ws = stream.str(); |
ws = stream.str(); |
その他の例
次の例では ws は std::wstring 型の変数です。 また、C++/CX では 8 ビット文字列から Platform::String を構築できますが、C++/WinRT ではこれは実行されません。
操作 | C++/CX | C++/WinRT |
---|---|---|
リテラルから文字列を構築する | String^ s = "hello"; String^ s = L"hello"; |
// winrt::hstring s{ "hello" }; // Doesn't compile winrt::hstring s{ L"hello" }; |
std::wstring から変換し、null 値を保持する | String^ s = ref new String(ws.c_str(), (uint32_t)ws.size()); |
winrt::hstring s{ ws }; s = winrt::hstring(ws); // s = ws; // Doesn't compile |
std::wstring から変換し、最初の null で停止する | String^ s = ref new String(ws.c_str()); |
winrt::hstring s{ ws.c_str() }; s = winrt::hstring(ws.c_str()); // s = ws.c_str(); // Doesn't compile |
std::wstring に変換し、null 値を保持する | std::wstring ws{ s->Data(), s->Length }; ws = std::wstring(s>Data(), s->Length); |
std::wstring ws{ s }; ws = s; |
std::wstring に変換し、最初の null で停止する | std::wstring ws{ s->Data() }; ws = s->Data(); |
std::wstring ws{ s.c_str() }; ws = s.c_str(); |
メソッドにリテラルを渡す | Method("hello"); Method(L"hello"); |
// Method("hello"); // Doesn't compile Method(L"hello"); |
std::wstring をメソッドに渡す | Method(ref new String(ws.c_str(), (uint32_t)ws.size()); // Stops on first null |
Method(ws); // param::winrt::hstring accepts std::wstring_view |