C++/WinRT で API を作成する

このトピックでは、直接的または間接的に winrt::implements 基本構造体を使用して、C++/WinRT API を作成する方法を示します。 このコンテキストで作成するの同義語は、生成する、または実装するです。 このトピックでは、C++/WinRT 型で API を実装するための次のシナリオを次の順序で説明します。

注意

このトピックでは Windows ランタイム コンポーネントについて説明しますが、C++/WinRT のコンテキストについてだけです。 すべての Windows ランタイム言語を対象とする Windows ランタイム コンポーネントに関するコンテンツをお探しの場合は、「Windows ランタイム コンポーネント」をご覧ください。

  • Windows ランタイム クラス (ランタイム クラス) は作成しません。アプリ内のローカルでの使用のために 1 つ以上の Windows ランタイム インターフェイスを実装するだけです。 このケースでは、winrt::implements から直接派生させて、関数を実装します。
  • ランタイム クラスを作成します。 アプリから使用されるコンポーネントを作成する場合もあります。 あるいは、XAML ユーザー インターフェイス (UI) から使用される型を作成することもあり、その場合は、同じコンパイル ユニット内でランタイム クラスの実装と使用の両方を行います。 このような場合は、winrt::implements から派生するクラスをツールに生成させます。

どちらの場合も、C++/WinRT API を実装する型は、実装型と呼ばれます。

重要

実装型の概念と投影型の概念を区別することが重要です。 投影型については、「C++/WinRT での API の使用」で説明します。

ランタイム クラスを作成しない場合

最も単純なのは、型で Windows ランタイム インターフェイスを実装し、その型を同じアプリ内で使用するというシナリオです。 その場合、型はランタイム クラスである必要はなく、ただ通常の C++ クラスでかまいません。 たとえば、CoreApplication に基づいてアプリを記述する場合があります。

型が XAML UI によって参照される場合は、たとえそれが XAML と同じプロジェクト内にあるとしても、ランタイム クラスである必要があります。 その場合は、「XAML UI で参照されるランタイム クラスを作成する場合」セクションを参照してください。

注意

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

Visual Studio では、Visual C++>Windows ユニバーサル>Core App (C++/WinRT) プロジェクト テンプレートで CoreApplication パターンを示しています。 このパターンは、Windows::ApplicationModel::Core::IFrameworkViewSource の実装を CoreApplication::Run に渡すことから始まります。

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    IFrameworkViewSource source = ...
    CoreApplication::Run(source);
}

CoreApplication はインターフェイスを使用してアプリの最初のビューを作成します。 概念的には、IFrameworkViewSource は次のように見えます。

struct IFrameworkViewSource : IInspectable
{
    IFrameworkView CreateView();
};

この場合も概念的に言えば、CoreApplication::Run の実装は、次を行います。

void Run(IFrameworkViewSource viewSource) const
{
    IFrameworkView view = viewSource.CreateView();
    ...
}

したがって、開発者は、IFrameworkViewSource インターフェイスを実装します。 C++/WinRT には基本構造体テンプレート winrt::implements があります。これを利用すれば、COM スタイルのプログラミングを使用せずに 1 つ以上のインターフェイスを簡単に実装することができます。 単に implements から型を派生させて、インターフェイスの機能を実装します。 ここではその方法を説明します。

// App.cpp
...
struct App : implements<App, IFrameworkViewSource>
{
    IFrameworkView CreateView()
    {
        return ...
    }
}
...

これで IFrameworkViewSource の処理は完了です。 次のステップでは、IFrameworkView インターフェイスを実装するオブジェクトを返します。 そのインターフェイスをアプリに実装する方法も選択できます。 次のコード例は、少なくともデスクトップ上でウィンドウを起動して実行する最小限のアプリを表しています。

// App.cpp
...
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const &) {}

    void Load(hstring const&) {}

    void Run()
    {
        CoreWindow window = CoreWindow::GetForCurrentThread();
        window.Activate();

        CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const & window)
    {
        // Prepare app visuals here
    }

    void Uninitialize() {}
};
...

アプリIFrameworkViewSource であるため、Run に渡すことだけができます。

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Windows ランタイム コンポーネントでランタイム クラスを作成する場合

型が別のバイナリ (別のバイナリは通常、アプリケーションです) から使用される Windows ランタイム コンポーネントにパッケージ化される場合、その型はランタイム クラスにする必要があります。 ランタイム クラスは Microsoft インターフェイス定義言語 (IDL) (.idl) ファイルに宣言します (「ランタイム クラスを Midl ファイル (.idl) にファクタリングする」を参照)。

結果として各 IDL ファイルが .winmd ファイルになり、Visual Studio では、それらのすべてのファイルをルート名前空間と同じ名前の単一のファイルにマージします。 その最終的な .winmd ファイルがコンポーネントのユーザーが参照するファイルになります。

ランタイム クラスを IDL ファイルに宣言する例を次に示します。

// MyRuntimeClass.idl
namespace MyProject
{
    runtimeclass MyRuntimeClass
    {
        // Declaring a constructor (or constructors) in the IDL causes the runtime class to be
        // activatable from outside the compilation unit.
        MyRuntimeClass();
        String Name;
    }
}

