C# から C++/WinRT への移行

ヒント

前にこのトピックを読んだことがあり、特定のタスクを念頭に置いて読み返している場合は、このトピックの「実行しているタスクに基づいてコンテンツを検索する」に進んでください。

このトピックでは、C# プロジェクト内のソース コードを C++/WinRT の同等のコードに移植することに関する技術的な詳細を総合的に分類します。

ユニバーサル Windows プラットフォーム (UWP) アプリ サンプルの移植についてのケース スタディに関しては、C# から C++/WinRT への Clipboard サンプルの移植についての関連トピックを参照してください。 チュートリアルの手順を実行し、サンプルを自分で移植することにより、移植を練習し経験を得ることができます。

事前の準備と、これから行うこと

C# から C++/WinRT への Clipboard サンプルの移植に関するケース スタディでは、プロジェクトを C++/WinRT に移植する際に行うソフトウェア設計に関する様々な決定の例が示されています。 それで、既存のコードの動作をはっきりと理解しておくことは、移植の良い準備となります。 そうすれば、アプリの機能やコードの構造に関して概要を理解できるため、行う決定によって正しい方向に進むことができます。

行うべき移植に関する変更の種類については、4 つのカテゴリに分類できます。

  • 言語プロジェクションでの移植。 Windows ランタイム (WinRT) では様々なプログラミング言語への "プロジェクション" が行われます。 これらの言語プロジェクションのそれぞれは、対応するプログラミング言語にとって自然なものになるように設計されています。 C# の場合、一部の Windows ランタイムの型は .NET の型としてプロジェクションが行われます。 それで、たとえば System.Collections.Generic.IReadOnlyList<T>Windows.Foundation.Collections.IVectorView<T> に変換することになります。 また C# では、一部の Windows ランタイムの操作は便利な C# の言語機能としてプロジェクションが行われます。 一例として、C# では、+= の演算子構文を使用して、イベント処理デリゲートの登録を行います。 それで、そのような言語の機能は、実行されている基本的な操作 (この例ではイベント登録) に変換することになります。
  • 言語構文の移植。 これらの変更の多くは単純な機械的な変換で、あるシンボルを他のシンボルに置き換えます。 たとえば、ドット (.) を 2 重コロン (::) に変更します。
  • 言語プロシージャの移植。 これらのうちの一部は単純で、変更の繰り返しです (myObject.MyProperty から myObject.MyProperty() など)。 大きな変更が必要なものもあります (System.Text.StringBuilder を使用するプロシージャを、std::wostringstream を使用するものに移植する、など)。
  • C++/WinRT に特有の移植関連のタスク Windows ランタイムの特定の詳細は、背後で C# によって暗黙的に処理されます。 これらの詳細は、C++/WinRT で明示的に処理されます。 一例として、.idl ファイルを使用してランタイム クラスを定義することがあります。

次に示すタスクに基づく索引の後、このトピックの残りのセクションは、上記の分類に従って構成されています。

実行しているタスクに基づいてコンテンツを検索する

タスク コンテンツ
Windows ランタイム コンポーネント (WRC) を作成する 一部の機能は、C++ を使用することによって (または、特定の API 呼び出しによって) のみ実現できます。 その機能を C++/WinRT WRC に組み込み、(たとえば) C# アプリから WRC を使用することができます。 「C++/WinRT を使用した Windows ランタイム コンポーネント」および「Windows ランタイム コンポーネントでランタイム クラスを作成する場合」を参照してください。
非同期メソッドを移植する C++/WinRT ランタイム クラスの非同期メソッドの最初の行を auto lifetime = get_strong(); にするのがよい方法です (「class-member コルーチンで this ポインターに安全にアクセスする」を参照してください)。

Task からの移植については、「非同期アクション」を参照してください。
Task<T> からの移植については、「非同期操作」を参照してください。
async void からの移植については、「fire-and-forget メソッド」を参照してください。
クラスを移植する まず、クラスをランタイム クラスにする必要があるか、または通常のクラスでかまわないかを判断します。 それを判断するときの参考として、「C++/WinRT での API の作成」の冒頭を参照してください。 その後で、その下の 3 つの行を参照してください。
ランタイム クラスを移植する C++ アプリの外部の機能を共有するクラス、または XAML データ バインディングで使用されるクラス。 「Windows ランタイム コンポーネントでランタイム クラスを作成する場合」または「XAML UI で参照されるランタイム クラスを作成する場合」を参照してください。

