Share via


C++/WinRT で API を使用する

このトピックでは、C++/WinRT の API が Windows に含まれるかどうか、サード パーティ コンポーネント ベンダーで実装するかどうか、またはユーザー自身が実装するかどうかに応じて、その使用方法について説明します。

重要

このトピックのコード例を短くし、試し易くするには、新しい Windows コンソール アプリケーション (C++/WinRT) プロジェクトを作成し、コードをコピーして貼り付けることによって再現することができます。 ただし、そのようなパッケージ化されていないアプリから、任意のカスタム (サード パーティ) Windows ランタイム型を使用することはできません。 これが Windows の型の唯一の使用方法です。

コンソール アプリからカスタム (サード パーティ) の Windows ランタイム型を使用するには、使用されたカスタム型の登録を解決できるように、アプリにパッケージ ID を指定する必要があります。 詳細については、Windows アプリケーション パッケージ プロジェクトに関するページを参照してください。

または、空のアプリ (C++/WinRT)Core アプリ (C++/WinRT)、または Windows ランタイム コンポーネント (C++/WinRT) のプロジェクト テンプレートから新しいプロジェクトを作成します。 これらの種類のアプリには既に パッケージ ID が指定されています。

API が Windows 名前空間にある場合

これは、Windows ランタイム API を使用する最も一般的なケースです。 メタデータで定義された Windows 名前空間のそれぞれの型に対して、C++/WinRT は C++ に適した等価型 (投影型と呼ばれます) を定義します。 投影型には Windows の型と同じ完全修飾名がありますが、C++ 構文を使用する C++ winrt 名前空間に配置されます。 たとえば、Windows::Foundation::Uri は、winrt::Windows::Foundation::Uri として C++/WinRT に投影されます。

こちらが簡単なコードの例です。 以下のコード例を Windows コンソール アプリケーション (C++/WinRT) プロジェクトのメイン ソース コード ファイルに直接コピーして貼り付ける場合は、最初にプロジェクト プロパティで [プリコンパイル済みヘッダーを使用しない] を設定します。

// main.cpp
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };
    Uri combinedUri = contosoUri.CombineUri(L"products");
}

winrt/Windows.Foundation.h は SDK の一部で、%WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\ フォルダー内部にあります。 そのフォルダー内のヘッダーには、C++/WinRT に投影された Windows 名前空間型が含まれます。 この例では、winrt/Windows.Foundation.h には、ランタイム クラス Windows::Foundation::Uri の投影型である winrt::Windows::Foundation::Uri が含まれます。

ヒント

Windows 名前空間から型を使用する場合は、その名前空間に対応する C++/WinRT ヘッダーを含めます。 using namespace ディレクティブはオプションですが、便利です。

上記のコード例では、C++/WinRT を初期化した後、winrt::Windows::Foundation::Uri 投影型の値を、文書で公開されているコンストラクターの 1 つ (この例では Uri(String)) を介してスタック割り当てしています。 最も一般的な使用例であるこの例の場合、通常はこれで十分です。 C++/WinRT 投影型の値がある場合は、すべての同じメンバーが含まれるため、それを実際の Windows ランタイム型のインスタンスであるかのように扱うことができます。

実際、この投影された値はプロキシであり、基本的にはバッキング オブジェクトへのスマート ポインタにすぎません。 投影された値のコンストラクターは、RoActivateInstance を呼び出して、バッキング Windows ランタイム クラス (この場合は Windows.Foundation.Uri) のインスタンスを作成し、そのオブジェクトの既定のインターフェイスを新しく投影された値に格納します。 次に示すように、投影された値のメンバーへの呼び出しは実際にはスマート ポインターを介して、状態変更が発生するバッキング オブジェクトに委任されます。

投影された Windows::Foundation::Uri 型

contosoUri の値が範囲外になると破壊し、その参照を既定のインターフェイスに解放します。 その参照がバッキング Windows ランタイム Windows.Foundation.Uri オブジェクトへの最後の参照である場合、バッキング オブジェクトも破棄されます。

ヒント