この IDL は、Windows ランタイム (ランタイム) クラスを宣言します。 ランタイム クラスは、通常実行可能な境界を越えて、モダン COM インターフェイスを介してアクティブ化でき、使用できる型です。 IDL ファイルをプロジェクトに追加し、ビルドすると、C++/WinRT ツールチェーン (midl.exe および cppwinrt.exe) が実装型を生成します。 IDL ファイルのワークフローの動作の例については、「XAML コントロール: C++/WinRT プロパティへのバインド」をご覧ください。

上記の IDL の例を使用した場合、実装型は、\MyProject\MyProject\Generated Files\sources\MyRuntimeClass.hMyRuntimeClass.cpp という名前のソース コード ファイル内の winrt::MyProject::implementation::MyRuntimeClass という名前の C++ 構造体スタブです。

実装型は次のようになります。

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        winrt::hstring Name();
        void Name(winrt::hstring const& value);
    };
}

// winrt::MyProject::factory_implementation::MyRuntimeClass is here, too.

F バインド ポリモーフィズム パターンが使用されていることに注意してください (MyRuntimeClass は、それ自身を、その基底クラス MyRuntimeClassT へのテンプレート引数として使用します)。 これは、CRTP (curiously recurring template pattern (奇妙に再帰するテンプレート パターン)) とも呼ばれます。 継承チェーンを上にたどると、MyRuntimeClass_base に行き当たります。

Windows 実装ライブラリ (WIL) を使用すると、シンプルなプロパティの実装を簡略化できます。 方法は以下のとおりです。

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        wil::single_threaded_rw_property<winrt::hstring> Name;
    };
}

シンプルなプロパティ」を参照してください。

template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>

したがって、このシナリオで継承階層のルートにあるのは、winrt::implements 基本構造体テンプレートです。

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

XAML UI で参照されるランタイム クラスを作成する場合

型が XAML UI によって参照される場合、その型は XAML と同じプロジェクト内にあるにもかかわらず、ランタイム クラスである必要があります。 ランタイム クラスは、通常、実行可能な境界を越えてアクティブ化されますが、代わりに、ランタイム クラスを実装するコンパイル ユニット内で使用することもできます。

このシナリオでは、API の作成使用の両方を行います。 ランタイム クラスを実装するための手順は、Windows ランタイム コンポーネントと基本的に同じです。 このため、前のセクション「Windows ランタイム コンポーネントでランタイム クラスを作成する場合」を参照してください。 唯一異なる点は、C++/WinRT ツールチェーンが IDL から実装型だけでなく投影型も生成することです。 このシナリオで単に "MyRuntimeClass" と言うだけではあいまいかもしれないと認識することが重要です。この名前を持つさまざまな種類の複数のエンティティがあるからです。

  • MyRuntimeClass はランタイム クラスの名前です。 しかし、これは実際は抽象化であり、IDL で宣言され、何らかのプログラミング言語で実装されるものです。
  • MyRuntimeClass は、C++ 構造体 winrt::MyProject::implementation::MyRuntimeClass の名前であり、ランタイム クラスの C++/WinRT 実装です。 これまでに見てきたように、実装プロジェクトと使用プロジェクトが別々にある場合、この構造体は実装プロジェクトにのみ存在します。 これは実装型、または実装です。 この型は、ファイル \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.hMyRuntimeClass.cpp で (cppwinrt.exe ツールによって) 生成されます。
  • MyRuntimeClass は、C++ 構造体 winrt::MyProject::MyRuntimeClass の形式の投影型の名前です。 実装プロジェクトと使用プロジェクトが別々にある場合、この構造体は使用プロジェクトにのみ存在します。 これは投影型、または投影です。 この型は、ファイル \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h で (cppwinrt.exe によって) 生成されます。

Projected type and implementation type

このトピックに関連する投影型の一部を次に示します。

// MyProject.2.h
...
namespace winrt::MyProject
{
    struct MyRuntimeClass : MyProject::IMyRuntimeClass
    {
        MyRuntimeClass(std::nullptr_t) noexcept {}
        MyRuntimeClass();
    };
}

INotifyPropertyChanged インターフェイスをランタイムに実装するチュートリアルの例については、「XAML コントロール、C++/WinRT プロパティへのバインド」を参照してください。

このシナリオでランタイム クラスを使用する手順については、「C++/WinRT での API の使用」で説明しています。

ランタイム クラスを Midl ファイル (.idl) にファクタリングする

Visual Studio プロジェクトと項目テンプレートでは、ランタイム クラスごとに個別の IDL ファイルが生成されます。 これにより、IDL ファイルとその生成されたソース コード ファイルとの間に論理的な通信手段が提供されます。

ただし、プロジェクトのすべてのランタイム クラスを 1 つの IDL ファイルに統合すると、ビルド時間が大幅に短縮されます。 これらの間に複雑な (またはわかりにくい) import 依存関係があると、統合が実際に必要になる場合があります。 また、まとまっていればランタイム クラスをより簡単に作成、レビューできることがわかります。

ランタイム クラス コンストラクター