それらのリンクではこれについて詳しく説明されていますが、ランタイム クラスは IDL で宣言する必要があります。 プロジェクトに IDL ファイル (Project.idl など) が既に含まれている場合は、そのファイルで新しいランタイム クラスを宣言することをお勧めします。 IDL において、アプリの外部または XAML で使用されるメソッドとデータ メンバーを宣言します。 IDL ファイルを更新した後、リビルドを行い、プロジェクトの Generated Files フォルダーで生成されたスタブ ファイル (.h.cpp) を確認します (ソリューション エクスプローラーでプロジェクトのノードを選択し、[すべてのファイルを表示] をオンにします)。 スタブ ファイルをプロジェクト内の既存のファイルと比較し、必要に応じて、ファイルを追加するか、関数のシグネチャを追加または更新します。 スタブ ファイルの構文は常に正しいので、ビルド エラーを最小限に抑えるためにそれを使用することをお勧めします。 プロジェクト内のスタブがスタブ ファイル内のそれと一致したら、C# コードを移植して実装できます。
通常のクラスを移植する ランタイム クラスを作成して "いない" 場合」を参照してください。
IDL を作成する Microsoft インターフェイス定義言語 3.0 の概要
XAML UI で参照されるランタイム クラスを作成する場合
XAML マークアップからのオブジェクトの使用
IDL でランタイム クラスを定義する
コレクションを移植する C++/WinRT でのコレクション
データ ソースを XAML マークアップで使用できるようにする
連想コンテナー
ベクター メンバーのアクセス
イベントを移植する クラス メンバーとしてのイベント ハンドラー デリゲート
イベント ハンドラー デリゲートの取り消し
メソッドを移植する C# から: private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
C++/WinRT の .h ファイルへ: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
C++/WinRT の .cpp ファイルへ: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
文字列を移植する C++/WinRT での文字列の処理
ToString
文字列の作成
文字列のボックス化とボックス化解除
型変換 (型キャスト) C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
ToString」も参照してください。

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
ボックス化解除が失敗した場合にスローします。 ボックス化とボックス化解除に関するページも参照してください。

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
ボックス化解除が失敗した場合は、フォールバックを返します。 ボックス化とボックス化解除に関するページも参照してください。

C#: (Class)o
C++/WinRT: o.as<Class>()
変換が失敗した場合にスローします。

C#: o as Class
C++/WinRT: o.try_as<Class>()
変換が失敗した場合は、null を返します。

言語プロジェクションに関連する変更

カテゴリ C# C++/WinRT 関連項目
型指定されていないオブジェクト object、または System.Object Windows::Foundation::IInspectable EnableClipboardContentChangedNotifications メソッドの移植
プロジェクション名前空間 using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
コレクションのサイズ collection.Count collection.Size() BuildClipboardFormatsOutputString メソッドの移植
一般的なコレクション型 IList<T>、および Add で要素を追加。 IVector<T>、および Append で要素を追加。 いずれかの場所で std::vector を使用する場合は、push_back で要素を追加。
読み取り専用のコレクション型 IReadOnlyList<T> IVectorView<T> BuildClipboardFormatsOutputString メソッドの移植
クラス メンバーとしてのイベント ハンドラー デリゲート myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); EnableClipboardContentChangedNotifications メソッドの移植
イベント ハンドラー デリゲートの取り消し myObject.EventName -= Handler; myObject.EventName(token); EnableClipboardContentChangedNotifications メソッドの移植
連想コンテナー IDictionary<K, V> IMap<K, V>
ベクター メンバーのアクセス x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

イベント ハンドラーの登録または取り消し

C++/WinRT では、「C++/WinRT でのデリゲートを使用したイベントの処理」で説明されているように、イベント ハンドラー デリゲートの登録または取り消しを行うための構文オプションがいくつかあります。 EnableClipboardContentChangedNotifications メソッドの移植に関する記事も参照してください。

