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

ヒント

前にこのトピックを読み、特定のタスクを念頭に置いてこのトピックに戻る場合は、このトピックの「 実行しているタスクに基づいてコンテンツを検索 する」セクションに進むことができます。

このトピックでは、 C# プロジェクトのソース コードを C ++/WinRT で同等のコードに移植する場合に関連する技術的な詳細を包括的にカタログ化します。

ユニバーサル Windows プラットフォーム (UWP) アプリ サンプルの 1 つを移植するケース スタディについては、C# から C++/WinRT へのクリップボード サンプルの移植に関するコンパニオン トピックを参照してください。 移植の練習と経験を得るには、そのチュートリアルに従い、自分でサンプルを移植します。

準備方法と期待される内容

C # から C++/WinRT にクリップボード サンプルを移植するケース スタディでは、プロジェクトを C++/WinRT に移植する際に行うソフトウェア設計上の決定の種類の例を示します。 そのため、既存のコードのしくみを十分に理解して、移植の準備をすることをお勧めします。 そうすることで、アプリの機能とコードの構造の概要を把握し、行った決定が常に前進し、正しい方向に進みます。

予想される移植の変更の種類に関しては、それらを 4 つのカテゴリにグループ化できます。

  • 言語プロジェクションを移植します。 Windows ランタイム (WinRT) は、さまざまなプログラミング言語に投影されます。 これらの言語プロジェクションはいずれも、対象のプログラミング言語らしく自然に感じられるよう設計されています。 C# の場合、一部のWindows ランタイム型は.NET型として投影されます。 たとえば、System.Collections.Generic.IReadOnlyList<T>Windows.Foundation.Collections.IVectorView<T> に戻すことになります。 また、C# では、一部のWindows ランタイム操作は便利な C# 言語機能として予測されます。 たとえば、C# では、 += 演算子構文を使用してイベント処理デリゲートを登録します。 そのため、このような言語機能を、実行されている基本的な操作 (この例ではイベント登録) に翻訳します。
  • ポート言語の構文。 これらの変更の多くは、1 つのシンボルを別のシンボルに置き換える単純な機械的変換です。 たとえば、ドット (.) を二重コロン (::) に変更します。
  • ポート言語の手順。 これらの一部は、単純で反復的な変更 ( myObject.MyProperty から myObject.MyProperty()など) である場合があります。 他のユーザーは、より詳細な変更を必要とします (たとえば、 System.Text.StringBuilder の使用を伴うプロシージャを std::wostringstream の使用を伴うプロシージャに移植するなど)。
  • C++/WinRT に固有の移植関連のタスク。 Windows ランタイムの特定の詳細は、バックグラウンドで C# によって暗黙的に処理されます。 これらの詳細は、C++/WinRT で明示的に行われます。 たとえば、 .idl ファイルを使用してランタイム クラスを定義します。

次のタスク ベースのインデックスの後、このトピックの残りのセクションは上記の分類に従って構成されます。

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

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

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

これらのリンクで詳しく説明されていますが、ランタイム クラスは IDL で宣言する必要があります。 projectに既に IDL ファイル (Project.idl など) が含まれている場合は、そのファイルに新しいランタイム クラスを宣言することをお勧めします。 IDL で、アプリの外部で使用されるメソッドとデータ メンバー、または XAML で使用されるメソッドとデータ メンバーを宣言します。 IDL ファイルを更新した後、プロジェクトの.h フォルダーに生成されたスタブ ファイル (.cppGenerated Files) を再構築して確認します (プロジェクト ノードが選択されているソリューション エクスプローラーで、[すべてのファイルの表示] がオンになっていることを確認します)。 スタブ ファイルをプロジェクトに既に含まれているファイルと比較し、必要に応じてファイルを追加または関数シグネチャを追加/更新します。 スタブ ファイルの構文は常に正しいので、ビルド エラーを最小限に抑えるために使用することをお勧めします。 プロジェクト内のスタブがスタブ ファイル内のスタブと一致したら、C# コードを移植して実装できます。
通常のクラスを移植する ランタイム クラスを作成していない場合を参照してください。
作成者 IDL Microsoft インターフェイス定義言語 3.0 の概要
XAML UI で参照するランタイム クラスを作成している場合
XAML マークアップからのオブジェクトの使用
IDL でランタイム クラスを定義する
コレクションを移植する C++/WinRT を使用したコレクション
データ ソースを XAML マークアップで使用できるようにする
連想コンテナー
ベクトル メンバーアクセス
イベントを移植する クラス メンバーとしてのイベント ハンドラー デリゲート
イベント ハンドラー デリゲートの取り消し
メソッドを移植する C# から: private async void SampleButton_Tapped(object sender, Microsoft.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 を返します。

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

Category 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> と、要素を追加するための Appendstd::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 では、OPENBUTTON_CLICK メソッドを XAML マークアップに登録する場合は、実装型でパブリックにする必要があります。 イベント ハンドラーを命令型コードでのみ登録する場合、イベント ハンドラーをパブリックにする必要はありません。

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

または、登録元の XAML ページを実装型の friend に指定し、OpenButton_Click を private にすることもできます。

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

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

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