上記のリストの重要なポイントを次に示します。

  • IDL で宣言する各コンストラクターにより、実装型と投影型の両方でコンストラクターが生成されます。 IDL で宣言されたコンストラクターは、異なるコンパイル ユニットからランタイム クラスを使用することを目的に使用されます。
  • コンストラクターを IDL で宣言してもしなくても、std::nullptr_t を受け取るコンストラクターのオーバーロードが投影型に対して生成されます。 std::nullptr_t コンストラクターの呼び出しは、"同じ" コンパイル ユニットからのランタイム クラスの使用における "2 つのステップのうちの最初のステップ" です。 詳細とコード例については、「C++/WinRT での API の使用」を参照してください。
  • 同じコンパイル ユニットからランタイム クラスを使用する場合は、既定以外のコンストラクターを実装型 (MyRuntimeClass.h にある) に直接実装することもできます。

注意

異なるコンパイル ユニットからランタイム クラスが使用されることが予想される場合は (それが一般的)、IDL にコンストラクター (少なくとも既定のコンストラクター) を含めます。 そうすることで、実装型と一緒にファクトリ実装も取得できます。

同じコンパイル ユニット内でのみランタイム クラスを作成して使用する場合は、IDL でコンストラクターを宣言しないでください。 ファクトリ実装は不要であり、生成されません。 実装型の既定のコンストラクターは削除されますが、簡単にコンストラクターを編集して、既定に設定することができます。

同じコンパイル ユニット内でのみランタイム クラスを作成して使用する予定で、コンストラクター パラメーターが必要な場合は、必要なコンストラクターを実装型で直接作成します。

ランタイム クラスのメソッド、プロパティ、イベント

ワークフローでは IDL を使ってランタイム クラスとそのメンバーを宣言すること、そしてツールによってプロトタイプとスタブの実装が自動的に生成されることを説明しました。 ランタイム クラスのメンバーに対して自動的に生成されるそれらのプロトタイプについては、IDL で宣言されている型から別の型を渡すように編集することが "できます"。 ただし、それができるのは、IDL で宣言されている型が、実装バージョンで宣言されている型に転送できる場合だけです。

いくつか例を挙げます。

  • パラメーターの型を緩和できます。 たとえば、IDL でメソッドが SomeClass を受け取る場合、実装ではそれを IInspectable に変更できます。 これができるのは、すべての SomeClassIInspectable に転送できるためです (もちろん、逆はできません)。
  • コピー可能なパラメーターを参照渡しではなく値で受け取ることができます。 たとえば、SomeClass const&SomeClass に変更します。 コルーチンに参照をキャプチャしないようにする必要があるときは、それが必要です (「パラメーターの引き渡し」をご覧ください)。
  • 戻り値を緩和できます。 たとえば、voidwinrt::fire_and_forget に変更できます。

最後の 2 つは、非同期イベント ハンドラーを作成する場合に非常に便利です。

実装型とインターフェイスをインスタンス化して返す

このセクションでは、IStringable インターフェイスと IClosable インターフェイスを実装する MyType という名前の実装型を例に挙げます。

winrt::implements から直接 MyType を派生させることができます (これはランタイム クラスではありません)。

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

struct MyType : implements<MyType, IStringable, IClosable>
{
    winrt::hstring ToString(){ ... }
    void Close(){}
};

あるいは、IDL から MyType を生成することもできます (これはランタイム クラスです)。

// MyType.idl
namespace MyProject
{
    runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
    {
        MyType();
    }    
}

実装型を直接割り当てることはできません。

MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class

ただし、winrt::make 関数テンプレートを呼び出すことで、MyType から IStringable または IClosable オブジェクトに移動し、これらのオブジェクトを使用するか、プロジェクションの一部として返すことができます。 make は実装型の既定のインターフェイスを返します。

IStringable istringable = winrt::make<MyType>();

注意

ただし、XAML UI から型を参照する場合は、同じプロジェクト内に実装型と投影型の両方が存在することになります。 その場合、make は投影型のインスタンスを返します。 そのシナリオのコード例については、「XAML コントロール、C++/WinRT プロパティへのバインド」を参照してください。

istringable (上のコード例) は、IStringable インターフェイスのメンバーを呼び出すためにだけ使用できます。 しかし、(投影されたインターフェイスである) C++/WinRT インターフェイスは winrt::Windows::Foundation::IUnknown から派生します。 したがって、その上で IUnknown::as (または IUnknown::try_as) を呼び出して他の投影された型またはインターフェイスのクエリを実行し、これを使用するか返すことができます。

ヒント

as または try_as を呼び出しては "ならない" シナリオは、ランタイム クラスの派生 ("構成可能なクラス") です。 実装型が別のクラスを構成する場合は、構成中のクラスのチェックされない、またはチェックされる QueryInterface を実行するために、as または try_as を呼び出さないでください。 代わりに、(this->) m_inner データ メンバーにアクセスし、それに対して as または try_as を呼び出します。 詳しくは、このトピックの「ランタイム クラスの派生」をご覧ください。

istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();