イベントの受信側 (イベントを処理するオブジェクト) が破棄されようとしているときなど、場合によってはイベント ハンドラーを取り消して、イベント ソース (イベントを発生させるオブジェクト) が破棄されたオブジェクトを呼び出さないようにしたいことがあります。 「登録済みデリゲートの取り消し」を参照してください。 そのような場合は、イベント ハンドラーに event_token メンバー変数を作成します。 例として、EnableClipboardContentChangedNotifications メソッドの移植に関する記事を参照してください。

また、XAML マークアップでイベント ハンドラーを登録することもできます。

<Button x:Name="OpenButton" Click="OpenButton_Click" />

C# では、OpenButton_Click メソッドはプライベートにすることができ、XAML は引き続きこれを、OpenButton によって発生した ButtonBase.Click イベントに接続できます。

C++/WinRT では、"XAML マークアップで登録したい場合"、OpenButton_Click メソッドは実装型でパブリックである必要があります。 命令型コードでのみイベント ハンドラーを登録したい場合、イベント ハンドラーはパブリックである必要はありません。

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

または、登録する XAML ページを実装型の friend にし、OpenButton_Click をプライベートにすることもできます。

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

最後のシナリオでは、移植する C# プロジェクトをマークアップからのイベント ハンドラーにバインドします (そのシナリオの背景の詳細については、「x:Bind の 関数」を参照してください)。

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

このマークアップをより単純な Click="OpenButton_Click" に変更することもできます。 または、必要であれば、そのマークアップをそのままにしておくこともできます。 これをサポートするために必要なのは、IDL でイベント ハンドラーを宣言することだけです。

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

注意

関数をファイア アンド フォーゲットとして "実装" する場合でも、これを void として宣言します。

言語構文に関連する変更

カテゴリ C# C++/WinRT 関連項目
アクセス修飾子 public \<member\> public:
    \<member\>
Button_Click メソッドの移植
データ メンバーへのアクセス this.variable this->variable  
非同期アクション async Task ... IAsyncAction ... IAsyncAction インターフェイスC++/WinRT を使用した同時実行操作と非同期操作
非同期操作 async Task<T> ... IAsyncOperation<T> ... IAsyncOperation インターフェイスC++/WinRT を使用した同時実行操作と非同期操作
fire-and-forget メソッド (つまり、非同期) async void ... winrt::fire_and_forget ... CopyButton_Click メソッドの移植ファイア アンド フォーゲット
列挙型定数へのアクセス E.Value E::Value DisplayChangedFormats メソッドの移植
協調的な待機 await ... co_await ... CopyButton_Click メソッドの移植
プライベート フィールドとしての投影型のコレクション private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
GUID の構築 private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
名前空間の区切り記号 A.B.T A::B::T
[Null] null nullptr UpdateStatus メソッドの移植
型オブジェクトの取得 typeof(MyType) winrt::xaml_typename<MyType>() Scenarios プロパティの移植
メソッドのパラメーター宣言 MyType MyType const& パラメーターの引き渡し
非同期メソッドのパラメーター宣言 MyType MyType パラメーターの引き渡し
静的メソッドの呼び出し T.Method() T::Method()
文字列 string または System.String winrt::hstring C++/WinRT での文字列の処理
文字列リテラル "a string literal" L"a string literal" コンストラクター、CurrentFEATURE_NAME の移植
推論された (または推測された) 型 var auto BuildClipboardFormatsOutputString メソッドの移植
using ディレクティブ using A.B.C; using namespace A::B::C; コンストラクター、CurrentFEATURE_NAME の移植
逐語的/未加工の文字列リテラル @"verbatim string literal" LR"(raw string literal)" DisplayToast メソッドの移植

注意

ヘッダー ファイルに特定の名前空間の using namespace ディレクティブが含まれていない場合、その名前空間のすべての型名を完全修飾する必要があります。または、コンパイラーがそれらを見つけられるように十分に修飾する必要があります。 例として、DisplayToast メソッドの移植に関する記事を参照してください。

クラスとメンバーの移植

C# の各型に関して、Windows ランタイムの型に移植するか、通常の C++ のクラス、構造体、列挙型に移植するかを決める必要があります。 詳細について、およびこれらの決定方法についての詳細な例については、C# から C++/WinRT への Clipboard サンプルの移植に関する記事を参照してください。

C# プロパティは一般的に、アクセサー関数、ミューテーター関数、バッキング データ メンバーになります。 詳細や例については、IsClipboardContentChangedEnabled プロパティの移植に関する記事を参照してください。