投影型は、API を使用するための Windows ランタイム型のラッパーです。 たとえば、投影インターフェイスは Windows ランタイム インターフェイスのラッパーです。

C++/WinRT 投影ヘッダー

C++/WinRT から Windows 名前空間 API を利用するには、%WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt フォルダのヘッダーを含めます。 使用する各名前空間に対応するヘッダーを含める必要があります。

たとえば、Windows::Security::Cryptography::Certificates 名前空間の場合、同等の C++/WinRT 型定義は winrt/Windows.Security.Cryptography.Certificates.h に存在します。 このヘッダーを含めると、Windows::Security::Cryptography::Certificates 名前空間内のすべての型にアクセスできます。

1 つの名前空間ヘッダーに関連する名前空間ヘッダーの一部が含まれる場合がありますが、この実装の詳細に依存しないでください。 使用する名前空間のヘッダーを明示的に含めます。

たとえば、Certificate::GetCertificateBlob メソッドは Windows::Storage::Streams::IBuffer インターフェイスを返します。 Certificate::GetCertificateBlob メソッドを呼び出す前に、返された Windows::Storage::Streams::IBuffer を確実に受信して操作できるように、winrt/Windows.Storage.Streams.h 名前空間ヘッダー ファイルを含める必要があります。

その名前空間で型を使用する前に必要な名前空間ヘッダーを含め忘れることは、ビルド エラーでよくある原因です。

オブジェクト経由、インターフェイス経由、または ABI 経由でのメンバーへのアクセス

C++/WinRT 投影を使用する場合、Windows ランタイム クラスのランタイム表現は、基盤となる ABI インターフェイス以上のものではありません。 ただし、便宜上、作成者が意図した方法でクラスに対してコードを記述することができます。 たとえば、そのクラスのメソッドであるかのように UriToString メソッドを呼び出すことができます (実際には、別の IStringable インターフェイスのメソッドです)。

WINRT_ASSERT はマクロ定義であり、_ASSERTE に展開されます。

Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.

この利便性は、適切なインターフェイスのクエリを介して実現できます。 ただし、制御できるのは常にユーザーです。 IStringable インターフェイスを自分で取得し、それを直接使用することで、わずかなパフォーマンスのためにその利便性を少し分配することもできます。 以下のコード例では、実際の IStringable インターフェイス ポインターを実行時に (1 回限りのクエリで) 取得しています。 その後、ToString への呼び出しは直接的であり、QueryInterface へのそれ以降の呼び出しを回避します。

...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");

同じインターフェイスの複数のメソッドを呼び出すことを把握している場合は、この方法を選ぶことをお勧めします。

ちなみに、ABI レベルでメンバーにアクセスする場合に可能です。 以下の例は、その方法を示すコード例であり、「C++/WinRT と ABI 間の相互運用」に詳細とコード例があります。

#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;

int main()
{
    winrt::init_apartment();
    Uri contosoUri{ L"http://www.contoso.com" };

    int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.

    winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
        contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
    HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}

初期化の遅延

C++/WinRT には、投影されたそれぞれの型に特別な C++/WinRT std::nullptr_t コンストラクターがあります。 その例外の 1 つとして、既定のコンストラクターを含むすべての投影された型のコンストラクターにより Windows ランタイムのバッキング オブジェクトが作成され、そのオブジェクトへのスマート ポインターが提供されます。 そのため、そのルールは、初期化されていないローカル変数、初期化されていないグローバル変数、初期化されていないメンバー変数など、既定のコンストラクターが使用されるあらゆる場所で適用されます。

一方で、作業を後に遅らせることができるように、Windows ランタイムのバッキング オブジェクトを作成しないで投影された型の変数を作成する場合は、これを実行できます。 その特別な C++/WinRT std::nullptr_t コンストラクターを使用して変数またはフィールドを宣言します (これにより C++/WinRT プロジェクションが各ランタイム クラスに組み込まれます)。 次のコード例では、その特殊なコンストラクターを m_gamerPicBuffer と共に使用します。

#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;

#define MAX_IMAGE_SIZE 1024

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