実装のすべてのメンバーにアクセスし、後でインターフェイスを呼び出し元に返す必要がある場合は、winrt::make_self 関数テンプレートを使用します。 make_self は、実装型をラップする winrt::com_ptr を返します。 そのインターフェイスのすべてのメンバーに (矢印演算子を使用して) アクセスしたり、呼び出し元にそれをそのまま返したり、そこで as を呼び出して結果のインターフェイス オブジェクトを呼び出し元に返したりできます。

winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();

MyType クラスは、投影の一部ではなく、実装です。 ただし、この方法では、仮想関数呼び出しのオーバーヘッドなしに、その実装メソッドを直接呼び出すことができます。 上記の例では、MyType::ToStringIStringable 上の投影されたメソッドと同じシグネチャを使用しているにもかかわらず、アプリケーション バイナリ インターフェイス (ABI) を経由することなく、非仮想メソッドを直接呼び出しています。 com_ptr は単に MyType 構造体へのポインターを保持しているだけなので、myimpl 変数と矢印演算子を介して、MyType の他の任意の内部情報にアクセスすることもできます。

インターフェイス オブジェクトがあり、それが実装上のインターフェイスであることがわかった場合は、winrt::get_self 関数テンプレートを使用して実装に戻すことができます。 これも仮想関数の呼び出しを回避する手法であり、それを利用して実装に直接アクセスできます。

注意

Windows SDK バージョン 10.0.17763.0 (Windows 10 Version 1809) 以降をインストールしていない場合は、winrt::get_self ではなく winrt::from_abi を呼び出す必要があります。

次に例を示します。 別の例が、BgLabelControl カスタム コントロール クラスの実装に関する記事にあります。

void ImplFromIClosable(IClosable const& from)
{
    MyType* myimpl = winrt::get_self<MyType>(from);
    myimpl->ToString();
    myimpl->Close();
}

ただし元のインターフェイス オブジェクトのみ参照に保持されます。 これを保持する場合は、com_ptr::copy_from を呼び出すことができます。

winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.

実装型自体は、winrt::Windows::Foundation::IUnknown から派生したものではないため、as 関数はありません。 それでも、上記の ImplFromIClosable 関数でわかるように、そのインターフェイスすべてのメンバーにアクセスできます。 ただし、そのようにする場合、未処理の実装型のインスタンスを呼び出し元に返さないでください。 代わりに、既に示した手法のいずれかを使用して、投影されるインターフェイスまたは com_ptr を返します。

実装型のインスタンスがある場合は、次のコード例に示すように、対応する投影型を想定している関数にこれを渡す必要があり、その後実行できます。 変換演算子は (cppwinrt.exe ツールによって実装型が生成されたという条件で) 実装型に存在し、これを可能にします。 実装型の値は、対応する投影型の値を想定しているメソッドに直接渡すことができます。 実装型のメンバー関数から、対応する投影型の値を想定しているメソッドに *this を渡すことができます。

// MyClass.idl
import "MyOtherClass.idl";
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void MemberFunction(MyOtherClass oc);
    }
}

// MyClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyClass : MyClassT<MyClass>
    {
        MyClass() = default;
        void MemberFunction(MyProject::MyOtherClass const& oc) { oc.DoWork(*this); }
    };
}
...

// MyOtherClass.idl
import "MyClass.idl";
namespace MyProject
{
    runtimeclass MyOtherClass
    {
        MyOtherClass();
        void DoWork(MyClass c);
    }
}

// MyOtherClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyOtherClass : MyOtherClassT<MyOtherClass>
    {
        MyOtherClass() = default;
        void DoWork(MyProject::MyClass const& c){ /* ... */ }
    };
}
...

//main.cpp
#include "pch.h"
#include <winrt/base.h>
#include "MyClass.h"
#include "MyOtherClass.h"
using namespace winrt;

// MyProject::MyClass is the projected type; the implementation type would be MyProject::implementation::MyClass.

void FreeFunction(MyProject::MyOtherClass const& oc)
{
    auto defaultInterface = winrt::make<MyProject::implementation::MyClass>();
    MyProject::implementation::MyClass* myimpl = winrt::get_self<MyProject::implementation::MyClass>(defaultInterface);
    oc.DoWork(*myimpl);
}
...

ランタイム クラスの派生

基底クラスが "アンシールド" として宣言されている場合は、別のランタイム クラスから派生するランタイム クラスを作成できます。 クラス派生の Windows ランタイム用語は、"コンポーザブル クラス" です。 派生クラスを実装するコードは、基底クラスが別のコンポーネントによって提供されるか、同じコンポーネントによって提供されるかに依存します。 ただし、これらの規則を学習する必要はありません。cppwinrt.exe コンパイラによって生成された sources 出力フォルダーからサンプル実装をコピーできます。

次の例について考えます。

// MyProject.idl
namespace MyProject
{
    [default_interface]
    runtimeclass MyButton : Windows.UI.Xaml.Controls.Button
    {
        MyButton();
    }

    unsealed runtimeclass MyBase
    {
        MyBase();
        overridable Int32 MethodOverride();
    }

    [default_interface]
    runtimeclass MyDerived : MyBase
    {
        MyDerived();
    }
}

上の例では、MyButton は、別のコンポーネントによって提供される XAML Button コントロールから派生しています。 その場合、実装は、非コンポーザブル クラスの実装と同じように見えます。

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyButton : MyButtonT<MyButton, implementation::MyButton>
    {
    };
}

