このトピックでは、ユニバーサル Windows プラットフォーム (UWP) アプリサンプルの 1 つを C# から C++/WinRT に移植するケース スタディについて説明します。 移植の練習と経験を得るには、チュートリアルに従い、自分でサンプルを移植します。
Note
移植される ソース コードは、UWP C# アプリです。 この記事の宛先 C++/WinRT コードは、WinUI 3 (Windows アプリ SDK) 用に記述されています。 この記事では、WinUI 3 とは異なる API ( Windows.UI.Core.CoreDispatcher と Microsoft.UI.Dispatching.DispatcherQueue など) が UWP ソースで使用されている場合は、C++/WinRT 出力で同等の正しい WinUI 3 を明示的に示します。 C++/WinRT 列のコード パターンは、WinUI 3 アプリで直接使用できます。
C# からの C++/WinRT への移植に関連する技術的な詳細の包括的なカタログについては、C# からの C++/WinRT への移行に関するコンパニオン トピックを参照してください。
C# と C++ のソース コード ファイルに関する簡単な序文
C# プロジェクトでは、ソース コード ファイルは主に .cs ファイルです。 C++ に移行すると、使い慣れるソース コード ファイルの種類が増えることがわかります。 その理由は、コンパイラの違い、C++ ソース コードの再利用方法、型とその関数 (そのメソッド) の 宣言 と 定義 の概念に関係します。
関数 宣言 は、関数の シグネチャ (戻り値の型、名前、およびパラメーターの型と名前) のみを記述します。 関数 定義 には、関数の 本体 (その実装) が含まれます。
型に関しては少し異なります。 型を 定義 する場合は、その名前を指定し、(少なくとも) すべてのメンバー関数 (およびその他のメンバー) を 宣言 するだけです。 正解です。メンバー関数を 定義 しない場合でも、型を定義できます。
- 一般的な C++ ソース コード ファイルは、
.h(dot aitch) ファイルと.cppファイルです。.hファイルはヘッダー ファイルであり、1 つ以上の型を定義します。 ヘッダーでメンバー関数を定義 できますが 、通常は.cppファイルの目的です。 したがって、仮定の C++ 型 MyClass の場合は、でMyClass.hを定義し、そのメンバー関数をMyClass.cppで定義します。 他の開発者がクラスを再利用するには、.hファイルとオブジェクト コードのみを共有します。 実装によって知的財産が構成されるため、.cppファイルは秘密にしておく必要があります。 - プリコンパイル済みヘッダー (
pch.h)。 通常、アプリケーションに含めるヘッダー ファイルのセットがあり、それらのファイルを頻繁に変更することはありません。 そのため、コンパイルするたびにそのヘッダー セットの内容を処理するのではなく、それらのヘッダーを 1 つのファイルに集約し、1 回コンパイルしてから、ビルドするたびにそのプリコンパイル ステップの出力を使用できます。 これは 、プリコンパイル済みヘッダー ファイル (通常はpch.hという名前) を使用して行います。 -
.idlファイル。 これらのファイルには、インターフェイス定義言語 (IDL) が含まれています。 IDL は、Windows ランタイム型のヘッダー ファイルと考えることができます。 詳細については、MainPage 型の IDL セクションで IDL について説明します。
クリップボードのサンプルをダウンロードしてテストする
クリップボードの サンプル Web ページにアクセスし、[ ZIP のダウンロード] をクリックします。 ダウンロードしたファイルを解凍し、フォルダー構造を確認します。
- サンプル ソース コードの C# バージョンは、
csという名前のフォルダーに含まれています。 - サンプル ソース コードの C++/WinRT バージョンは、
cppwinrtという名前のフォルダーに含まれています。 - C# バージョンと C++/WinRT バージョンの両方で使用されるその他のファイルは、
sharedフォルダーとSharedContentフォルダーにあります。
このトピックのチュートリアルでは、C# ソース コードから移植することで、クリップボード サンプルの C++/WinRT バージョンを再作成する方法を示します。 そうすることで、独自の C# プロジェクトを C++/WinRT に移植する方法を確認できます。
サンプルの動作を確認するには、C# ソリューション (\Clipboard_sample\cs\Clipboard.sln) を開き、必要に応じて構成を変更 ( x64 に変更)、ビルド、実行します。 サンプル独自のユーザー インターフェイス (UI) では、さまざまな機能を順を追って説明します。
Tip
ダウンロードしたサンプルのルート フォルダーには、Clipboardではなく、Clipboard_sampleという名前が付けられている場合があります。 ただし、後の手順で作成する C++/WinRT バージョンと区別するために、そのフォルダーを Clipboard_sample と引き続き参照します。
クリップボードという名前の空のアプリを作成する
Note
C++/WinRT Visual Studio拡張機能 (VSIX) と NuGet パッケージ (プロジェクト テンプレートとビルド サポートを提供するパッケージ) のインストールと使用については、C++/WinRT のサポートVisual Studio参照してください。
Microsoft Visual Studioで新しい C++/WinRT プロジェクトを作成して、移植プロセスを開始します。 空のアプリ (パッケージ化済み、デスクトップの WinUI 3) の C++ プロジェクト テンプレートを使用して、新しいプロジェクトを作成します。 その名前を クリップボードに設定し(フォルダー構造がチュートリアルと一致するように)、 ソリューションとプロジェクトを同じディレクトリに配置 するチェック ボックスがオフになっていることを確認します。
ベースラインを取得するには、この新しい空のプロジェクトがビルドされて実行されていることを確認します。
Package.appxmanifest ファイルとアセット ファイル
サンプルの C# バージョンと C++/WinRT バージョンを同じコンピューターに並べてインストールする必要がない場合は、2 つのプロジェクトのアプリ パッケージ マニフェスト ソース ファイル (Package.appxmanifest) を同じにすることができます。 その場合は、 Package.appxmanifest を C# プロジェクトから C++/WinRT プロジェクトにコピーするだけで済みます。
2 つのバージョンのサンプルを共存させるためには、異なる識別子が必要です。 その場合、C++/WinRT プロジェクトで、XML エディターで Package.appxmanifest ファイルを開き、これら 3 つの値を書き留めます。
- /Package/Identity 要素内で、Name 属性の値をメモします。 これは パッケージ名です。 新しく作成されたプロジェクトの場合、プロジェクトには一意の GUID の初期値が与えられます。
- /Package/Applications/Application 要素内で、Id 属性の値をメモします。 これは アプリケーション ID です。
- /Package/mp:PhoneIdentity 要素内で、PhoneProductId 属性の値をメモします。 ここでも、新しく作成されたプロジェクトの場合、これはパッケージ名が設定されているのと同じ GUID に設定されます。
次に、 Package.appxmanifest を C# プロジェクトから C++/WinRT プロジェクトにコピーします。 最後に、指定した 3 つの値を復元できます。 または、コピーした値を編集して、アプリケーションや組織 (通常は新しいプロジェクトの場合と同様) に一意または適切なものにすることができます。 たとえば、この場合、パッケージ名の値を復元する代わりに、コピーした値を Microsoft.SDKSamples.Clipboard.CS から Microsoft.SDKSamples.Clipboard.CppWinRT に変更するだけで済みます。 また、アプリケーション ID は App に設定したままにすることができます。 パッケージ名 または アプリケーション ID が異なる限り、2 つのアプリケーションのアプリケーション ユーザー モデル ID (AUMID) は異なります。 これは、2 つのアプリを同じコンピューターに並べてインストールするために必要な機能です。
このチュートリアルでは、 Package.appxmanifestで他のいくつかの変更を行うのが理にかなっています。 文字列 クリップボード C# サンプルは 3 回出現します。 それをClipboard C++/WinRT Sampleに変更します。
C++/WinRT プロジェクトでは、 Package.appxmanifest ファイルとプロジェクトが参照する資産ファイルに関して同期が取れなくなっています。 これを解決するには、まず、Assets フォルダー (Visual Studioのソリューション エクスプローラー) 内のすべてのファイルを選択し、それらを削除して (ダイアログで [削除] を選択)、C++/WinRT プロジェクトから資産を削除します。
C# プロジェクトは、共有フォルダーからアセット ファイルを参照します。 C++/WinRT プロジェクトでも同じ操作を行うことができます。また、このチュートリアルで行うのと同じようにファイルをコピーすることもできます。
\Clipboard_sample\SharedContent\media フォルダーに移動します。 C# プロジェクトに含まれる 7 つのファイル (microsoft-sdk.png、 smalltile-sdk.png、 splash-sdk.png、 squaretile-sdk.png、 storelogo-sdk.png、 tile-sdk.png、および windows-sdk.png) を選択してコピーし、新しいプロジェクトの \Clipboard\Clipboard\Assets フォルダーに貼り付けます。
Assets フォルダー (C++/WinRT プロジェクトのソリューション エクスプローラー) >Add>Existing item... を右クリックし、\Clipboard\Clipboard\Assetsに移動します。 ファイル ピッカーで 7 つのファイルを選択し、[ 追加] をクリックします。
Package.appxmanifest は、プロジェクトのアセット ファイルと同期しています。
MainPage (サンプルを構成する機能を含む)
クリップボード のサンプルは、すべてのユニバーサル Windows プラットフォーム (UWP) アプリサンプルと同様に、ユーザーが一度に 1 つずつステップ実行できるシナリオのコレクションで構成されています。 特定のサンプルのシナリオのコレクションは、サンプルのソース コードで構成されます。 コレクション内の各シナリオは、タイトルと、シナリオを実装するプロジェクト内のクラスの型を格納するデータ項目です。
サンプルの C# バージョンでは、 SampleConfiguration.cs ソース コード ファイルを見ると、2 つのクラスが表示されます。 ほとんどの構成ロジックは MainPage クラスにあります。これは部分クラスです ( MainPage.xaml のマークアップと MainPage.xaml.csの命令型コードと組み合わせると、完全なクラスを形成します)。 このソース コード ファイルのもう 1 つのクラスは、Title プロパティと ClassType プロパティを含む Scenario です。
次のいくつかのサブセクションでは、 MainPage と シナリオを移植する方法について説明します。
MainPage 型の IDL
このセクションでは、インターフェイス定義言語 (IDL) について簡単に説明し、C++/WinRT を使用してプログラミングする際にどのように役立つかを説明します。 IDL は、Windows ランタイム型の呼び出し可能なサーフェスを記述するソース コードの一種です。 型の呼び出し可能な(またはパブリックな)公開面は、その型を利用できるように外部へ 公開 されます。 この型の 投影部分 は、型の実際の内部実装とは対照的です。これはもちろん、呼び出し可能ではなく、パブリックではありません。 これは、IDL で定義した投影部分だけです。
(.idl ファイル内で) IDL ソース コードを作成した後、IDL をコンピューターが読み取り可能なメタデータ ファイル (Windows メタデータとも呼ばれます) にコンパイルできます。 これらのメタデータ ファイルには拡張子 .winmdがあり、その用途の一部を次に示します。
-
.winmdは、コンポーネント内のWindows ランタイム型を記述できます。 アプリケーション プロジェクトから Windows ランタイム コンポーネント (WRC) を参照すると、アプリケーション プロジェクトは WRC に属する Windows メタデータを読み取ります (そのメタデータは別のファイルに含まれているか、WRC 自体と同じファイルにパッケージ化される可能性があります)。 -
.winmdは、同じアプリケーションの別の部分で使用できるように、アプリケーションの 1 つの部分でWindows ランタイムの種類を記述できます。 たとえば、同じアプリ内の XAML ページで利用される Windows ランタイム の型です。 - Windows ランタイム型 (組み込みまたはサード パーティ) の使用を容易にするために、C++/WinRT ビルド システムでは、
.winmdファイルを使用してラッパー型を生成し、それらのWindows ランタイム型の投影部分を表します。 - 独自のWindows ランタイム型を実装しやすくするために、C++/WinRT ビルド システムは IDL を
.winmdファイルに変換し、それを使用してプロジェクションのラッパーと、実装の基礎となるスタブを生成します (これらのスタブについては、このトピックの後半で詳しく説明します)。
C++/WinRT で使用する IDL の特定のバージョンは、インターフェイス定義言語 3.0 Microsoftです。 このセクションの残りの部分では、C# MainPage 型について詳しく説明します。 どの部分を C++/WinRT MainPage 型の プロジェクション(つまり、呼び出し可能な public インターフェイス)に含める必要があるか、またどの部分をその実装の一部のみにできるかを決定します。 この区別は重要です。IDL を作成する場合 (この後のセクションで行います)、そこで呼び出し可能な部分のみを定義するためです。
MainPage 型を一緒に実装する C# ソース コード ファイルは、MainPage.xaml (コピーして間もなく移植します)、MainPage.xaml.cs、およびSampleConfiguration.csです。
C++/WinRT バージョンでは、 MainPage 型を同様の方法でソース コード ファイルに組み込みます。
MainPage.xaml.csのロジックを取得し、大部分をMainPage.hとMainPage.cppに翻訳します。 また、 SampleConfiguration.csのロジックについては、 SampleConfiguration.h と SampleConfiguration.cppに変換します。
C# ユニバーサル Windows プラットフォーム (UWP) アプリケーションのクラスは、もちろんWindows ランタイム型です。 ただし、C++/WinRT アプリケーションで型を作成する場合は、その型がWindows ランタイム型か、通常の C++ クラス/構造体/列挙型かを選択できます。
プロジェクト内のすべての XAML ページはWindows ランタイム型である必要があるため、MainPage はWindows ランタイム型である必要があります。 C++/WinRT プロジェクトでは、MainPage は既にWindows ランタイム型であるため、その側面を変更する必要はありません。 具体的には、 ランタイム クラスです。
- 特定の型のランタイム クラスを作成する必要があるかどうかの詳細については、 C++/WinRT を使用した API の作成に関するトピックを参照してください。
- C++/WinRT では、ランタイム クラスの内部実装とその投影 (パブリック) 部分は、2 つの異なるクラスの形式で存在します。 これらは実装 型 と 投影型と呼ばれます。 詳細については、前の箇条書きで説明したトピックと、 C++/WinRT を使用した API の使用に関するページを参照してください。
- ランタイム クラスと IDL(
.idlファイル)の関係について詳しくは、トピック XAML コントロール: C++/WinRT プロパティへのバインド を参照しながら読み進めることができます。 このトピックでは、新しいランタイム クラスを作成するプロセスについて取り上げます。最初の手順は、新しい Midl File (.idl) 項目をプロジェクトに追加することです。
MainPage の場合、実際には C++/WinRT プロジェクトに必要なMainPage.idl ファイルが既にあります。 それは、プロジェクト テンプレートがそれを作成してくれたからです。 ただし、このチュートリアルの後半では、プロジェクトにさらに .idl ファイルを追加します。
既存の MainPage.idl ファイルに追加する必要がある IDL の一覧がすぐに表示されます。 その前に、IDL で何を行う必要があるか、何を行わないかについて何らかの理由で行う必要があります。
(MainPage ランタイム クラスの一部になるように) MainPage.idlで宣言する必要がある MainPage のメンバーと、単に MainPage 実装型のメンバーを決定するには、C# MainPage クラスのメンバーの一覧を作成します。 これらのメンバーを見つけるには、 MainPage.xaml.cs と SampleConfiguration.csを調べる必要があります。
合計 12 個の protected と private のフィールドとメソッドが見つかります。 そして、次の public メンバーを見つけます。
- 既定のコンストラクター
MainPage()。 - 静的フィールド Current と FEATURE_NAME。
- プロパティ IsClipboardContentChangedEnabled と Scenarios。
- メソッド BuildClipboardFormatsOutputString、DisplayToast、EnableClipboardContentChangedNotifications、および NotifyUser。
publicで宣言する候補となるのは、MainPage.idlメンバーです。 それでは、それぞれを調べて、 MainPage ランタイム クラスの一部である必要があるかどうか、または実装の一部である必要があるかどうかを確認しましょう。
- 既定のコンストラクター
MainPage()。 XAML ページの場合、IDL で既定のコンストラクターを宣言するのが普通です。 そうすることで、XAML UI フレームワークで型をアクティブ化できます。 - 静的フィールド Current は、 アプリケーションの MainPage インスタンスにアクセスするために、個々のシナリオの XAML ページ内から使用されます。 Current は XAML フレームワークとの相互運用に使用されていないため (また、コンパイル ユニット間で使用されることもありません)、実装型のメンバーとしてのみ予約できます。 このような場合、自分のプロジェクトであれば、そうすることを選ぶかもしれません。 しかし、フィールドは投影された型のインスタンスであるため、IDL で宣言するのは論理的だと感じます。 そのため、ここで行います (また、コードが少しクリーンになります)。
- MainPage 型内でアクセスされる静的FEATURE_NAME フィールドの場合も同様です。 ここでも、IDL で宣言することを選択すると、コードが少しクリーンになります。
- プロパティ IsClipboardContentChangedEnabled は OtherScenarios クラスでのみ使用されます。 そのため、ポート中に少し簡略化し、 OtherScenarios ランタイム クラスのプライベート フィールドにします。 そうすれば、それは IDL には入りません。
- プロパティ Scenarios は、 Scenario 型 ( 前に説明した型) のオブジェクトのコレクションです。 次のサブセクションで Scenario について説明します。そのため、それまでは Scenarios プロパティを残しておきましょう。
- BuildClipboardFormatsOutputString、DisplayToast、および EnableClipboardContentChangedNotifications メソッドは、メイン ページよりもサンプルの一般的な状態との関係が強いユーティリティ関数です。 そのため、ポート中に、これらの 3 つのメソッドを SampleState という名前の新しいユーティリティ型にリファクタリングします (Windows ランタイム型である必要はありません)。 そのため、これら 3 つのメソッドは IDL には含まれません。
- メソッド NotifyUser は、静的な Current フィールドから返される MainPage のインスタンス上の個々のシナリオの XAML ページ内から呼び出されます。 すでに述べたように、Current は射影型のインスタンスであるため、IDL で NotifyUser を宣言する必要があります。 NotifyUser は NotifyType 型のパラメーターを受け取ります。 これについては、次のサブセクションで説明します。
データ バインドの対象にしたいメンバーは、{x:Bind} と {Binding} のどちらを使用している場合でも、IDL でも宣言する必要があります。 詳細については、「 データ バインディング」を参照してください。
進行中です。 MainPage.idl ファイルに追加するメンバーと追加しないメンバーの一覧を開発しています。 ただし、 Scenarios プロパティと NotifyType 型について説明する必要があります。 それでは、次にそうしましょう。
シナリオと NotifyType 型の IDL
Scenario クラスは、SampleConfiguration.csで定義されています。 そのクラスを C++/WinRT に移植する方法について決定しました。 既定では、通常の C++ structにする可能性があります。 ただし、シナリオがバイナリ間で使用されている場合、または XAML フレームワークと相互運用する場合は、IDL でWindows ランタイム型として宣言する必要があります。
C# ソース コードを調べると、このコンテキストで シナリオ が使用されていることがわかります。
<ListBox x:Name="ScenarioControl" ... >
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
Scenario オブジェクトのコレクションが、ListBox (項目コントロール) の ItemsSource プロパティに割り当てられます。 シナリオは XAML と相互運用する必要があるため、Windows ランタイム型である必要があります。 そのため、IDL で定義する必要があります。 IDL で シナリオ の種類を定義すると、C++/WinRT ビルド システムは、バックグラウンド ヘッダー ファイル (このチュートリアルでは重要ではない名前と場所) で シナリオ のソース コード定義を生成します。
MainPage.Scenarios は Scenario オブジェクトのコレクションであり、IDL に存在する必要があると述べたことを思い出してください。 そのため、 MainPage.Scenarios 自体も IDL で宣言する必要があります。
NotifyType は、C# のenumで宣言されたMainPage.xaml.csです。
MainPage ランタイム クラスに属するメソッドに NotifyType を渡すので、NotifyType もWindows ランタイム型である必要があり、MainPage.idlで定義する必要があります。
次に、 ファイルに新しい型と、IDL で宣言することにした Mainpage の新しいメンバーを追加してみましょう。 同時に、Visual Studio プロジェクト テンプレートから提供された Mainpage のプレースホルダー メンバーを IDL から削除します。
そのため、C++/WinRT プロジェクトで MainPage.idlを開き、次の一覧のように編集します。 編集の 1 つは、名前空間名を クリップボード から SDKTemplate に変更することです。 必要に応じて、 MainPage.idl の内容全体を次のコードに置き換えることができます。 注意すべきもう 1 つの調整は、 Scenario::ClassType の名前を Scenario::ClassName に変更することです。
// MainPage.idl
namespace SDKTemplate
{
struct Scenario
{
String Title;
Microsoft.UI.Xaml.Interop.TypeName ClassName;
};
enum NotifyType
{
StatusMessage,
ErrorMessage
};
[default_interface]
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
static MainPage Current{ get; };
static String FEATURE_NAME{ get; };
static Windows.Foundation.Collections.IVector<Scenario> scenarios{ get; };
void NotifyUser(String strMessage, NotifyType type);
};
}
Note
C++/WinRT プロジェクトの.idl ファイルの内容の詳細については、「Microsoft インターフェイス定義言語 3.0」を参照してください。
独自の移植作業では、上記のように名前空間名を変更したくない場合や変更する必要がない場合があります。 ここで行っているのは、移植する C# プロジェクトの既定の名前空間が SDKTemplate であるためです。プロジェクトとアセンブリの名前は クリップボードです。
ただし、このチュートリアルのポートに進むにつれて、 クリップボード 名前空間名のソース コード内のすべての出現箇所を SDKTemplate に変更します。 C++/WinRT プロジェクトのプロパティには クリップボード の名前空間名が表示される場所もあります。そのため、ここで変更する機会を得ます。
Visual Studioで、C++/WinRT プロジェクトの場合、プロジェクト プロパティの共通プロパティ>C++/WinRT>Root 名前空間を値 SDKTemplate に設定します。
IDL を保存し、スタブ ファイルを生成し直す
C++/WinRT プロパティへのバインドに関する XAML コントロールのトピックでは、スタブ ファイルの概念を紹介し、それらの動作のチュートリアルを示します。 また、このトピックで前述したスタブについては、C++/WinRT ビルド システムが.idl ファイルの内容を Windows メタデータに変換し、そのメタデータから cppwinrt.exe という名前のツールによって、実装の基盤となるスタブが生成されることを説明しました。
IDL で何かを追加、削除、または変更し、ビルドするたびに、ビルド システムによって、これらのスタブ ファイル内のスタブ実装が更新されます。 そのため、IDL を変更してビルドするたびに、これらのスタブ ファイルを表示し、変更した署名をコピーして、プロジェクトに貼り付けることをお勧めします。 これを行う方法の詳細と例をすぐに説明します。 しかし、これを行う利点は、実装型の形状とメソッドのシグネチャが何であるかを常に知るためのエラーのない方法を提供することです。
チュートリアルのこの時点で、 MainPage.idl ファイルの編集が完了したので、ここで保存する必要があります。 現時点ではプロジェクトは完了するまでビルドされませんが、 MainPage のスタブ ファイルが再生成されるため、ビルドを今すぐ実行すると便利です。 そのため、プロジェクトを今すぐビルドし、ビルド エラーを無視します。
この C++/WinRT プロジェクトでは、スタブ ファイルは \Clipboard\Clipboard\Generated Files\sources フォルダーに生成されます。 部分的なビルドが終了した後に見つかります (予想どおり、ビルドは完全には成功しません。ただし、関心のあるステップ (スタブの生成) は 成功しました)。 関心のあるファイルは、 MainPage.h と MainPage.cppです。
これら 2 つのスタブ ファイルには、IDL に追加した MainPage のメンバーの新しいスタブ実装が表示されます (現在 と FEATURE_NAMEなど)。 これらのスタブ実装を、プロジェクトに既に存在する MainPage.h ファイルと MainPage.cpp ファイルにコピーします。 同時に、IDL と同様に、Visual Studio プロジェクト テンプレートが提供した Mainpage のプレースホルダー メンバー (MyProperty というダミー プロパティと ClickHandler という名前のイベント ハンドラー) を、既存のファイルから削除します。
実際、MainPage の現在のバージョンに含まれるメンバーのうち、残したいのはコンストラクターだけです。
スタブ ファイルから新しいメンバーをコピーし、不要なメンバーを削除し、名前空間を更新すると、プロジェクト内の MainPage.h ファイルと MainPage.cpp ファイルは次のコード一覧のようになります。
MainPage には 2 つの種類があることに注意してください。 1 つは implementation 名前空間に、もう 1 つは factory_implementation 名前空間に。
factory_implementationに加えた唯一の変更は、SDKTemplate を名前空間に追加することです。
// MainPage.h
#pragma once
#include "MainPage.g.h"
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
MainPage();
static SDKTemplate::MainPage Current();
static hstring FEATURE_NAME();
static Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> scenarios();
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
};
}
namespace winrt::SDKTemplate::factory_implementation
{
struct MainPage : MainPageT<MainPage, implementation::MainPage>
{
};
}
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
namespace winrt::SDKTemplate::implementation
{
MainPage::MainPage()
{
InitializeComponent();
}
SDKTemplate::MainPage MainPage::Current()
{
throw hresult_not_implemented();
}
hstring MainPage::FEATURE_NAME()
{
throw hresult_not_implemented();
}
Windows::Foundation::Collections::IVector<SDKTemplate::Scenario> MainPage::scenarios()
{
throw hresult_not_implemented();
}
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
}
文字列の場合、C# は System.String を使用します。 例については、 MainPage.NotifyUser メソッドを参照してください。 IDL では、 文字列を使用して文字列を宣言し、 cppwinrt.exe ツールが C++/WinRT コードを生成するときに winrt::hstring 型を使用します。 C# コードで文字列が見つかるたびに、 winrt::hstring に移植します。 詳細については、「 C++/WinRT での文字列処理」を参照してください。
メソッド シグネチャの const& パラメーターの説明については、「 パラメーターの受け渡し」を参照してください。
残りのすべての名前空間宣言/参照を更新し、ビルドする
C++/WinRT プロジェクトをビルドする前に、Clipboard 名前空間の宣言 (および クリップボード への参照) を見つけて、 それらを SDKTemplate に変更します。
-
MainPage.xamlとApp.xaml. 名前空間は、x:Class属性とxmlns:local属性の値に表示されます。 -
App.idl。 -
App.h。 -
App.cpp。 2 つのusing namespaceディレクティブ (部分文字列using namespace Clipboardの検索) と MainPage 型の 2 つの修飾 (Clipboard::MainPageの検索) があります。 それらは変更する必要があります。
MainPage からイベント ハンドラーを削除したので、MainPage.xamlに移動し、マークアップから Button 要素を削除します。
すべてのファイルを保存します。 ソリューション (Build>Clean Solution) をクリーンアップし、ビルドします。 これまでに記述したとおりに、すべての変更に従った結果、ビルドは成功することが期待されます。
IDL で宣言した MainPage メンバーを実装する
コンストラクター、Current、およびFEATURE_NAME
移植する必要がある (C# プロジェクトの) 関連するコードを次に示します。
<!-- MainPage.xaml -->
...
<TextBlock x:Name="SampleTitle" ... />
...
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
public static MainPage Current;
public MainPage()
{
InitializeComponent();
Current = this;
SampleTitle.Text = FEATURE_NAME;
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
public const string FEATURE_NAME = "Clipboard C# sample";
...
}
...
間もなく、 MainPage.xaml 全体を (コピーして) 再利用します。 現時点 (以下) では、C++/WinRT プロジェクトのに、適切な名前の MainPage.xaml 要素を一時的に追加します。
FEATURE_NAME は MainPage の静的フィールドです (C# const フィールドは基本的にその動作で静的です)、 SampleConfiguration.csで定義されます。 C++/WinRT の場合、(静的) フィールドではなく、(静的) 読み取り専用プロパティの C++/WinRT 式にします。 C++/WinRT でプロパティ ゲッターを表す方法は、プロパティ値を返し、パラメーターを受け取らない関数(アクセサー)として表現することです。 そのため、C# FEATURE_NAME 静的フィールドは C++/WinRT FEATURE_NAME 静的アクセサー関数になります (この場合は文字列リテラルを返します)。
ちなみに、C# の読み取り専用プロパティを移植する場合も同じことを行います。 C# の書き込み可能なプロパティの場合、プロパティ セッターを表す C++/WinRT の方法は、プロパティ値をパラメーター (ミューテーター) として受け取る void 関数です。 どちらの場合も、C# フィールドまたはプロパティが静的な場合は、C++/WinRT アクセサーやミューテーターも静的です。
Current は MainPage の静的な (定数ではない) フィールドです。 ここでも、それを (C++/WinRT 式の) 読み取り専用プロパティにし、再び静的にします。
FEATURE_NAMEが定数の場合、Current は定数ではありません。 したがって、C++/WinRT ではバッキング フィールドが必要になり、アクセサーはそのフィールドを返します。 そのため、C++/WinRT プロジェクトでは、MainPage.h という名前のプライベート静的フィールド宣言し、でMainPage.cppを定義または初期化します (静的ストレージ期間があるため)。Current という名前のパブリック静的アクセサー関数を使用してアクセスします。
コンストラクター自体は、移植が簡単ないくつかの割り当てを実行します。
C++/WinRT プロジェクトで、という名前の新しい >>SampleConfiguration.cpp 項目を追加します。
以下の一覧と一致するように、 MainPage.xaml、 MainPage.h、 MainPage.cpp、および SampleConfiguration.cpp を編集します。
<!-- MainPage.xaml -->
...
<StackPanel ...>
<TextBlock x:Name="SampleTitle" />
</StackPanel>
...
// MainPage.h
...
namespace winrt::SDKTemplate::implementation
{
struct MainPage : MainPageT<MainPage>
{
...
static SDKTemplate::MainPage Current() { return current; }
...
private:
static SDKTemplate::MainPage current;
...
};
...
}
// MainPage.cpp
...
namespace winrt::SDKTemplate::implementation
{
SDKTemplate::MainPage MainPage::current{ nullptr };
MainPage::MainPage()
{
InitializeComponent();
MainPage::current = *this;
SampleTitle().Text(FEATURE_NAME());
}
...
}
// SampleConfiguration.cpp
#include "pch.h"
#include "MainPage.h"
using namespace winrt;
using namespace SDKTemplate;
hstring implementation::MainPage::FEATURE_NAME()
{
return L"Clipboard C++/WinRT Sample";
}
また、MainPage.cppFEATURE_NAME()のから既存の関数本体を削除してください。これらのメソッドは別の場所で定義されているためです。
ご覧のように、 MainPage::current は、投影された型である SDKTemplate::MainPage 型として宣言されています。
SDKTemplate::implementation::MainPage 型ではありません。これは実装型です。 投影型は、XAML 相互運用のために、またはバイナリ間でプロジェクト内で使用されるように設計された型です。 実装型は、投影型で公開した機能を実装するために使用するものです。
MainPage::current (MainPage.h) の宣言は実装名前空間 (winrt::SDKTemplate::implementation) 内に表示されるため、修飾されていない MainPage は実装型を参照します。 したがって、MainPage::current を投影された型 winrt::SDKTemplate::MainPage のインスタンスにしたいことを明確にするために、SDKTemplate:: で修飾します。
コンストラクターには、説明に値する MainPage::current = *this; に関連するいくつかの点があります。
- 実装型のメンバー内で
thisポインターを使用する場合、thisポインターは 実装型へのポインターになります。 -
thisポインターを対応する投影型に変換するには、それを逆参照します。 IDL から実装型を生成する場合 (ここに示すように)、実装型には、投影された型に変換する変換演算子があります。 これが、ここでの割り当てが機能する理由です。
これらの詳細の詳細については、「 実装型とインターフェイスのインスタンス化と返し」を参照してください。
コンストラクター内にも SampleTitle().Text(FEATURE_NAME()); があります。
SampleTitle()部分は、Xaml に追加した TextBlock を返す SampleTitle という名前の単純なアクセサー関数の呼び出しです。 XAML 要素を x:Name するたびに、XAML コンパイラによって、その要素の名前が付けられたアクセサーが生成されます。
.Text(...) パーツは、SampleTitle アクセサーが返した TextBlock オブジェクトに対して Text ミューテーター関数を呼び出します。
FEATURE_NAME()は静的 MainPage::FEATURE_NAME アクセサー関数を呼び出して文字列リテラルを返します。 つまり、そのコード行で、SampleTitle という名前の TextBlock の Text プロパティを設定します。
文字列はWindows ランタイムでワイドであるため、文字列リテラルを移植するには、ワイド文字エンコード プレフィックスLでプレフィックスを付けます。 したがって、(たとえば) "a string literal" を L"a string literal" に変更します。
ワイド文字列リテラルも参照してください。
シナリオ
移植する必要がある関連する C# コードを次に示します。
// MainPage.xaml.cs
...
public sealed partial class MainPage : Page
{
...
public List<Scenario> Scenarios
{
get { return this.scenarios; }
}
...
}
...
// SampleConfiguration.cs
...
public partial class MainPage : Page
{
...
List<Scenario> scenarios = new List<Scenario>
{
new Scenario() { Title = "Copy and paste text", ClassType = typeof(CopyText) },
new Scenario() { Title = "Copy and paste an image", ClassType = typeof(CopyImage) },
new Scenario() { Title = "Copy and paste files", ClassType = typeof(CopyFiles) },
new Scenario() { Title = "Other Clipboard operations", ClassType = typeof(OtherScenarios) }
};
...
}
...
以前の調査から、この シナリオ オブジェクトのコレクションが ListBox に表示されていることがわかります。 C++/WinRT では、アイテム コントロールの ItemsSource プロパティに割り当てることができるコレクションの種類に制限があります。 コレクションはベクターまたは監視可能なベクターである必要があり、その要素は次のいずれかである必要があります。
- ランタイム クラス、または
- IInspectable。
IInspectable の場合、要素自体がランタイム クラスでない場合、それらの要素は、IInspectable との間でボックス化およびボックス化解除できる種類である必要があります。 つまり、Windows ランタイム型である必要があります (IInspectable への値のボックス化とボックス化解除を参照)。
このケース スタディでは、 Scenario をランタイム クラスにしませんでした。 しかし、これは依然として妥当な選択肢です。 また、ご自身の移植作業においては、ランタイム クラスが間違いなく最適な選択肢となる場合もあります。 たとえば、要素の型を 監視可能 にする必要がある場合 (XAML コントロールを参照し 、C++/WinRT プロパティにバインドする)、または要素に他の理由でメソッドが必要であり、単なるデータ メンバーのセット以上の場合です。
このウォークスルーでは、Scenario 型にランタイム クラスを使用しないため、ボクシングについて考える必要があります。
シナリオを通常の C++ structにした場合、ボックス化することはできません。 しかし、 シナリオを IDL の struct として宣言したので、ボックス化 できます 。
残る選択肢は、Scenario を事前にボックス化するか、ItemsSource に割り当てる直前まで待ってジャストインタイム方式でボックス化するかです。 これら 2 つのオプションに関するいくつかの考慮事項を次に示します。
- あらかじめボックス化を行う。 このオプションでは、データ メンバーは UI にそのまま割り当てられる IInspectable のコレクションです。 初期化時に、 シナリオ オブジェクトをそのデータ メンバーにボックス化します。 そのコレクションのコピーは 1 つだけ必要ですが、フィールドを読み取る必要があるたびに要素のボックス化を解除する必要があります。
- ジャスト イン タイムのボックス化。 このオプションでは、データ メンバーは シナリオのコレクションです。 UI に割り当てる時が来たら、データ メンバーの Scenario オブジェクトを 新しい IInspectable コレクションにボックス化します。 ボックス化を解除せずにデータ メンバー内の要素のフィールドを読み取ることができますが、コレクションの 2 つのコピーが必要です。
ご覧のとおり、このような小規模なコレクションでは、長所と短所を比べると結局プラスマイナスゼロです。 そのため、このケース スタディでは、Just-In-Time オプションを使用します。
シナリオ メンバーは MainPage のフィールドであり、SampleConfiguration.csで定義および初期化されます。 また 、Scenarios は MainPage の読み取り専用プロパティであり、 MainPage.xaml.cs で定義されています ( シナリオ フィールドを 単に返すために実装されます)。 C++/WinRT プロジェクトでも同様のことを行います。ただし、2 つのメンバーを静的にします (アプリケーション全体で必要なインスタンスは 1 つだけであるため、クラス インスタンスを必要とせずにアクセスできるようにします)。 また、それぞれ scenariosInner と scenarios という名前 を付けます。
scenariosInner をMainPage.hで宣言します。 また、静的ストレージ期間があるため、 .cpp ファイル (この場合はSampleConfiguration.cpp) で定義/初期化します。
以下の一覧と一致するように MainPage.h と SampleConfiguration.cpp を編集します。
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
static Windows::Foundation::Collections::IVector<Scenario> scenarios() { return scenariosInner; }
...
private:
static winrt::Windows::Foundation::Collections::IVector<Scenario> scenariosInner;
...
};
// SampleConfiguration.cpp
...
using namespace Windows::Foundation::Collections;
...
IVector<Scenario> implementation::MainPage::scenariosInner = winrt::single_threaded_observable_vector<Scenario>(
{
Scenario{ L"Copy and paste text", xaml_typename<SDKTemplate::CopyText>() },
Scenario{ L"Copy and paste an image", xaml_typename<SDKTemplate::CopyImage>() },
Scenario{ L"Copy and paste files", xaml_typename<SDKTemplate::CopyFiles>() },
Scenario{ L"History and roaming", xaml_typename<SDKTemplate::HistoryAndRoaming>() },
Scenario{ L"Other Clipboard operations", xaml_typename<SDKTemplate::OtherScenarios>() },
});
また、MainPage.cpp のから既存の関数本体を削除してください。これは、ヘッダー ファイルでそのメソッドを定義しているためです。
ご覧のように、SampleConfiguration.cppでは、winrt::single_threaded_observable_vector という名前の C++/WinRT ヘルパー関数を呼び出すことによって、静的データ メンバー のシナリオInner を初期化します。 この関数は、新しいWindows ランタイムコレクション オブジェクトを作成し、IObservableVector インターフェイスとして返します。 このサンプルでは、コレクションは 監視できないため (初期化後に要素を追加または削除しないため、必要ありません)、 代わりに winrt::single_threaded_vector を呼び出すように選択できました。 この関数は、 コレクションを IVector インターフェイスとして返します。
コレクションとコレクションへのバインドの詳細については、 XAML 項目コントロール、C++/WinRT コレクションへのバインド、および C++/WinRTを使用したコレクションに関するページを参照してください。
追加した初期化コードは、プロジェクトにまだ含まれていない型 ( winrt::SDKTemplate::CopyText など) を参照します。 これを解決するために、先に進み、新しい 5 つの空の XAML ページをプロジェクトに追加しましょう。
5 つの新しい空の XAML ページを追加する
新しい Visual C++>空白ページ (C++/WinRT) 項目をプロジェクトに追加します (空白ページ (C++/WinRT) 項目テンプレートではなく、空白ページ (C++/WinRT) 項目テンプレートであることを確認してください)。
CopyText と名前を付けます。 新しい XAML ページは、 SDKTemplate 名前空間内で定義されます。これは、必要な内容です。
上記のプロセスをもう 4 回繰り返し、XAML ページに CopyImage、 CopyFiles、 HistoryAndRoaming、 OtherScenariosという名前を付けます。
必要に応じて、もう一度ビルドできるようになります。
NotifyUser
C# プロジェクトでは、 MainPage.NotifyUser メソッドの実装が MainPage.xaml.csにあります。
MainPage.NotifyUser は MainPage.UpdateStatus に依存しており、そのメソッドには、まだ移植していない XAML 要素への依存関係があります。 ここでは、C++/WinRT プロジェクトで UpdateStatus メソッドをスタブアウトし、後で移植します。
移植する必要がある関連する C# コードを次に示します。
// MainPage.xaml.cs
...
public void NotifyUser(string strMessage, NotifyType type)
if (Dispatcher.HasThreadAccess)
{
UpdateStatus(strMessage, type);
}
else
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateStatus(strMessage, type));
}
private void UpdateStatus(string strMessage, NotifyType type) { ... }{
...
NotifyUser は 、UI の更新をメイン スレッドにディスパッチします。 WinUI 3 では、これには古い CoreDispatcher の代わりに Microsoft.UI.Dispatching.DispatcherQueue を使用します。 C++/WinRT では、WindowsまたはMicrosoft名前空間の型を使用する場合は常に、対応する C++/WinRT 名前空間ヘッダー ファイルを含める必要があります (詳細については、「C++/WinRT の概要」を参照してください)。 この場合、次のコード一覧に示すように、ヘッダーは winrt/Microsoft.UI.Dispatching.hされ、 pch.hに含められます。
UpdateStatus はプライベートです。 そのため、 MainPage 実装型でプライベート メソッドを作成します。 UpdateStatus はランタイム クラスで呼び出される予定がないため、IDL では宣言しません。
MainPage.NotifyUser を移植し、MainPage.UpdateStatus をスタブアウトした後、これは C++/WinRT プロジェクトに含まれるものです。 このコード一覧の後、いくつかの詳細を確認します。
// pch.h
...
#include <winrt/Microsoft.UI.Dispatching.h>
...
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
void NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
private:
void UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type);
...
};
// MainPage.cpp
...
void MainPage::NotifyUser(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
if (DispatcherQueue().HasThreadAccess())
{
UpdateStatus(strMessage, type);
}
else
{
DispatcherQueue().TryEnqueue([strMessage, type, this]()
{
UpdateStatus(strMessage, type);
});
}
}
void MainPage::UpdateStatus(hstring const& strMessage, SDKTemplate::NotifyType const& type)
{
throw hresult_not_implemented();
}
...
C# では、ドット表記を使用して、入れ子になったプロパティ にドット を付けることができます。 そのため、C# MainPage 型は、構文を使用して独自の Dispatcher プロパティにアクセスできます。 また、C# では、などの構文を使用して、その値にさらにDispatcher.HasThreadAccessを付けることができます。 C++/WinRT では、プロパティはアクセサー関数として実装されるため、構文は関数呼び出しごとにかっこを追加する点でのみ異なります。
| C# | C++/WinRT |
|---|---|
Dispatcher.HasThreadAccess |
DispatcherQueue().HasThreadAccess() |
C# バージョンの NotifyUser が Dispatcher.RunAsync を呼び出すと、WinUI 3 と同等の場合 は DispatcherQueue.TryEnqueue が使用されます。 C++/WinRT バージョンでは、コールバック デリゲートがラムダ関数として実装されます。 C++/WinRT では、使用する 2 つのパラメーターと、(メンバー関数を呼び出すので) ポインターをthisします。 ラムダとしてのデリゲートの実装とコード例の詳細については、「 C++/WinRT でデリゲートを使用してイベントを処理する」を参照してください。
残りの MainPage メンバーを 実装する
MainPage のメンバー (とMainPage.xaml.cs全体で実装SampleConfiguration.cs) の完全なリストを作成して、これまでに移植したメンバーと、まだ実行していないメンバーを確認できるようにします。
| メンバー | アクセス | Status |
|---|---|---|
| MainPage コンストラクター | public |
移植 |
| 現在の プロパティ | public |
移植済み |
| FEATURE_NAME プロパティ | public |
移植 |
| IsClipboardContentChangedEnabled プロパティ | public |
未開始 |
| Scenarios プロパティ | public |
移植 |
| BuildClipboardFormatsOutputString メソッド | public |
未開始 |
| DisplayToast メソッド | public |
未開始 |
| EnableClipboardContentChangedNotifications メソッド | public |
未開始 |
| NotifyUser メソッド | public |
移植 |
| OnNavigatedTo メソッド | protected |
未開始 |
| isApplicationWindowActive フィールド | private |
未開始 |
| needToPrintClipboardFormat フィールド | private |
未開始 |
| scenarios フィールド | private |
移植 |
| Button_Click メソッド | private |
未開始 |
| DisplayChangedFormats メソッド | private |
未開始 |
| Footer_Click メソッド | private |
未開始 |
| HandleClipboardChanged メソッド | private |
未開始 |
| OnClipboardChanged メソッド | private |
未開始 |
| OnWindowActivated メソッド | private |
未開始 |
| ScenarioControl_SelectionChanged メソッド | private |
未開始 |
| UpdateStatus メソッド | private |
スタブアウト |
次のいくつかのサブセクションでは、未報告のメンバーについて説明します。
Note
XAML マークアップ ( MainPage.xaml) の UI 要素に対するソース コード内の参照が随時見つかるでしょう。 これらの参照に進むにつれて、XAML に単純なプレースホルダー要素を追加することで、それらを一時的に回避します。 そうすることで、プロジェクトは各サブセクションの後に引き続きビルドされます。 代わりに、の内容MainPage.xamlを C# プロジェクトから C++/WinRT プロジェクトにコピーして、参照を解決することもできます。 しかし、そうすると、ピットストップに来て再びビルドできるようになるまでには長い時間がかかります(したがって、途中で入力ミスやその他のエラーが隠される可能性があります)。
MainPage クラスの命令型コードの移植が完了したら、XAML ファイルの内容をコピーし、プロジェクトがまだビルドされることを確信します。
IsClipboardContentChangedEnabled
これは、既定で false に設定される get-set C# プロパティです。
これは MainPage のメンバーであり、SampleConfiguration.csで定義されています。
C++/WinRT の場合、アクセサー関数、ミューテーター関数、およびバッキング データ メンバーがフィールドとして必要になります。
IsClipboardContentChangedEnabled は MainPage 自体の状態ではなく、サンプル内のいずれかのシナリオの状態を表しているため、SampleState という新しいユーティリティの種類に新しいメンバーを作成します。 また、 SampleConfiguration.cpp ソース コード ファイルでこれを実装し、メンバーを static します (アプリケーション全体で必要なインスタンスは 1 つだけであるため、クラス インスタンスを必要とせずにアクセスできるようにします)。
C++/WinRT プロジェクトにSampleConfiguration.cppを添付するには、という名前の新しい >>SampleConfiguration.h 項目を追加します。 以下の一覧と一致するように SampleConfiguration.h と SampleConfiguration.cpp を編集します。
// SampleConfiguration.h
#pragma once
#include "pch.h"
namespace winrt::SDKTemplate
{
struct SampleState
{
static bool IsClipboardContentChangedEnabled();
static void IsClipboardContentChangedEnabled(bool checked);
private:
static bool isClipboardContentChangedEnabled;
};
}
// SampleConfiguration.cpp
...
#include "SampleConfiguration.h"
...
bool SampleState::isClipboardContentChangedEnabled = false;
...
bool SampleState::IsClipboardContentChangedEnabled()
{
return isClipboardContentChangedEnabled;
}
void SampleState::IsClipboardContentChangedEnabled(bool checked)
{
if (isClipboardContentChangedEnabled != checked)
{
isClipboardContentChangedEnabled = checked;
}
}
ここでも、 static ストレージを持つフィールド ( SampleState::isClipboardContentChangedEnabled など) をアプリケーションで 1 回定義する必要があり、 .cpp ファイルが適切な場所です (この場合SampleConfiguration.cpp )。
BuildClipboardFormatsOutputString
このメソッドは MainPage のパブリック メンバーであり、 SampleConfiguration.csで定義されています。
// SampleConfiguration.cs
...
public string BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
StringBuilder output = new StringBuilder();
if (clipboardContent != null && clipboardContent.AvailableFormats.Count > 0)
{
output.Append("Available formats in the clipboard:");
foreach (var format in clipboardContent.AvailableFormats)
{
output.Append(Environment.NewLine + " * " + format);
}
}
else
{
output.Append("The clipboard is empty");
}
return output.ToString();
}
...
C++/WinRT では、 BuildClipboardFormatsOutputString を SampleState のパブリック静的メソッドにします。 インスタンス メンバーにアクセスしないため、 static にすることができます。
C++/WinRT で Clipboard 型と DataPackageView 型を使用するには、C++/WinRT Windows名前空間ヘッダー ファイルをwinrt/Windows.ApplicationModel.DataTransfer.h含める必要があります。
C# では、 DataPackageView.AvailableFormats プロパティは IReadOnlyList であるため、その Count プロパティにアクセスできます。 C++/WinRT では、DataPackageView::AvailableFormats アクセサー関数は、呼び出すことができる Size アクセサー関数を持つ IVectorView を返します。
C# System.Text.StringBuilder 型の使用を移植するために、標準の C++ 型 std::wostringstream を使用します。 この型はワイド文字列の出力ストリームです (使用するには、 sstream ヘッダー ファイルを含める必要があります)。
StringBuilder と同様に Append メソッドを使用する代わりに、wostringstream などの出力ストリームで<< () を使用します。 詳細については、 iostream プログラミングと C++/WinRT 文字列の書式設定に関するページを参照してください。
C# コードは、 キーワードを使用して new を構築します。 C# では、オブジェクトは既定で参照型であり、 newを使用してヒープで宣言されます。 最新の標準 C++ では、オブジェクトは既定で値型であり、( newを使用せずに) スタックで宣言されます。 そのため、 StringBuilder output = new StringBuilder(); を単に std::wostringstream output;として C++/WinRT に移植します。
C# var キーワードは、型を推論するようにコンパイラに求めます。
varを C++/WinRT のautoに移植します。 しかし、C++/WinRT では、(コピーを避けるために) 推論された (または推定された) 型への 参照 が必要で、推定された型への左辺値参照を auto&で表す場合があります。
lvalue で初期化される場合でも rvalue で初期化される場合でも正しくバインドされる、特別な種類の参照が必要になることもあります。 そして、あなたは auto&&で表現します。 これは、次の移植されたコードの for ループで使用されている形式です。
左辺値と右辺値の概要については、値のカテゴリとその参照を参照してください。
pch.h、SampleConfiguration.h、およびSampleConfiguration.cppを以下の一覧と一致するように編集します。
// pch.h
...
#include <sstream>
#include "winrt/Windows.ApplicationModel.DataTransfer.h"
...
// SampleConfiguration.h
...
struct SampleState
{
static hstring BuildClipboardFormatsOutputString();
...
}
...
// SampleConfiguration.cpp
...
using namespace Windows::ApplicationModel::DataTransfer;
...
hstring SampleState::BuildClipboardFormatsOutputString()
{
DataPackageView clipboardContent{ Clipboard::GetContent() };
std::wostringstream output;
if (clipboardContent && clipboardContent.AvailableFormats().Size() > 0)
{
output << L"Available formats in the clipboard:";
for (auto&& format : clipboardContent.AvailableFormats())
{
output << std::endl << L" * " << std::wstring_view(format);
}
}
else
{
output << L"The clipboard is empty";
}
return hstring{ output.str() };
}
Note
コード DataPackageView clipboardContent{ Clipboard::GetContent() }; 行の構文では、 統一初期化と呼ばれる最新の標準 C++ の機能が使用され、 = 記号の代わりに中かっこが使用されます。 その構文により、割り当てではなく初期化が行われていることが明確になります。 割り当てのように 見える 構文の形式 (実際にはそうではない) を希望する場合は、上記の構文を同等の DataPackageView clipboardContent = Clipboard::GetContent();に置き換えることができます。 ただし、両方の方法で初期化を表現する方法に慣れるのは良い考えです。これは、発生したコードで両方が頻繁に使用される可能性があるためです。
DisplayToast
DisplayToast は C# MainPage クラスのパブリック静的メソッドであり、 SampleConfiguration.csで定義されています。 C++/WinRT では、 SampleState のパブリック静的メソッドにします。
このメソッドの移植に関連するほとんどの詳細と手法が既に見つかりました。 注意すべき新しい項目の 1 つは、C# 逐語的文字列リテラル (@) を標準の C++ 生文字列リテラル (LR) に移植することです。
また、C++/WinRT で ToastNotification 型と XmlDocument 型を参照する場合は、名前空間名で修飾するか、 SampleConfiguration.cpp を編集して、次の例のような using namespace ディレクティブを追加できます。
using namespace Windows::UI::Notifications;
XmlDocument 型を参照するときと、他のWindows ランタイム型を参照する場合も同じ選択肢があります。
これらの項目とは別に、前に行ったのと同じガイダンスに従って、次の手順を実行します。
-
SampleConfiguration.hでメソッドを宣言し、SampleConfiguration.cppで定義します。 -
pch.hを編集して、必要な C++/WinRT Windows名前空間ヘッダー ファイルを含めます。 - ヒープ上ではなく、スタック上に C++/WinRT オブジェクトを構築します。
- プロパティ取得アクセサーの呼び出しを関数呼び出し構文 (
()) に置き換えます。
コンパイラ/リンカー エラーの非常に一般的な原因は、必要な C++/WinRT Windows名前空間ヘッダー ファイルを含め忘れることです。 考えられるエラーの詳細については、 C3779 を参照してください。コンパイラが "consume_Something: 'auto' を返す関数を定義する前に使用できません" というエラーが発生するのはなぜですか?
チュートリアルとポート DisplayToast に従う場合は、ダウンロードした クリップボード サンプル ソース コードの ZIP の C++/WinRT バージョンのコードと結果を比較できます。
クリップボードの内容変更通知を有効にする
EnableClipboardContentChangedNotifications は C# MainPage クラスのパブリック静的メソッドであり、 SampleConfiguration.csで定義されています。
// SampleConfiguration.cs
...
public bool EnableClipboardContentChangedNotifications(bool enable)
{
if (IsClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled = enable;
if (enable)
{
Clipboard.ContentChanged += OnClipboardChanged;
Window.Current.Activated += OnWindowActivated;
}
else
{
Clipboard.ContentChanged -= OnClipboardChanged;
Window.Current.Activated -= OnWindowActivated;
}
return true;
}
...
private void OnClipboardChanged(object sender, object e) { ... }
private void OnWindowActivated(object sender, WindowActivatedEventArgs e) { ... }
...
C++/WinRT では、 SampleState のパブリック静的メソッドにします。
C# では、 += および -= 演算子の構文を使用して、イベント処理デリゲートを登録および取り消します。 C++/WinRT では、「 C++/WinRT でデリゲートを使用してイベントを処理する」の説明に従って、デリゲートを登録または取り消すための構文オプションがいくつかあります。 しかし、一般的には、そのイベント名を冠した一対の関数を呼び出して登録と解除を行います。 登録するには、デリゲートを登録関数に渡し、戻り値 ( winrt::event_token) で失効トークンを取得します。 取り消すには、そのトークンを失効関数に渡します。 この場合、ハンドャーは静的であり、(次のコード一覧でわかるように) 関数呼び出しの構文は簡単です。
C# では、バックグラウンドで同様のトークン が実際に 使用されます。 しかし、言語はその詳細を暗黙的にします。 C++/WinRT では、それが明示されます。
オブジェクトの種類は、C# イベント ハンドラーのシグネチャに表示されます。 C# 言語では、オブジェクトは .NET System.Object 型のエイリアスです。 C++/WinRT での同等の機能は winrt::Windows::Foundation::IInspectable です。 そのため、C++/WinRT イベント ハンドラーに IInspectable が表示されます。
以下の一覧と一致するように SampleConfiguration.h と SampleConfiguration.cpp を編集します。
// SampleConfiguration.h
...
static bool EnableClipboardContentChangedNotifications(bool enable);
...
private:
...
static event_token clipboardContentChangedToken;
static event_token activatedToken;
static void OnClipboardChanged(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
static void OnWindowActivated(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::WindowActivatedEventArgs const& e);
...
// SampleConfiguration.cpp
...
using namespace Windows::Foundation;
using namespace Microsoft::UI;
using namespace Microsoft::UI::Xaml;
...
event_token SampleState::clipboardContentChangedToken;
event_token SampleState::activatedToken;
...
bool SampleState::EnableClipboardContentChangedNotifications(bool enable)
{
if (isClipboardContentChangedEnabled == enable)
{
return false;
}
IsClipboardContentChangedEnabled(enable);
if (enable)
{
clipboardContentChangedToken = Clipboard::ContentChanged(OnClipboardChanged);
activatedToken = Window::Current().Activated(OnWindowActivated);
}
else
{
Clipboard::ContentChanged(clipboardContentChangedToken);
Window::Current().Activated(activatedToken);
}
return true;
}
void SampleState::OnClipboardChanged(IInspectable const&, IInspectable const&){}
void SampleState::OnWindowActivated(IInspectable const&, WindowActivatedEventArgs const& e){}
ここでは、イベント処理デリゲート自体 (OnClipboardChanged と OnWindowActivated) をスタブのままにします。 これらは既に移植するメンバーの一覧に含まれているため、後のサブセクションで説明します。
OnNavigatedTo
OnNavigatedTo は C# MainPage クラスの保護されたメソッドであり、 MainPage.xaml.csで定義されています。 こちらが、それが参照している XAML の ListBox と併せたものです。
<!-- MainPage.xaml -->
...
<ListBox x:Name="ScenarioControl" ... />
...
// MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Populate the scenario list from the SampleConfiguration.cs file
var itemCollection = new List<Scenario>();
int i = 1;
foreach (Scenario s in scenarios)
{
itemCollection.Add(new Scenario { Title = $"{i++}) {s.Title}", ClassType = s.ClassType });
}
ScenarioControl.ItemsSource = itemCollection;
if (Window.Current.Bounds.Width < 640)
{
ScenarioControl.SelectedIndex = -1;
}
else
{
ScenarioControl.SelectedIndex = 0;
}
}
これは重要で興味深い手法です。というのも、ここで Scenario オブジェクトのコレクションが UI に割り当てられるからです。 C# コードは、System.Collections.Generic.List of Scenario オブジェクトをビルドし、ListBox (項目コントロール) の ItemsSource プロパティに割り当てます。 また、C# では、 文字列補間 を使用して各 Scenario オブジェクトのタイトルをビルドします ( $ 特殊文字の使用に注意してください)。
C++/WinRT では、 OnNavigatedTo を MainPage のパブリック メソッドにします。 また、ビルドが成功するように、スタブ ListBox 要素を XAML に追加します。 コード一覧の後、いくつかの詳細を確認します。
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<ListBox x:Name="ScenarioControl" />
</StackPanel>
...
// MainPage.h
...
void OnNavigatedTo(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
...
// MainPage.cpp
...
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::UI::Xaml::Navigation;
...
void MainPage::OnNavigatedTo(NavigationEventArgs const& /* e */)
{
auto itemCollection = winrt::single_threaded_observable_vector<IInspectable>();
int i = 1;
for (auto s : MainPage::scenarios())
{
s.Title = winrt::to_hstring(i++) + L") " + s.Title;
itemCollection.Append(winrt::box_value(s));
}
ScenarioControl().ItemsSource(itemCollection);
if (Window::Current().Bounds().Width < 640)
{
ScenarioControl().SelectedIndex(-1);
}
else
{
ScenarioControl().SelectedIndex(0);
}
}
...
ここでも winrt::single_threaded_observable_vector 関数を呼び出していますが、今回 は IInspectable のコレクションを作成します。 それは、Scenario オブジェクトを必要時にボックス化するという私たちの判断の一環でした。
また、ここでは C# で文字列補間を使用する代わりに、to_hstring関数と winrt::hstring の連結演算子の組み合わせを使用します。
isApplicationWindowActive
C# では、isApplicationWindowActive は bool クラスに属する単純なプライベート フィールドであり、SampleConfiguration.csで定義されています。 既定値は false です。 C++/WinRT では、同じ既定値を使用して、ファイルとSampleConfiguration.hファイル内の SampleConfiguration.cpp のプライベート静的フィールドにします (既に説明した理由から)。
静的フィールドを宣言、定義、初期化する方法については、既に説明しました。 リフレッシャーについては、 isClipboardContentChangedEnabled フィールドで行ったことを振り返り、 isApplicationWindowActive で同じ操作を行います。
needToPrintClipboardFormat
isApplicationWindowActive と同じパターン (この直前の見出しを参照)。
Button_Click
Button_Click は C# MainPage クラスのプライベート (イベント処理) メソッドであり、 MainPage.xaml.csで定義されています。 ここでは、参照する XAML SplitView と、それを登録する ToggleButton と共に示します。
<!-- MainPage.xaml -->
...
<SplitView x:Name="Splitter" ... />
...
<ToggleButton Click="Button_Click" .../>
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Splitter.IsPaneOpen = !Splitter.IsPaneOpen;
}
C++/WinRT に移植された同等の機能を次に示します。 C++/WinRT バージョンでは、イベント ハンドラーはpublicされることに注意してください (ご覧のとおり、宣言のprivate:宣言します)。 これは、このような XAML マークアップに登録されているイベント ハンドラーは、XAML マークアップがアクセスするために C++/WinRT で public する必要があるためです。 一方、イベント ハンドラーを命令型コードに登録する場合 (前の MainPage::EnableClipboardContentChangedNotifications で行ったように)、イベント ハンドラーを publicする必要はありません。
<!-- MainPage.xaml -->
...
<StackPanel ...>
...
<SplitView x:Name="Splitter" />
</StackPanel>
...
// MainPage.h
...
void Button_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
void MainPage::Button_Click(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* e */)
{
Splitter().IsPaneOpen(!Splitter().IsPaneOpen());
}
DisplayChangedFormats
C# では、 DisplayChangedFormats は MainPage クラスに属するプライベート メソッドであり、 SampleConfiguration.csで定義されています。
private void DisplayChangedFormats()
{
string output = "Clipboard content has changed!" + Environment.NewLine;
output += BuildClipboardFormatsOutputString();
NotifyUser(output, NotifyType.StatusMessage);
}
C++/WinRT では、ファイルとSampleConfiguration.h ファイル内の SampleConfiguration.cpp (インスタンス メンバーにはアクセスしません) のプライベート静的フィールドにします。 このメソッドの C# コードでは System.Text.StringBuilder を使用していません。しかし、文字列の書式設定を十分に行っているため、C++/WinRT バージョンでは std::wostringstream を使用するのに適した箇所でもあります。
C# コードで使用される静的 な System.Environment.NewLine プロパティの代わりに、標準の C++ std::endl (改行文字) を出力ストリームに挿入します。
// SampleConfiguration.h
...
private:
static void DisplayChangedFormats();
...
// SampleConfiguration.cpp
void SampleState::DisplayChangedFormats()
{
std::wostringstream output;
output << L"Clipboard content has changed!" << std::endl;
output << BuildClipboardFormatsOutputString().c_str();
MainPage::Current().NotifyUser(output.str(), NotifyType::StatusMessage);
}
上記の C++/WinRT バージョンの設計には、小さな非効率性があります。 まず、 std::wostringstream を作成します。 ただし、 BuildClipboardFormatsOutputString メソッド (前に移植しました) も呼び出します。 そのメソッドは、独自の std::wostringstream を作成します。 そして、ストリームを winrt::hstring に変換し、それを返します。 hstring::c_str 関数を呼び出して、返された hstring を C スタイルの文字列に戻し、ストリームに挿入します。 メソッドが文字列を直接挿入できるように、 std::wostringstream を 1 つだけ作成し、その周りに (参照を) 渡す方が効率的です。
これは、C++/WinRT バージョンの クリップボード サンプル ソース コード (ダウンロードした ZIP) で行います。 そのソース コードには、 SampleState::AddClipboardFormatsOutputString という名前の新しいプライベート静的メソッドがあり、出力ストリームへの参照を受け取って操作します。 次に、 SampleState::D isplayChangedFormats メソッドと SampleState::BuildClipboardFormatsOutputString メソッドがリファクタリングされ、その新しいメソッドが呼び出されます。 このトピックのコード 一覧と機能的には同等ですが、より効率的です。
Footer_Click
Footer_Click は、C# MainPage クラスに属する非同期イベント ハンドラーであり、 MainPage.xaml.csで定義されています。 以下のコード一覧は、ダウンロードしたソース コードのメソッドと機能的に同等です。 しかし、ここでは、1 行から 4 行にアンパックして、何が行っているかを簡単に確認できるようにし、その結果、移植する方法を説明しました。
async void Footer_Click(object sender, RoutedEventArgs e)
{
var hyperlinkButton = (HyperlinkButton)sender;
string tagUrl = hyperlinkButton.Tag.ToString();
Uri uri = new Uri(tagUrl);
await Windows.System.Launcher.LaunchUriAsync(uri);
}
技術的には、メソッドは非同期ですが、 awaitの後には何も行わないので、 await (または async キーワード) は必要ありません。 Visual Studioの IntelliSense メッセージを回避するために使用される可能性があります。
同等の C++/WinRT メソッドも非同期になります ( Launcher.LaunchUriAsync が呼び出されるため)。 ただし、 co_awaitしたり、非同期オブジェクトを返したりする必要はありません。
co_awaitオブジェクトと非同期オブジェクトの詳細については、「C++/WinRT を使用したコンカレンシーと非同期操作」を参照してください。
次に、メソッドの実行内容について説明します。 これは HyperlinkButton の Click イベントのイベント ハンドラーであるため、sender という名前のオブジェクトは実際には HyperlinkButton です。 したがって、型変換は安全です (代わりに、この変換を sender as HyperlinkButtonとして表現することもできます)。 次に、 Tag プロパティの値を取得します (C# プロジェクトの XAML マークアップを見ると、Web URL を表す文字列に設定されていることがわかります)。
FrameworkElement.Tag プロパティ (HyperlinkButton は FrameworkElement) はオブジェクト型ですが、C# では Object.ToString で文字列化できます。 結果の文字列から、 Uri オブジェクトを構築します。 最後に (シェルの助けを借りて) ブラウザーを起動し、URL に移動します。
C++/WinRT に移植されたメソッドを次に示します (ここでも、わかりやすくするために拡張されています)。その後、詳細について説明します。
// pch.h
...
#include "winrt/Windows.System.h"
...
// MainPage.h
...
void Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
...
// MainPage.cpp
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::UI::Xaml::Controls;
...
void MainPage::Footer_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const&)
{
auto hyperlinkButton{ sender.as<HyperlinkButton>() };
hstring tagUrl{ winrt::unbox_value<hstring>(hyperlinkButton.Tag()) };
Uri uri{ tagUrl };
Windows::System::Launcher::LaunchUriAsync(uri);
}
常に、イベント ハンドラーを publicします。
送信者オブジェクトの as 関数を使用して HyperlinkButton に変換します。 C++/WinRT では、Tag プロパティは IInspectable(Object に相当)です。 ただし、IInspectable に Tostring はありません。 代わりに、 IInspectable をスカラー値 (この場合は文字列) にボックス化解除する必要があります。 ここでも、ボックス化とボックス化解除の詳細については、「 IInspectable への値のボックス化とボックス化解除」を参照してください。
最後の 2 行は前に見た移植パターンを繰り返し、C# のバージョンをかなりエコーします。
HandleClipboardChanged
このメソッドの移植には新しい作業はありません。 ダウンロードした クリップボード サンプル ソース コードの ZIP で、C# と C++/WinRT のバージョンを比較できます。
OnClipboardChanged と OnWindowActivated
これまでのところ、これら 2 つのイベント ハンドラーには空のスタブしか用意されていません。 しかし、移植は簡単で、議論する新しいものは何も発生しません。
ScenarioControl_SelectionChanged
これは、C# MainPage クラスに属し、 MainPage.xaml.csで定義されているもう 1 つのプライベート イベント ハンドラーです。 C++/WinRT では、公開し、 MainPage.h と MainPage.cppに実装します。
このメソッドでは、 MainPage::navigating (プライベートブール型フィールド) が falseに初期化されている必要があります。 また、ScenarioFrame という名前のMainPage.xamlがに必要です。 しかし、これらの詳細とは別に、このメソッドを移植しても新しい手法は明らかになりません。
手動で移植する代わりに、ダウンロードした クリップボード サンプル ソース コードの ZIP で C++/WinRT バージョンからコードをコピーする場合は、 MainPage::NavigateTo がそこで使用されます。 ここでは、 NavigateTo の内容を ScenarioControl_SelectionChangedにリファクタリングするだけです。
UpdateStatus
MainPage.UpdateStatus は現時点ではスタブしかありません。 その実装の移植については、改めて取り上げても、おおむね既出の内容をなぞることになります。 新しい点の 1 つは、C# では 文字列 を String.Empty と比較できる点です。C++/WinRT では、代わりに winrt::hstring::empty 関数を呼び出します。 もう 1 つは、 nullptr が C# の nullと同等の標準的な C++ であるということです。
ポートの残りの部分は、既に説明した手法を使用して実行できます。 このメソッドの移植されたバージョンがコンパイルされる前に行う必要がある操作の種類の一覧を次に示します。
-
MainPage.xamlするには、「StatusBorder」という名前の Border を追加します。 -
MainPage.xamlするには、StatusBlock という名前の TextBlock を追加します。 -
MainPage.xamlするには、StatusPanel という名前の StackPanel を追加します。 -
pch.hするには、#include "winrt/Microsoft.UI.Xaml.Media.h"を追加します。 -
pch.hするには、#include "winrt/Microsoft.UI.Xaml.Automation.Peers.h"を追加します。 -
MainPage.cppを追加using namespace winrt::Microsoft::UI::Xaml::Media;。 -
MainPage.cppを追加using namespace winrt::Microsoft::UI::Xaml::Automation::Peers;。
MainPage の移植を完了するために必要な XAML とスタイルをコピーする
XAML の場合、理想的なケースは、C# と C++/WinRT プロジェクトで 同じ XAML マークアップを使用できることです。 また、クリップボードのサンプルもその 1 つです。
Styles.xaml ファイルでは、クリップボード サンプルにスタイルの XAML ResourceDictionary があり、アプリケーションの UI 全体のボタン、メニュー、およびその他の UI 要素に適用されます。
Styles.xaml ページはApp.xamlにマージされます。 その後、UI の標準的な MainPage.xaml の開始点があります。これについては、既に簡単に説明しました。 これで、プロジェクトの C++/WinRT バージョンで、変更せずに、これら 3 つの .xaml ファイルを再利用できるようになりました。
アセット ファイルと同様に、複数のバージョンのアプリケーションから同じ共有 XAML ファイルを参照することもできます。 このチュートリアルでは、わかりやすくするために、ファイルを C++/WinRT プロジェクトにコピーし、そのように追加します。
\Clipboard_sample\SharedContent\xaml フォルダーに移動し、App.xamlとMainPage.xamlを選択してコピーし、その 2 つのファイルを C++/WinRT プロジェクトの\Clipboard\Clipboard フォルダーに貼り付け、メッセージが表示されたらファイルを置き換えることを選択します。
Visual Studioの C++/WinRT プロジェクトで、[すべてのファイルを表示] をクリックしてオンに切り替えます。 次に、プロジェクト ノードのすぐ下に新しいフォルダーを追加し、 Styles名前を付けます。 エクスプローラーで、 \Clipboard_sample\SharedContent\xaml フォルダーに移動し、 Styles.xamlを選択してコピーし、先ほど作成した \Clipboard\Clipboard\Styles フォルダーに貼り付けます。 C++/WinRT プロジェクトのソリューション エクスプローラーに戻り、Styles フォルダー >Add>Existing item... を右クリックし、\Clipboard\Clipboard\Stylesに移動します。 ファイル ピッカーで Styles を選択し、[ 追加] をクリックします。
プロジェクト ノードのすぐ下にある C++/WinRT プロジェクトに新しいフォルダーを追加し、 Stylesという名前を付けます。
\Clipboard_sample\SharedContent\xaml フォルダーに移動し、Styles.xamlを選択してコピーし、C++/WinRT プロジェクトの\Clipboard\Clipboard\Styles フォルダーに貼り付けます。
Styles フォルダー (C++/WinRT プロジェクトのソリューション エクスプローラー) >Add>Existing item... を右クリックし、\Clipboard\Clipboard\Stylesに移動します。 ファイル ピッカーで Styles を選択し、[ 追加] をクリックします。
[ すべてのファイルを表示 ] をもう一度クリックしてオフに切り替えます。
MainPage の移植が完了しました。手順に従っている場合は、C++/WinRT プロジェクトがビルドされて実行されます。
.idl ファイルを統合する
クリップボード サンプルには、UI の標準的な MainPage.xaml の開始点に加えて、シナリオ固有の XAML ページが他に 5 つあり、対応する分離コード ファイルも含まれています。 プロジェクトの C++/WinRT バージョンでは、これらのすべてのページの実際の XAML マークアップを変更せずに再利用します。 この後のいくつかの主要なセクションで、コードビハインドを移植する方法を見ていきます。 しかし、その前に、IDL について話しましょう。
ランタイム クラスの IDL を 1 つの IDL ファイルに統合することには価値があります。 その値の詳細については、「 ランタイム クラスを Midl ファイル (.idl) に分解する」を参照してください。 次に、CopyFiles.idl、CopyImage.idl、CopyText.idl、HistoryAndRoaming.idl、OtherScenarios.idl の内容を、その IDL を Project.idl という名前の単一のファイルに移動することで統合します(その後、元のファイルを削除します)。
これを行っている間に、自動生成されたダミー プロパティ (Int32 MyProperty;とその実装) も、これら 5 つの XAML ページの種類から削除しましょう。
まず、新しい Midl ファイル (.idl) 項目を C++/WinRT プロジェクトに追加します。
Project.idl と名前を付けます。
Project.idl のコンテンツ全体を次のコードに置き換えます。
// Project.idl
namespace SDKTemplate
{
[default_interface]
runtimeclass CopyFiles : Microsoft.UI.Xaml.Controls.Page
{
CopyFiles();
}
[default_interface]
runtimeclass CopyImage : Microsoft.UI.Xaml.Controls.Page
{
CopyImage();
}
[default_interface]
runtimeclass CopyText : Microsoft.UI.Xaml.Controls.Page
{
CopyText();
}
[default_interface]
runtimeclass HistoryAndRoaming : Microsoft.UI.Xaml.Controls.Page
{
HistoryAndRoaming();
}
[default_interface]
runtimeclass OtherScenarios : Microsoft.UI.Xaml.Controls.Page
{
OtherScenarios();
}
}
ご覧のように、これは個々の .idl ファイルの内容のコピーであり、すべて 1 つの名前空間内にあり、 MyProperty 各ランタイム クラスから削除されます。
Visual Studioのソリューション エクスプローラーで、元の IDL ファイル (CopyFiles.idl、CopyImage.idl、CopyText.idl、HistoryAndRoaming.idl、OtherScenarios.idl) をすべて複数選択し、編集>削除します (ダイアログで [削除] を選択します)。
最後に、同じ 5 つの XAML ページの各種類のMyPropertyファイルと.h ファイルで、.cppの削除を完了するには、int32_t MyProperty() アクセサーとvoid MyProperty(int32_t)ミューテーター関数の宣言と定義を削除します。
なお、XAML ファイルの名前は、それらが表すクラスの名前と一致することを常に推奨します。 たとえば、XAML マークアップ ファイルに x:Class="MyNamespace.MyPage" がある場合は、そのファイルに MyPage.xaml という名前を付ける必要があります。 これは技術的な要件ではありませんが、同じ成果物に対して異なる名前を付ける必要がないと、プロジェクトの理解性と保守性が高く、作業が容易になります。
CopyFiles
C# プロジェクトでは、 CopyFiles XAML ページ型が CopyFiles.xaml および CopyFiles.xaml.cs ソース コード ファイルに実装されます。 次に、 CopyFiles の各メンバーを見てみましょう。
rootPage
これはプライベート フィールドです。
// CopyFiles.xaml.cs
...
public sealed partial class CopyFiles : Page
{
MainPage rootPage = MainPage.Current;
...
}
...
C++/WinRT では、次のように定義して初期化できます。
// CopyFiles.h
...
struct CopyFiles : CopyFilesT<CopyFiles>
{
...
private:
SDKTemplate::MainPage rootPage{ MainPage::Current() };
};
...
ここでも ( MainPage::current と同様に) 、CopyFiles::rootPage は、実装型ではなく投影型である SDKTemplate::MainPage 型として宣言されます。
CopyFiles (コンストラクター)
C++/WinRT プロジェクトでは、 CopyFiles 型には、必要なコードを含むコンストラクターが既に含まれています ( InitializeComponent を呼び出すだけです)。
CopyButton_Click
C# CopyButton_Click メソッドはイベント ハンドラーであり、そのシグネチャの async キーワードから、メソッドが非同期処理を行っていることを通知できます。 C++/WinRT では、非同期メソッドを コルーチンとして実装します。 C++/WinRT でのコンカレンシーの概要と コルーチン の説明については、「 C++/WinRT を使用したコンカレンシーと非同期操作」を参照してください。
コルーチンの完了後にさらに作業をスケジュールすることが一般的です。このような場合、コルーチンは待機可能な非同期オブジェクト型を返し、必要に応じて進行状況を報告します。 ただし、これらの考慮事項は通常、イベント ハンドラーには適用されません。 そのため、非同期操作を実行するイベント ハンドラーがある場合は、 winrt::fire_and_forget を返すコルーチンとして実装できます。 詳細については、「 ファイア アンド フォーゲット」を参照してください。
fire-and-forget コルーチンの考え方では、その完了時期を気にしませんが、処理自体はバックグラウンドで引き続き進行しています(または一時停止され、再開を待っています)。 C# の実装から、this ポインターに依存していることがわかります (インスタンス データ メンバーrootPageにアクセスします)。 したがって、 this ポインター ( CopyFiles オブジェクトへのポインター) が CopyButton_Click コルーチンを上回っていることを確認する必要があります。 ユーザーが UI ページ間を移動するこのサンプル アプリケーションのような状況では、それらのページの有効期間を直接制御することはできません。
CopyButton_Click がバックグラウンド スレッドでまだ実行中の間に CopyFiles ページが破棄された場合(そのページから移動した場合など)、rootPage に安全にアクセスすることはできません。 コルーチンを正しくするには、 this ポインターへの厳密な参照を取得し、コルーチンの期間中、その参照を保持する必要があります。 詳細については、「 C++/WinRT の強参照と弱参照」を参照してください。
サンプルの C++/WinRT バージョンを見ると、 CopyFiles::CopyButton_Click で、スタック上の単純な宣言で完了していることがわかります。
fire_and_forget CopyFiles::CopyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime{ get_strong() };
...
}
注目すべき移植されたコードの他の側面を見てみましょう。
このコードでは、 FileOpenPicker オブジェクトをインスタンス化し、後でそのオブジェクトの FileTypeFilter プロパティに 2 行アクセスします。 そのプロパティの戻り値の型は、文字列の IVector を実装します。 その IVector に対して、IVector<T>.ReplaceAll(T[]) メソッドを呼び出します。 興味深い点は、配列が予想されるそのメソッドに渡す値です。 コード行を次に示します。
filePicker.FileTypeFilter().ReplaceAll({ L"*" });
渡す値 ({ L"*" }) は、標準の C++ 初期化子リストです。 この場合は 1 つのオブジェクトが含まれますが、初期化子リストには任意の数のコンマ区切りオブジェクトを含めることができます。 このようなメソッドに初期化子リストを渡す便利な C++/WinRT の部分については、 標準初期化子リストで説明します。
C# await キーワードを C++/WinRT で co_await に移植します。 コードの例を次に示します。
auto storageItems{ co_await filePicker.PickMultipleFilesAsync() };
次に、この C# コード行について考えてみましょう。
dataPackage.SetStorageItems(storageItems);
C# では、< で表される IReadOnlyList>StorageFile を DataPackage.SetStorageItems で想定される IEnumerable<IStorageItem> に暗黙的に変換できます。 ただし、C++/WinRT では、 IVectorView<StorageFile> から IIterable<IStorageItem> に明示的に変換する必要があります。 そのため、動作中の as 関数のもう 1 つの例があります。
dataPackage.SetStorageItems(storageItems.as<IVectorView<IStorageItem>>());
C# で null キーワード (たとえば、Clipboard.SetContentWithOptions(dataPackage, null)) を使用する場合は、C++/WinRT (nullptr など) でClipboard::SetContentWithOptions(dataPackage, nullptr)を使用します。
PasteButton_Click
これは、ファイア アンド フォーゲット コルーチンの形式のもう 1 つのイベント ハンドラーです。 注目すべき移植されたコードの側面を見てみましょう。
サンプルの C# バージョンでは、 catch (Exception ex)で例外をキャッチします。 移植された C++/WinRT コードには、式 catch (winrt::hresult_error const& ex)が表示されます。
winrt::hresult_error とその使用方法の詳細については、「C++/WinRT でのエラー処理」を参照してください。
C# オブジェクトが null されているかどうかをテストする例が if (storageItems != null)。 C++/WinRT では、boolに対するテストを内部的に行う、nullptrへの変換演算子に依存できます。
移植された C++/WinRT バージョンのサンプルのコードフラグメントの少し簡略化されたバージョンを次に示します。
std::wostringstream output;
output << std::wstring_view(ApplicationData::Current().LocalFolder().Path());
そのように std::wstring_view を winrt::hstring から構築することは、hstring::c_str 関数を呼び出すこと(winrt::hstring を C スタイル文字列に変換するため)の代替手段の一例です。 この代替方法は、hstring の std::wstring_view への変換演算子のおかげで機能します。
C# のこのフラグメントについて考えてみましょう。
var file = storageItem as StorageFile;
if (file != null)
...
C# as キーワードを C++/WinRT に移植するには、これまで関数 として 数回使用しました。 型変換が失敗した場合、その関数は例外をスローします。 ただし、(コードでその条件を処理できるように) 失敗した場合に nullptr を返すように変換する場合は、 代わりにtry_as 関数を使用します。
auto file{ storageItem.try_as<StorageFile>() };
if (file)
...
CopyFiles の移植を完了するために必要な XAML をコピーする
元のサンプル ソース コード ダウンロードのCopyFiles.xaml フォルダーからshared ファイルの内容全体を選択し、C++/WinRT プロジェクトのCopyFiles.xaml ファイルに貼り付けることができます (C++/WinRT プロジェクト内のファイルの既存の内容を置き換えます)。
最後に、対応する XAML マークアップを上書きしただけなので、 CopyFiles.h を編集して .cpp し、ダミーの ClickHandler 関数を削除します。
これで CopyFile の移植が完了し、手順に従っている場合は、C++/WinRT プロジェクトがビルドされて実行され、CopyFiles シナリオが機能するようになります。
CopyImage
CopyImage XAML ページの種類を移植するには、CopyFiles の場合と同じプロセスに従います。 CopyImage の移植中に、C# using ステートメントを使用します。これにより、IDisposable インターフェイスを実装するオブジェクトが正しく破棄されます。
if (imageReceived != null)
{
using (var imageStream = await imageReceived.OpenReadAsync())
{
... // Pass imageStream to other APIs, and do other work.
}
}
C++/WinRT の同等のインターフェイスは、単一の Close メソッドを持つ IClosable です。 上記の C# コードと同等の C++/WinRT を次に示します。
if (imageReceived)
{
auto imageStream{ co_await imageReceived.OpenReadAsync() };
... // Pass imageStream to other APIs, and do other work.
imageStream.Close();
}
C++/WinRT オブジェクトは、主に決定論的な最終処理を欠く言語の利点のために 、IClosable を実装します。 C++/WinRT には確定的な最終処理があるため、多くの場合、C++/WinRT を記述するときに IClosable::Close を呼び出す必要はありません。 しかし、それを呼び出すのが良い場合があり、これはそれらの時間の一つです。 ここで、imageStream 識別子は、基になるWindows ランタイム オブジェクト (この場合は IRandomAccessStreamWithContentType を実装するオブジェクト) を囲む参照カウントラッパーです。 imageStream のファイナライザー(そのデストラクター)は、それを囲むスコープ(中かっこ)の終わりに実行されると判断できますが、そのファイナライザーが Close を呼び出すかどうかまでは確実ではありません。 これは、imageStream を他の API に渡し、基になるWindows ランタイム オブジェクトの参照カウントに引き続き寄与している可能性があるためです。 そのため、 これは Close を明示的に呼び出すのが良い場合です。 詳細については、「 使用するランタイム クラスで IClosable::Close を呼び出す必要がありますか?」を参照してください。
次に、(uint)(imageDecoder.OrientedPixelWidth * 0.5) イベント ハンドラーで見つかる C# 式のについて考えます。 その式は uint に doubleを乗算し、結果として doubleになります。 その後、それを uint にキャストします。 C++/WinRT では、似た見た目の C スタイルのキャスト () (uint32_t)(imageDecoder.OrientedPixelWidth() * 0.5)、意図するキャストの種類を正確に明確にすることをお勧めします。この場合は、static_cast<uint32_t>(imageDecoder.OrientedPixelWidth() * 0.5)で行います。
C# バージョンの CopyImage.OnDeferredImageRequestedHandler には finally 句がありますが、 catch 句はありません。 C++/WinRT バージョンをさらに少し進め、遅延レンダリングが成功したかどうかを報告できるように、 catch 句を実装しました。
この XAML ページの残りの部分を移植しても、新しい説明は得られません。 ダミーの ClickHandler 関数は必ず削除してください。 また、 CopyFiles と同様に、ポートの最後の手順は、 CopyImage.xamlの内容全体を選択し、C++/WinRT プロジェクト内の同じファイルに貼り付けることです。
CopyText
既に説明した手法を使用して、 CopyText.xaml と CopyText.xaml.cs を移植できます。
HistoryAndRoaming
HistoryAndRoaming XAML ページ型を移植する際に、いくつか留意すべき点があります。
まず、C# ソース コードを見て、OnHistoryEnabledChanged イベント ハンドラーを通じて OnNavigatedTo から制御のフローに従い、最後に非同期関数 CheckHistoryAndRoaming に進みます (これは待機されていないため、基本的に起動して忘れてしまいます)。
CheckHistoryAndRoaming は非同期であるため、C++/WinRT では、this ポインターの有効期間について注意する必要があります。
HistoryAndRoaming.cppソース コード ファイルの実装を見ると、結果を確認できます。 まず、 デリゲートを Clipboard::HistoryEnabledChanged イベントと Clipboard::RoamingEnabledChanged イベントに アタッチする場合、 HistoryAndRoaming ページ オブジェクトへの弱い参照のみを受け取ります。 これを行うには、 ポインターへの依存関係ではなく、this から返された値に依存するデリゲートを作成します。 つまり、最終的に非同期コードを呼び出すデリゲート自体は、そこから移動した場合でも、HistoryAndRoaming ページを存続させておくことはありません。
2 つ目は、最終的に fire-and-forget の CheckHistoryAndRoaming コルーチンにたどり着いたとき、まず最初に行うのは、HistoryAndRoaming ページが少なくともそのコルーチンの完了までは存続することを保証するために、this への強参照を取得することです。 説明した両方の側面の詳細については、「 C++/WinRT の厳密な参照と弱い参照」を参照してください。
CheckHistoryAndRoaming の移植中に、もう 1 つの目的地が見つかります。 UI を更新するコードが含まれています。そのため、メイン UI スレッドで行っていることを確認する必要があります。 最初にイベント ハンドラーを呼び出すスレッドがメイン UI スレッドです。 ただし、通常、非同期メソッドは任意のスレッドで実行または再開できます。 C# では、ソリューションは UI スレッドに作業をディスパッチすることです。 C++/WinRT では、 winrt::resume_foreground 関数を this ポインターの DispatcherQueue と共に使用してコルーチンを中断し、メイン UI スレッドですぐに再開できます。
関連する式は co_await winrt::resume_foreground(DispatcherQueue());。 短いバージョンは、C++/WinRT によって提供される変換演算子によって提供されます。
この XAML ページの残りの部分を移植しても、新しい説明は得られません。 ダミーの ClickHandler 関数を削除し、XAML マークアップをコピーすることを忘れないでください。
OtherScenarios
既に説明した手法を使用して、 OtherScenarios.xaml と OtherScenarios.xaml.cs を移植できます。
まとめ
このチュートリアルが、C# アプリケーションをご自身で C++/WinRT に移植するために十分な情報と手法を提供できていれば幸いです。 リフレッシャーを使用すると、クリップボード サンプルのソース コードの前 (C#) と後 (C++/WinRT) バージョンを引き続き参照し、それらを並べて比較して対応を確認できます。
関連トピック
Windows developer