int main()
{
    winrt::init_apartment();
    Sample s;
    // ...
    s.DelayedInit();
}

std::nullptr_t コンストラクターを "除く" 投影された型のすべてのコンストラクターにより、Windows ランタイムのバッキング オブジェクトが作成されます。 std::nullptr_t コンストラクターは、基本的には何もしません。 投影されたオブジェクトは、その後初期化されることが想定されています。 したがって、ランタイム クラスに既定のコンストラクターが存在するかにかかわらず、この方法を使用して効率的に初期化を遅延させることができます。

この考慮事項は、ベクトルやマップ内など、既定のコンストラクターを呼び出す他の場所に影響します。 空のアプリ (C++/WinRT) プロジェクトが必要な、このコード例を検討します。

std::map<int, TextBlock> lookup;
lookup[2] = value;

割り当てによって新しい TextBlock を作成し、すぐに value で上書きします。 対処法は次のようになります。

std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);

既定のコンストラクターによるコレクションへの影響」も参照してください。

誤って遅延初期化しない

誤って std::nullptr_t コンストラクターを呼び出さないように注意してください。 コンパイラの競合解決では、ファクトリ コンストラクターよりそれが優先されます。 たとえば、次のような 2 つのランタイム クラスの定義について考えます。

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox();
}

// Gift.idl
runtimeclass Gift
{
    Gift(GiftBox giftBox); // You can create a gift inside a box.
}

ここで、ボックス内にない Gift を構築したいものとします (初期化されていない GiftBox で構築された Gift)。 最初に、それを行う "間違った" 方法を見てみましょう。 GiftBox を受け取る Gift コンストラクターがあることはわかっています。 ただし、null GiftBox を渡すと (以下で行うような同一の初期化で Gift コンストラクターを呼び出す)、目的の結果は得られません

// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.

Gift gift{ nullptr };
auto gift{ Gift(nullptr) };

ここで得られるのは、初期化されていない Gift です。 GiftBox が初期化されていない Gift は得られません。 次に示すのは、それを行う "正しい" 方法です。

// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.

Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };

正しくない例では、nullptr リテラルを渡しすことで、遅延初期化コンストラクター優先で解決されます。 ファクトリ コンストラクター優先で解決するには、パラメーターの型が GiftBox である必要があります。 それでも、正しい例で示されているように、明示的に遅延初期化される GiftBox を渡すオプションがあります。

この次の例は、パラメーターの型が GiftBox であり、std::nullptr_t ではないので、"やはり" 正しい方法です。

GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.

あいまいさが発生するのは、nullptr リテラルを渡したときだけです。

誤ってコピー コンストラクトしない

この注意は、上の「誤って遅延初期化しない」セクションで説明したものと似ています。

遅延初期化コンストラクターだけでなく、C++/WinRT プロジェクションではすべてのランタイム クラスにコピー コンストラクターも挿入されます。 それは、構築されているオブジェクトと同じ型を受け取る、単一パラメーターのコンストラクターです。 結果のスマート ポインターでは、そのコンストラクター パラメーターでポイントされるのと同じバッキング Windows ランタイム オブジェクトがポイントされます。 結果は、同じバッキング オブジェクトをポイントする 2 つのスマート ポインター オブジェクトになります。

次に示すのは、コード例で使用するランタイム クラスの定義です。

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}

大きい GiftBox の内部に GiftBox を構築したいものとします。

GiftBox bigBox{ ... };

// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.

GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };

それを行う "正しい" 方法は、アクティベーション ファクトリを明示的に呼び出すことです。

GiftBox bigBox{ ... };

// These two ways call the activation factory explicitly.

GiftBox smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
    winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };

API が Windows ランタイム コンポーネントに実装されている場合

このセクションは、コンポーネントを自分で作成した場合でも、ベンダーから提供された場合でも適用されます。

注意

C++/WinRT Visual Studio Extension (VSIX) と NuGet パッケージ (両者が連携してプロジェクト テンプレートとビルドをサポート) のインストールと使用については、Visual Studio での C++/WinRT のサポートに関する記事を参照してください。