一方、上記の例で、MyDerived は、同じコンポーネント内の別のクラスから派生しています。 この場合、実装には、基底クラスの実装クラスを指定する追加のテンプレート パラメーターが必要です。

namespace winrt::MyProject::implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {                                     // ^^^^^^^^^^^^^^^^^^^^^^
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
    {
    };
}

どちらの場合も、実装では、base_type 型エイリアスで修飾することで、基底クラスからメソッドを呼び出すことができます。

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
        void OnApplyTemplate()
        {
            // Call base class method
            base_type::OnApplyTemplate();

            // Do more work after the base class method is done
            DoAdditionalWork();
        }
    };

    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {
        int MethodOverride()
        {
            // Return double what the base class returns
            return 2 * base_type::MethodOverride();
        }
    };
}

ヒント

実装型が別のクラスを構成する場合は、構成中のクラスのチェックされない、またはチェックされる QueryInterface を実行するために、as または try_as を呼び出さないでください。 代わりに、(this->) m_inner データ メンバーにアクセスし、それに対して as または try_as を呼び出します。

既定以外のコンストラクターを持つ型からの派生

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) は既定以外のコンストラクターの例です。 既定のコンストラクターがないため、ToggleButtonAutomationPeer を作成するには、owner を渡す必要があります。 したがって、ToggleButtonAutomationPeer から派生させる場合は、owner を受け取って基底クラスに渡すコンストラクターを提供する必要があります。 実際には次のようになります。

// MySpecializedToggleButton.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButton :
        Windows.UI.Xaml.Controls.Primitives.ToggleButton
    {
        ...
    };
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButtonAutomationPeer :
        Windows.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
    {
        MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
    };
}

実装型用に生成されるコンストラクターは次のようになります。

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner)
{
    ...
}
...

唯一足りない部分は、そのコンストラクター パラメーターを基底クラスに渡す必要があることです。 上で述べた F バインド ポリモーフィズム パターンを覚えていますか。 C++/WinRT で使用されるパターンの詳細が理解できれば、基底クラスが何と呼ばれているかがわかります (あるいは、実装クラスのヘッダー ファイルを見ればわかります)。 このケースで基底クラス コンストラクターを呼び出す方法は、次のとおりです。

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner) : 
    MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
    ...
}
...

基底クラス コンストラクターは、ToggleButton を期待します。 ToggleButton となるのは MySpecializedToggleButtonです

(そのコンストラクター パラメーターを基底クラスに渡すために) 上記の編集を行うまで、コンパイラはコンストラクターにフラグを設定し、(このケースでは) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer> と呼ばれる型で使用できる適切な既定のコンストラクターがないことを指摘します。 これは実際には、実装型の基底クラスの基底クラスです。

名前空間: 投影型、実装型、ファクトリ

このトピックで前に説明したように、C++/WinRT のランタイム クラスは、複数の名前空間に複数の C++ クラスの形式で存在します。 したがって、名前 MyRuntimeClass の意味は、winrt::MyProject 名前空間内と winrt::MyProject::implementation 名前空間内では異なります。 現在のコンテキストがどちらの名前空間かを認識し、別の名前空間の名前が必要な場合は名前空間プレフィックスを使います。 問題になる名前空間についてさらに詳しく見てみましょう。

  • winrt::MyProject。 この名前空間には投影型が含まれます。 投影型のオブジェクトはプロキシです。基本的に基になるオブジェクトへのスマート ポインターであり、基になるオブジェクトは自分のプロジェクト内で実装されている場合も、別のコンパイル単位内で実装されている場合もあります。
  • winrt::MyProject::implementation。 この名前空間には、実装型が含まれます。 実装型のオブジェクトはポインターではありません。値であり、完全な C++ スタック オブジェクトです。 実装型を直接構築しないでください。代わりに、winrt::make を呼び出し、テンプレート パラメーターとして実装型を渡します。 このトピックでは前に動作する winrt::make の例を示しました。別の例については、「XAML コントロール: C++/WinRT プロパティへのバインド」をご覧ください。 「直接割当ての診断」もご覧ください。
  • winrt::MyProject::factory_implementation。 この名前空間にはファクトリが含まれます。 この名前空間のオブジェクトでは、IActivationFactory がサポートされています。

次の表では、異なるコンテキストで使う必要がある最小限の名前空間の修飾を示します。

コンテキスト内の名前空間 実装型を指定する場合 実装型を指定する場合
winrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementation MyProject::MyRuntimeClass MyRuntimeClass

重要

実装から投影型を返す必要がある場合は、MyRuntimeClass myRuntimeClass; を記述することにより実装型をインスタンス化しないように注意してください。 その場合の適切な手法とコードは、このトピックのセクション「実装型とインターフェイスをインスタンス化して返す」で示してあります。

そのシナリオでの MyRuntimeClass myRuntimeClass; に関する問題は、スタック上に winrt::MyProject::implementation::MyRuntimeClass オブジェクトが作成されることです。 その (実装型の) オブジェクトは、いくつかの点で投影型のように動作します。同じ方法でそのオブジェクトのメソッドを呼び出すことができ、投影型に変換することさえできます。 ただし、スコープが終了すると、C++ の通常のルールに従って、オブジェクトは破棄されます。 そのため、そのオブジェクトへの投影型 (スマート ポインター) を返した場合、そのポインターは中ぶらりんになります。