非静的フィールドについては、実装型のデータ メンバーにしてください。

C# の静的フィールドは、C++/WinRT の静的アクセサーおよびミューテーター関数になります。 詳細や例については、コンストラクタ、CurrentFEATURE_NAME の移植に関する記事を参照してください。

メンバー関数については、再度、それぞれが IDL に属しているかどうか、または実装型のパブリックもしくはプライベートのメンバー関数であるかどうかを決める必要があります。 決定方法に関する詳細と例については、「MainPage 型の IDL」を参照してください。

XAML マークアップ、資産ファイルの移植

C# から C++/WinRT への Clipboard サンプルの移植に関する記事では、同じ XAML マークアップ (リソースを含む) と資産ファイルを、C# および C++/WinRT プロジェクト全体で使用することができました。 ある場合には、そうするためにマークアップの編集が必要になります。 「MainPage の移植を完了するために必要な XAML とスタイルをコピーする」を参照してください。

言語内のプロシージャに関連する変更

カテゴリ C# C++/WinRT 関連項目
非同期メソッドでの有効期間の管理 該当なし auto lifetime{ get_strong() }; または
auto lifetime = get_strong();
CopyButton_Click メソッドの移植
破棄 using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
CopyImage メソッドの移植
オブジェクトの構築 new MyType(args) MyType{ args } または
MyType(args)
Scenarios プロパティの移植
初期化されていない参照の作成 MyType myObject; MyType myObject{ nullptr }; または
MyType myObject = nullptr;
コンストラクター、CurrentFEATURE_NAME の移植
オブジェクトを、引数を持つ変数に構築 var myObject = new MyType(args); auto myObject{ MyType{ args } }; または
auto myObject{ MyType(args) }; または
auto myObject = MyType{ args }; または
auto myObject = MyType(args); または
MyType myObject{ args }; または
MyType myObject(args);
Footer_Click メソッドの移植
オブジェクトを、引数を持たない変数に構築 var myObject = new T(); MyType myObject; BuildClipboardFormatsOutputString メソッドの移植
オブジェクトの初期化の短縮形 var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
一括のベクター操作 var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
CopyButton_Click メソッドの移植
コレクションの反復処理 foreach (var v in c) for (auto&& v : c) BuildClipboardFormatsOutputString メソッドの移植
例外のキャッチ catch (Exception ex) catch (winrt::hresult_error const& ex) PasteButton_Click メソッドの移植
例外の詳細 ex.Message ex.message() PasteButton_Click メソッドの移植
プロパティ値の取得 myObject.MyProperty myObject.MyProperty() NotifyUser メソッドの移植
プロパティ値の設定 myObject.MyProperty = value; myObject.MyProperty(value);
プロパティ値の増分 myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
文字列の場合、ビルダーに切り替え
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
言語文字列を Windows ランタイム文字列に 該当なし winrt::hstring{ s }
文字列の作成 StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
文字列の作成
文字列補間 $"{i++}) {s.Title}" winrt::to_hstring および winrt::hstring::operator+ OnNavigatedTo メソッドの移植
比較のための空の文字列 System.String.Empty winrt::hstring::empty UpdateStatus メソッドの移植
空の文字列の作成 var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
ディクショナリ操作 map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
型変換 (エラー発生時にスロー) (MyType)v v.as<MyType>() Footer_Click メソッドの移植
型変換 (エラー発生時は null) v as MyType v.try_as<MyType>() PasteButton_Click メソッドの移植
x:Name を持つ XAML 要素はプロパティ MyNamedElement MyNamedElement() コンストラクター、CurrentFEATURE_NAME の移植
UI スレッドへの切り替え CoreDispatcher.RunAsync CoreDispatcher.RunAsync または winrt::resume_foreground NotifyUser メソッドの移植 および HistoryAndRoaming メソッドの移植
XAML ページの命令型コードでの UI 要素の構築 UI 要素の構築」を参照 UI 要素の構築」を参照

以下のセクションでは、表内のいくつかの項目について詳細を説明します。

UI 要素の構築

次のコード例で、XAML ページの命令型コードで UI 要素を構築する方法を示します。

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Windows::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

C# 型には Object.ToString メソッドが用意されています。