アプリケーション プロジェクトで、Windows ランタイム コンポーネントの Windows ランタイム メタデータ (.winmd) ファイルを参照し、ビルドします。 ビルド中、cppwinrt.exe ツールにより、コンポーネントの API サーフェイスを十分に説明する ("投影する") 標準的な C++ ライブラリが生成されます。 言い換えると、生成されたライブラリにはコンポーネントの投影型が含まれます。

その後、Windows 名前空間型と同様に、ヘッダーを含め、そのコンストラクターの 1 つを介して投影型を構築します。 アプリケーション プロジェクトのスタートアップ コードでランタイム クラスが登録され、投影型のコンストラクターで RoActivateInstance が呼び出され、参照されたコンポーネントからランタイム クラスをアクティブにします。

#include <winrt/ThermometerWRC.h>

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer thermometer;
    ...
};

Windows ランタイム コンポーネントで実装された API の使用に関する詳細、コード、チュートリアルについては、「C++/WinRT を使用した Windows ランタイム コンポーネント」および「C++/WinRT でのイベントの作成」を参照してください。

API が使用するプロジェクトに実装されている場合

このセクションのコード例は、XAML コントロール: C++/WinRT プロパティへのバインドに関するトピックから抜粋したものです。 詳細、コード、および使用しているものと同じプロジェクトに実装するランタイム クラスの使用に関するチュートリアルについてのトピックを参照してください。

XAML UI で使用される型は、それが XAML と同じプロジェクトにある場合でも、ランタイム クラスである必要があります。 このシナリオでは、ランタイム クラスの Windows ランタイム メタデータ (.winmd) から投影型を生成します。 ここでも、ヘッダーを含めることになりますが、ランタイム クラスのインスタンスを構築する方法としてバージョン 1.0 またはバージョン 2.0 の C++/WinRT を選択できます。 バージョン 1.0 メソッドでは、winrt:: make が使用されます。バージョン 2.0 メソッドは均一の構築と呼ばれています。 それぞれを順番に見てみましょう。

winrt:: make を使用して構築する

既定 (C++ /WinRT バージョン 1.0) のメソッドから始めましょう。少なくとも、このパターンについては理解しておくことをお勧めします。 その std::nullptr_t コンストラクターを介して投影される型を作成します。 このコンストラクターは初期化を行わないため、次に winrt::make ヘルパー関数を介してインスタンスに値を代入し、必要なコンストラクター引数を渡す必要があります。 使用するコードと同じプロジェクトに実装されたランタイム クラスは、Windows ランタイム/COM アクティブ化を介して登録したり、インスタンス化したりする必要はありません。

完全なチュートリアルについては、XAML コントロール: C++/WinRT プロパティへのバインドに関するページを参照してください。 このセクションでは、そのチュートリアルからの引用を示します。

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        BookstoreViewModel MainViewModel{ get; };
    }
}

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...

// MainPage.cpp
...
#include "BookstoreViewModel.h"

MainPage::MainPage()
{
    m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
    ...
}

均一の構築

C++/WinRT バージョン 2.0 以降では、"均一の構築" と呼ばれる最適化された形式の構築が用意されています (「C++/WinRT 2.0 の新機能と変更点」を参照してください)。

完全なチュートリアルについては、XAML コントロール: C++/WinRT プロパティへのバインドに関するページを参照してください。 このセクションでは、そのチュートリアルからの引用を示します。

winrt:: make ではなく均一の構築を使用するには、アクティベーション ファクトリが必要です。 IDL にコンストラクターを追加して、生成することをお勧めします。

// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

その後、以下に示すように、MainPage.h で、m_mainViewModel を 1 回だけ宣言して初期化します。

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel;
        ...
    };
}
...

次に、MainPage.cppMainpage.xaml コンストラクターでは、コード m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>(); は必要ありません。

均一の構築の詳細とコード例については、「均一コンストラクションのオプトインと、実装への直接アクセス」を参照してください。

投影型とインターフェイスをインスタンス化して返す