そのマークアップをより単純な Click="OpenButton_Click"に変更できます。 または、必要に応じて、そのマークアップをそのまま保持することもできます。 サポートする必要があるのは、IDL でイベント ハンドラーを宣言することです。

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

Note

Fire and forget として実装する場合でも、関数はvoidとして宣言します。

言語構文に関連する変更

Category C# C++/WinRT こちらも参照ください
アクセス修飾子 public \<member\> public:
    \<member\>
Button_Click メソッドの移植
データ メンバーにアクセスする this.variable this->variable  
非同期アクション async Task ... IAsyncAction ... C++/WinRT を使用した IAsyncAction インターフェイスコンカレンシーおよび非同期操作
非同期操作 async Task<T> ... IAsyncOperation<T> ... C++/WinRT を使用した IAsyncOperation インターフェイスコンカレンシーおよび非同期操作
Fire-and-forget メソッド (非同期を意味します) async void ... winrt::fire_and_forget ... CopyButton_Clickメソッドの移植Fire and forget
列挙定数にアクセスする 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; Current、コンストラクター、およびFEATURE_NAMEの移植
Verbatim/raw 文字列リテラル @"verbatim string literal" LR"(raw string literal)" DisplayToast メソッドの移植

Note

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

クラスとメンバーの移植

C# 型ごとに、Windows ランタイム型に移植するか、通常の C++ クラス/構造体/列挙型に移植するかを決定する必要があります。 詳細と、これらの決定を行う方法を示す詳細な例については、「 C# から C++/WinRT へのクリップボードサンプルの移植」を参照してください。

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

非静的フィールドの場合は、 実装型のデータ メンバーにします。

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

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

XAML マークアップとアセット ファイルの移植

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

言語内のプロシージャを含む変更

Category C# C++/WinRT こちらも参照ください
非同期メソッドでの有効期間管理 N/A 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;
コンストラクター、Current、およびFEATURE_NAMEの移植
args を使用して変数にオブジェクトを構築する 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 ランタイム 文字列に変換する N/A 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() コンストラクター、Current、およびFEATURE_NAMEの移植
UI スレッドに切り替える CoreDispatcher.RunAsync DispatcherQueue.TryEnqueue、または winrt::resume_foreground NotifyUser メソッドの移植HistoryAndRoaming メソッドの移植
XAML ページの命令型コードでの UI 要素の構築 UI 要素の構築を参照してください UI 要素の構築を参照してください

次のセクションでは、表の一部の項目について詳しく説明します。

UI 要素の構築

これらのコード例は、XAML ページの命令型コードでの UI 要素の構築を示しています。

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Microsoft.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Microsoft::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 もサポートされています。 追加で文字列化したい型に対応するオーバーロードを追加する必要があります。

Language int を文字列化 列挙型の文字列化
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);

列挙型を文字列化する場合は、 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 型が組み込まれています。

Category C# C++/WinRT
文字列の構築 StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
null 値を保持したまま Windows ランタイム 文字列を追加する 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 スレッドで作業する場合は、通常 、DispatcherQueue.TryEnqueue メソッド (または UWP の古い CoreDispatcher.RunAsync ) を使用します。 C# でのパターンの外観を次に示します。

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        // 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 DispatcherQueue();
    // 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;
Operation 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);

値型への null ポインターのボックス化を解除しようとすると、C++/CX と C# で例外が発生します。 C++/WinRT では、これがプログラミング エラーと見なされ、クラッシュします。 C++/WinRT では、オブジェクトが思った型ではないケースを処理する場合は、 winrt::unbox_value_or 関数を使用します。

Scenario 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 文字列は、取得方法に応じて異なる表現を持つことができます。

Behavior C# C++/WinRT
宣言 object o;
string s;
IInspectable o;
hstring s;
文字列型カテゴリ 参照の種類 値型
null HSTRING として投影される "" hstring{}
null と "" は同じですか? No Yes
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

基本的なボクシングとアンボクシング。

Operation 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 文字列になります。

OR

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 でパブリックに公開する必要があります。

また、Boolean にバインドすると、C# では true または false と表示されますが、C++/WinRT では Windows.Foundation.IReference`1<Boolean> と表示されます。

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

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

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

このように Midl ファイル (.idl) を作成できます (ランタイム クラスを Midl ファイル (.idl) に分解する方法も参照してください)。

namespace Bookstore
{
    runtimeclass BookSku { ... }

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

    runtimeclass MainPage : Microsoft.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>
  • IBindableVectorINotifyCollectionChanged
  • IBindableVectorIBindableObservableVector
  • IBindableVector 単独 (変更には応答しません)
  • IVector<IInspectable>
  • IBindableIterable (要素を反復処理してプライベート コレクションに保存します)

IVector<T> などの汎用インターフェイスは実行時に検出できません。 各 IVector<T> には、 T の関数である異なるインターフェイス識別子 (IID) があります。どの開発者も T のセットを任意に展開できるため、XAML バインド コードはクエリ対象の完全なセットを認識できません。 IEnumerable<T> を実装するすべての CLR オブジェクトが自動的に IEnumerable を実装するため、C# ではこの制限は問題になりません。 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 に戻す必要があります。

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 : Microsoft.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