この種のメモリ破損のバグを診断するのは困難です。 したがって、デバッグ ビルドの場合は、C++/WinRT アサーションでスタック検出機能を使ってこの誤りをキャッチすると役に立ちます。 ただし、コルーチンはヒープ上に割り当てられるので、誤りがコルーチン内にある場合、この誤りに関するヘルプは得られません。 詳細については、「直接割当ての診断」を参照してください。

さまざまな C++/WinRT 機能での投影型と実装型の使用

以下では、型が必要な C++/WinRT のさまざまな機能と、必要な型の種類 (投影型、実装型、両方) を示します。

機能 受入 メモ
T (スマート ポインターを表す) 投影 誤った実装型の使用については、「名前空間: 投影型、実装型、ファクトリ」の注意をご覧ください。
agile_ref<T> 両方 実装型を使う場合は、コンストラクターの引数を com_ptr<T> にする必要があります。
com_ptr<T> 実装 投影型を使うと、次のエラーが発生します: 'Release' is not a member of 'T'
default_interface<T> 両方 実装型を使った場合、最初に実装されたインターフェイスが返されます。
get_self<T> 実装 投影型を使うと、次のエラーが発生します: '_abi_TrustLevel': is not a member of 'T'
guid_of<T>() 両方 既定のインターフェイスの GUID が返されます。
IWinRTTemplateInterface<T>
投影 実装型を使ってもコンパイルされますが、それは誤りです。「名前空間: 投影型、実装型、ファクトリ」の注意をご覧ください。
make<T> 実装 投影型を使うと、次のエラーが発生します: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) 両方 実装型を使う場合は、引数を com_ptr<T> にする必要があります。
make_self<T> 実装 投影型を使うと、次のエラーが発生します: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> 投影 実装型を使った場合、既定のインターフェイスの文字列化された GUID が返されます。
weak_ref<T> 両方 実装型を使う場合は、コンストラクターの引数を com_ptr<T> にする必要があります。

均一コンストラクションのオプトインと、実装への直接アクセス

ここでは、オプトインされる C++/WinRT 2.0 の機能について説明します。ただし、新しいプロジェクトでは既定で有効になります。 既存のプロジェクトの場合は、cppwinrt.exe ツールを構成することでオプトインする必要があります。 Visual Studio で、プロジェクトのプロパティ [共通プロパティ]>[C++/WinRT]>[最適化][はい] に設定します。 その効果として、プロジェクト ファイルに <CppWinRTOptimized>true</CppWinRTOptimized> が追加されます。 また、コマンド ラインから cppwinrt.exe を呼び出すときにスイッチを追加するのと同じ効果があります。

-opt[imize] スイッチを使用すると、一般に "均一コンストラクション" と呼ばれる機能が有効になります。 均一 (または "統合") コンストラクションでは、C++/WinRT 言語プロジェクション自体を使用して、実装型 (アプリケーションで使用するためにコンポーネントによって実装された型) が効率的に作成され、使用されます。ローダーの問題はありません。

この機能について説明する前に、まず均一コンストラクションが "ない" 状況を見てみましょう。 説明のために、このサンプル Windows ランタイム クラスから始めます。

// MyClass.idl
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void Method();
        static void StaticMethod();
    }
}

C++/WinRT ライブラリを使い慣れている開発者は、次のようなクラスを使用できます。

using namespace winrt::MyProject;

MyClass c;
c.Method();
MyClass::StaticMethod();

また、示されている使用中のコードがこのクラスを実装する同じコンポーネント内に存在しなかった場合、これは完全に合理的です。 言語プロジェクションとして、C++/WinRT では開発者が ABI (Windows ランタイムで定義される COM ベースのアプリケーション バイナリ インターフェイス) から遮蔽されます。 C++/WinRT では、実装が直接呼び出されず、ABI を経由します。

その結果、MyClass オブジェクト (MyClass c;) を構築しているコード行で、C++/WinRT プロジェクションによってクラスまたはアクティベーション ファクトリを取得するために RoGetActivationFactory が呼び出され、そのファクトリを使用してオブジェクトが作成されます。 最後の行でも同様にファクトリを使用して、静的なメソッド呼び出しのように見えるものが作成されます。 このすべてにおいて、クラスを登録し、モジュールで DllGetActivationFactory エントリ ポイントを実装する必要があります。 C++/WinRT にはとても高速なファクトリ キャッシュがあるので、このいずれもコンポーネントを使用するアプリケーションの問題の原因にはなりません。 問題は、コンポーネント内で若干問題のある処理を行ったことだけです。

まず、C++/WinRT ファクトリ キャッシュの速度に関係なく、RoGetActivationFactory (またはファクトリ キャッシュを経由する後続の呼び出し) を通じた呼び出しは、実装を直接呼び出すよりも時間がかかります。 RoGetActivationFactory に続いて IActivationFactory::ActivateInstance、さらに QueryInterface を呼び出すことは、明らかに、ローカルに定義された型に対して C++ new 式を使用する場合ほど効率的ではありません。 その結果、経験豊富な C++/WinRT 開発者は、コンポーネント内にオブジェクトを作成するときの winrt::make または winrt::make_self ヘルパー関数の使用に慣れています。

// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };

しかし、ご覧のとおり、これはあまり便利でも簡潔でもありません。 オブジェクトを作成するにはヘルパー関数を使用する必要があります。また、実装型と投影型を明確に区別する必要もあります。

次に、プロジェクションを使用してクラスを作成することは、そのアクティベーション ファクトリがキャッシュされることを意味します。 通常、これは目的どおりですが、呼び出しを行っているのと同じモジュール (DLL) 内にファクトリが存在する場合は、事実上 DLL を固定し、アンロードされないようにしています。 多くの場合、これは問題になりませんが、一部のシステム コンポーネントではアンロードをサポートする "必要があります"。

ここで、"均一コンストラクション" という用語が登場します。 作成コードが、クラスを使用しているだけのプロジェクト内に存在するのか、それとも実際にクラスを "実装" しているプロジェクト内に存在するのかにかかわらず、同じ構文を自由に使用してオブジェクトを自由に作成できます。

// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;

-opt[imize] スイッチを使用してコンポーネント プロジェクトをビルドすると、言語プロジェクションを使用した呼び出しは、実装型を直接作成する winrt::make 関数の同じ効率的な呼び出しにコンパイルされます。 これにより、構文が単純で予測可能になり、ファクトリを使用した呼び出しによるパフォーマンスの低下が回避され、プロセス内のコンポーネントの固定が回避されます。 コンポーネント プロジェクトに加えて、これは XAML アプリケーションにも役立ちます。 同じアプリケーション内に実装されたクラスの RoGetActivationFactory をバイパスすることで、コンポーネントの外部にクラスがある場合とまったく同じ方法で (登録する必要なしに) クラスを構築できます。

均一コンストラクションは、ファクトリによって内部的に処理される "任意" の呼び出しに適用されます。 実際には、これは最適化がコンストラクターと静的メンバーの両方に対して機能することを意味します。 次に、元の例をもう一度示します。

MyClass c;
c.Method();
MyClass::StaticMethod();

-opt[imize] がない場合、最初のステートメントと最後のステートメントでは、ファクトリ オブジェクトを介した呼び出しが必要になります。 -opt[imize] が "ある" 場合、どちらも当てはまりません。 これらの呼び出しは、実装に対して直接コンパイルされ、インライン化される可能性もあります。 -opt[imize] (つまり "直接実装" アクセス) について話すときによく使用される他の用語にも言及しています。

言語プロジェクションは便利ですが、実装に直接アクセスできる場合は、それを利用すれば可能な範囲で最も効率的なコードを生成することができるので、そうするべきです。 C++/WinRT でこれを行うことができます。プロジェクションの安全性と生産性から離れることは強制されません。

言語プロジェクションがその実装型に到達して直接アクセスできるようにするにはコンポーネントが連携する必要があるので、これは破壊的変更です。 C++/WinRT はヘッダーのみのライブラリなので、内部を参照して何が行われているかを確認できます。 -opt[imize] がない場合、MyClass コンストラクターと StaticMethod メンバーは、このようなプロジェクションによって定義されます。

namespace winrt::MyProject
{
    inline MyClass::MyClass() :
        MyClass(impl::call_factory<MyClass>([](auto&& f){
		    return f.template ActivateInstance<MyClass>(); }))
    {
    }
    inline void MyClass::StaticMethod()
    {
        impl::call_factory<MyClass, MyProject::IClassStatics>([&](auto&& f) {
		    return f.StaticMethod(); });
    }
}

上記のすべてに追従する必要はありません。目的は、両方の呼び出しに call_factory という名前の関数の呼び出しが含まれていることを示すことです。 これは、これらの呼び出しにファクトリ キャッシュが含まれており、実装に直接アクセスしていないことを示す手掛かりです。 -opt[imize] が "ある" 場合、これらの同じ関数はまったく定義されていません。 代わりに、プロジェクションによって宣言され、その定義はコンポーネントに委ねられます。

その後、コンポーネントでは実装を直接呼び出す定義を提供できます。 これで、破壊的変更に到達しました。 これらの定義は -component-opt[imize] の両方を使用する場合に生成され、Type.g.cpp という名前のファイルに表示されます。ここで、Type は実装されているランタイム クラスの名前です。 そのため、既存のプロジェクト内で -opt[imize] を初めて有効にしたときに、さまざまなリンカー エラーが発生する可能性があります。 物事をまとめるには、生成されたファイルを実装に含める必要があります。

この例では、(-opt[imize] が使用されているかどうかに関係なく) MyClass.h はこのようになります。

// MyClass.h
#pragma once
#include "MyClass.g.h"
 
namespace winrt::MyProject::implementation
{
    struct MyClass : ClassT<MyClass>
    {
        MyClass() = default;
 
        static void StaticMethod();
        void Method();
    };
}
namespace winrt::MyProject::factory_implementation
{
    struct MyClass : ClassT<MyClass, implementation::MyClass>
    {
    };
}