こちらは、使用するプロジェクトにおける投影型とインターフェイスの外観の例です。 投影された型 (この例のような型) はツールによって生成されるものであり、ユーザー自身が作成するものではないことに留意してください。

struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
    Windows::Foundation::IStringable, Windows::Foundation::IClosable>

MyRuntimeClass は投影型です。投影されたインターフェイスには、IMyRuntimeClassIStringableIClosable などがあります。 このトピックでは、投影型をインスタンス化できるさまざまな方法を紹介しています。 こちらは、MyRuntimeClass を例にして、リマインダーと要約を示します。

// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;

// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
  • 投影型のすべてのインターフェイスのメンバーにアクセスできます。
  • 投影型は、呼び出し元に返すことができます。
  • 投影型とインターフェイスは、winrt::Windows::Foundation::IUnknown から派生します。 したがって、IUnknown::as を投影型やインターフェイスで呼び出して他の投影されたインターフェイスのクエリを実行し、これを使用するか呼び出し元に返すことができます。 as メンバー関数は、QueryInterface のように機能します。
void f(MyProject::MyRuntimeClass const& myrc)
{
    myrc.ToString();
    myrc.Close();
    IClosable iclosable = myrc.as<IClosable>();
    iclosable.Close();
}

アクティベーション ファクトリ

C++/WinRT オブジェクトを作成する便利で直接的な方法は、次のとおりです。

using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };

ただし、アクティベーション ファクトリを自分で作成し、そこから自分の都合に合わせてオブジェクトを作成する必要がある場合もあります。 こちらに、winrt::get_activation_factory 関数テンプレートを使用した例を示します。

using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");

上記の例の 2 つのクラスは、Windows 名前空間から生成された型です。 次の例では、ThermometerWRC::Thermometer は Windows ランタイム コンポーネントに実装されたカスタム型です。

auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();

メンバー/型のあいまいさ

メンバー関数の名前が型と同じである場合は、あいまいさがあります。 メンバー関数で C++ の修飾されていない名前参照のルールにより、名前空間内で検索する前にクラスが検索されます。 代入エラーはエラーではない (SFNAE) のルールは適用されません (これは関数テンプレートのオーバーロードの解決中に適用されます)。 したがって、クラス内の名前が意味を持たない場合、コンパイラはより適切な一致を探し続けません。単にエラーを報告するだけです。

struct MyPage : Page
{
    void DoWork()
    {
        // This doesn't compile. You get the error
        // "'winrt::Windows::Foundation::IUnknown::as':
        // no matching overloaded function found".
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Style>() };
    }
}

上記のように、コンパイラは、FrameworkElement.Style() (C++/WinRT ではメンバー関数) をテンプレート パラメーターとして IUnknown:: as に渡していると認識します。 解決策は、名前 Style が強制的に型 Windows::UI::Xaml:: Style として解釈されるようにすることです。

struct MyPage : Page
{
    void DoWork()
    {
        // One option is to fully-qualify it.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };

        // Another is to force it to be interpreted as a struct name.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<struct Style>() };

        // If you have "using namespace Windows::UI;", then this is sufficient.
        auto style{ Application::Current().Resources().
            Lookup(L"MyStyle").as<Xaml::Style>() };

        // Or you can force it to be resolved in the global namespace (into which
        // you imported the Windows::UI::Xaml namespace when you did
        // "using namespace Windows::UI::Xaml;".
        auto style = Application::Current().Resources().
            Lookup(L"MyStyle").as<::Style>();
    }
}

修飾されていない名前参照には、名前の後に :: が指定されている場合は特別な例外があります。この場合、関数、変数、および列挙値は無視されます。 これにより、次のようなことができます。

struct MyPage : Page
{
    void DoSomething()
    {
        Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
    }
}

Visibility() への呼び出しは、UIElement.Visibility メンバー関数名に解決されます。 ただし、パラメーター Visibility::Collapsed は単語 Visibility の後に :: が続くため、メソッド名は無視され、コンパイラは列挙型クラスを検出します。

重要な API