int i = 2;
var s = i.ToString(); // s is a System.String with value "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# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
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 の独自のオーバーロードを指定する必要があります。そうしないと、コンパイラ エラーが発生します。

Footer_Click メソッドの移植に関する記事も参照してください。

文字列の作成

C# には、文字列の作成用に組み込みの StringBuilder 型があります。

カテゴリ C# C++/WinRT
文字列の作成 StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Windows ランタイム文字列を追加し、null を保持 builder.Append(s); builder << std::wstring_view{ s };
改行を追加 builder.Append(Environment.NewLine); builder << std::endl;
結果にアクセス s = builder.ToString(); ws = builder.str();

BuildClipboardFormatsOutputString メソッドの移植および DisplayChangedFormats メソッドの移植 に関する記事も参照してください。

メイン UI スレッドでコードを実行する

この例は、「バーコード スキャナーのサンプル」に基づいています。

C# プロジェクトで、メイン UI スレッドで何らかの操作を行いたい場合は、通常は次のように CoreDispatcher.RunAsync メソッドを使用します。

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

C++/WinRT では、ずっとシンプルに表現できます。 最初の中断ポイント (この例では co_await) の後にパラメーターにアクセスすることを想定して、パラメーターを値渡しで受け取っていることに注目してください。 詳細については、「パラメーターの引き渡し」を参照してください。

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await Dispatcher();
    // Do work on the main UI thread here.
}

既定以外の優先順位で操作を行う必要がある場合、winrt::resume_foreground 関数を確認してください。これには、優先されるオーバーロードがあります。 winrt::resume_foreground への呼び出しを待つ方法を示すコード例については、「スレッドの関係を考慮したプログラミング」を参照してください。

IDL でランタイム クラスを定義する

MainPage 型の IDL」および「.idl ファイルを統合する」を参照してください。

必要な C++/WinRT Windows 名前空間ヘッダー ファイルをインクルードする

C++/WinRT では、Windows 名前空間から型を使用する場合は、対応する C++/WinRT Windows 名前空間ヘッダー ファイルを含めます。 例については、NotifyUser メソッドの移植に関する記事を参照してください。

ボックス化とボックス化解除

C++ では、スカラーが自動的にオブジェクト内にボックス化されます。 C++/Winrt では、winrt::box_value 関数を明示的に呼び出す必要があります。 どちらの言語でも、明示的にボックス化解除する必要があります。 C++/WinRT を使用したボックス化とボックス化解除に関するページを参照してください。

次の表では、これらの定義を使用します。

C# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
操作 C# 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# C++/WinRT
既知の整数をボックス化解除する i = (int)o; i = unbox_value<int>(o);
o が null の場合 System.NullReferenceException クラッシュ
o がボックス化された int でない場合 System.InvalidCastException クラッシュ
int をボックス化解除し、null の場合はフォールバックを使用する。それ以外の場合はクラッシュ i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
可能であれば int をボックス化解除する。それ以外の場合はフォールバックを使用する i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

例については、OnNavigatedTo メソッドの移植および Footer_Click メソッドの移植に関する記事を参照してください。

文字列のボックス化とボックス化解除

文字列は、ある点では値型であり、別の点では参照型です。 C# と C++/WinRT では文字列の扱いが異なります。

ABI 型 HSTRING は、参照カウント文字列へのポインターです。 しかし、IInspectable から派生したものではないため、厳密に言えばオブジェクトではありません。 さらに、null の HSTRING は空の文字列を表します。 IInspectable から派生したもの以外のボックス化は、IReference <T> 内にラップすることによって行われ、Windows ランタイムでは標準の実装が PropertyValue オブジェクトの形式で行われます (カスタム型は PropertyType::Othertype として報告されます)。

C# は Windows ランタイム文字列を参照型として表しますが、C++/WinRT は文字列を値型として投影します。 つまり、ボックス化された null 文字列は、どのように表されるかによって異なる表現を持つことができます。

動作 C# C++/WinRT
宣言 object o;
string s;
IInspectable o;
hstring s;
文字列型のカテゴリ 参照の種類 値タイプ
null HSTRING による投影 "" hstring{}
null と "" が同一かどうか いいえ はい
null の有効性 s = null;
s.Length により NullReferenceException が生成される
s = hstring{};
s.size() == 0 (有効)
オブジェクトに null 文字列を割り当てる場合 o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
オブジェクトに "" を割り当てる場合 o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