MyClass.cpp にすべてがまとめられています。

#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
 
namespace winrt::MyProject::implementation
{
    void MyClass::StaticMethod()
    {
    }
 
    void MyClass::Method()
    {
    }
}

したがって、既存のプロジェクト内で均一コンストラクションを使用するには、各実装の .cpp ファイルを編集して、実装クラスの包含 (および定義) の後に #include <Sub/Namespace/Type.g.cpp> を含める必要があります。 このファイルでは、プロジェクションが未定義のままの関数の定義が提供されます。 MyClass.g.cpp ファイル内で、これらの定義は次のようになります。

namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

また、実装の効率的な直接呼び出しによってプロジェクションを正常に完了して、ファクトリ キャッシュの呼び出しを回避し、リンカーに適合させます。

-opt[imize] によって最後に行われることは、C++/WinRT 1.0 に必要だった厳密な型の結合を排除することで増分ビルドがはるかに高速になるような方法で、プロジェクトの module.g.cpp (DLL の DllGetActivationFactoryDllCanUnloadNow のエクスポートを実装するのに役立つファイル) の実装を変更することです。 これは通常、"型消去されたファクトリ" と呼ばれます。 -opt[imize] がない場合、コンポーネントに対して生成される module.g.cpp ファイルでは、最初にご自身のすべての実装クラス (この例では MyClass.h) の定義をインクルードします。 その後、このような各クラス用の実装ファクトリが直接作成されます。

if (requal(name, L"MyProject.MyClass"))
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}

この場合も、すべての詳細に追従する必要はありません。 確認しておくと役立つのは、これにはコンポーネントによって実装されているすべてのクラスに完全な定義が必要であるということです。 1 つの実装に対する変更が原因で module.g.cpp が再コンパイルされるので、これは内部ループに大きな影響を与える可能性があります。 -opt[imize] では、これが適用されません。 代わりに、生成された module.g.cpp ファイルに対して 2 つの処理が行われます。 1 つ目は、実装クラスがインクルードされなくなったことです。 この例では、MyClass.h がまったくインクルードされません。 代わりに、実装についての知識なしで実装ファクトリが作成されます。

void* winrt_make_MyProject_MyClass();
 
if (requal(name, L"MyProject.MyClass"))
{
    return winrt_make_MyProject_MyClass();
}

当然ながら、定義を含める必要はありません。また、winrt_make_Component_Class 関数の定義の解決はリンカーによって行われる必要があります。 もちろん、生成された (および均一コンストラクションをサポートするために以前に追加した) MyClass.g.cpp ファイルでもこの関数が定義されているので、これについて考える必要はありません。 この例に対して生成される MyClass.g.cpp ファイルの全体を次に示します。

void* winrt_make_MyProject_MyClass()
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

ご覧のように、winrt_make_MyProject_MyClass 関数では、実装のファクトリが直接作成されます。 これはすべて、特定の実装を適切に変更でき、module.g.cpp の再コンパイルはまったく不要であることを意味します。 module.g.cpp が更新されるのは Windows ランタイム クラスを追加または削除したときだけで、再コンパイルが必要です。

基底クラスの仮想メソッドのオーバーライド

基底クラスと派生クラスが両方ともアプリで定義されたクラスであっても、仮想メソッドが祖父母 Windows ランタイム クラスに定義されている場合、派生クラスは仮想メソッドで問題を発生する可能性があります。 実際には、これは XAML クラスから派生した場合に発生します。 このセクションの残りの部分は、派生クラスの例から続きます。

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

階層は Windows::UI::Xaml::Controls::Page<- BasePage<- DerivedPage です。 BasePage::OnNavigatedFrom メソッドは Page::OnNavigatedFrom を適切にオーバーライドしますが、DerivedPage::OnNavigatedFromBasePage::OnNavigatedFrom をオーバーライドしません。

ここでは、DerivedPageBasePage から IPageOverrides vtable を再利用します。これは、IPageOverrides::OnNavigatedFrom メソッドをオーバーライドできないことを意味します。 考えられる 1 つの解決策では、BasePage 自体をテンプレート クラスにし、その実装をヘッダー ファイルに完全に含める必要がありますが、これでは受け入れがたいほど物事が複雑になります。

回避策として、OnNavigatedFrom メソッドを基底クラスに明示的な仮想として宣言します。 このようにして、DerivedPage::IPageOverrides::OnNavigatedFrom の vtable エントリが BasePage::IPageOverrides::OnNavigatedFrom を呼び出すと、producer が BasePage::OnNavigatedFrom を呼び出し、最終的にこれが (その仮想性が原因で) DerivedPage::OnNavigatedFrom を呼び出します。

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        // Note the `virtual` keyword here.
        virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

これには、クラス階層のすべてのメンバーが、OnNavigatedFrom メソッドの戻り値とパラメーター型に同意する必要があります。 同意しない場合は、上記のバージョンを仮想メソッドとして使用し、代替をラップする必要があります。

注意

オーバーライドされたメソッドを IDL で宣言する必要はありません。 詳細については、オーバーライド可能なメソッドの実装に関する記事を参照してください。

重要な API