基本的なボックス化とボックス化解除。

操作 C# C++/WinRT
文字列をボックス化する o = s;
空の文字列は非 null オブジェクトになります。
o = box_value(s);
空の文字列は非 null オブジェクトになります。
既知の文字列をボックス化解除する s = (string)o;
null オブジェクトは null 文字列になります。
文字列でない場合は InvalidCastException。
s = unbox_value<hstring>(o);
null オブジェクトはクラッシュします。
文字列でない場合はクラッシュします。
使用可能な文字列をボックス化解除する s = o as string;
null オブジェクトまたは文字列以外は null 文字列になります。

または

s = o as string ?? fallback;
null または文字列以外はフォールバックになります。
空の文字列は保持されます。
s = unbox_value_or<hstring>(o, fallback);
null または文字列以外はフォールバックになります。
空の文字列は保持されます。

クラスを {Binding} マークアップ拡張に使用できるようにする

{Binding} マークアップ拡張を使用してデータ型にデータ バインドする場合は、「{Binding} を使って宣言されたバインディング オブジェクト」を参照してください。

XAML マークアップからのオブジェクトの使用

C# プロジェクトでは、XAML マークアップからプライベート メンバーと名前付き要素を使用できます。 ただし、C++/WinRT では、XAML {x:Bind} マークアップ拡張の使用によって使用されるすべてのエンティティが、IDL で公開されている必要があります。

また、ブール値へのバインドにより C# では true または false が表示されますが、C++/WinRT では Windows.Foundation.IReference`1<Boolean> が表示されます。

詳細とコード例については、マークアップからのオブジェクトの使用に関するページを参照してください。

データ ソースを XAML マークアップで使用できるようにする

C++/WinRT バージョン 2.0.190530.8 以降では、winrt::single_threaded_observable_vector により、IObservableVector<T>IObservableVector<IInspectable> の両方をサポートする監視可能なベクターが作成されます。 例については、Scenarios プロパティの移植に関する記事を参照してください。

次のような Midl ファイル (.idl) を作成できます (「ランタイム クラスを Midl ファイル (.idl) にファクタリングする」も参照してください)。

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

さらに、次のように実装できます。

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
	Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

詳細については、「XAML アイテム コントロール: C++/WinRT コレクションへのバインド」と「C++/WinRT でのコレクション」を参照してください。

データ ソースを XAML マークアップで使用できるようにする (C++/WinRT 2.0.190530.8 より前)

XAML データ バインディングでは、項目ソースが IIterable<IInspectable> と、次のインターフェイスの組み合わせのいずれかを実装する必要があります。

  • IObservableVector<IInspectable>
  • IBindableVector および INotifyCollectionChanged
  • IBindableVector および IBindableObservableVector
  • IBindableVector 単独 (変更には対応しません)
  • Ivector<IInspectable>
  • IBindableIterable (要素を反復処理してプライベート コレクションに保存します)

IVector<T> などのジェネリック インターフェイスは、実行時に検出できません。 各 IVector<T> には異なるインターフェイス識別子 (IID) があります。これが T の機能です。すべての開発者は T のセットを自由に拡張できるため、XAML バインディングのコードではクエリの対象となる完全なセットが明確にはわかりません。 この制約は C# では問題ではありません。IEnumerable<T> を実装するすべての CLR オブジェクトは IEnumerable を自動的に実装するためです。 これは、ABI レベルでは、IObservableVector<T> を実装するすべてのオブジェクトが IObservableVector<IInspectable> を自動的に実装することを意味します。

C++/WinRT ではその保証は提供されていません。 C++/WinRT ランタイム クラスが IObservableVector<T> を実装する場合は、IObservableVector<IInspectable> の実装も何らかの形で提供されるとは想定できません。

そのため、前の例では、次のように確認する必要があります。

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

実装は次のとおりです。

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
	Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

m_bookSkus 内のオブジェクトにアクセスする必要がある場合は、再度 Bookstore::BookSku に QI する必要があります。

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

派生クラス

ランタイム クラスから派生するためには、基底クラスが構成可能である必要があります。 C# ではクラスを構成可能にするために特別な手順を実行する必要はありませんが、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>
    {
        ...
    }
}

重要な API