C++/WinRT での COM コンポーネントの作成

C++/WinRT は、Windows Runtime クラスを作成するのに役立つのと同様に、従来のコンポーネント オブジェクト モデル (COM) コンポーネント (またはコクラス) を作成するのに役立ちます。 このトピックでは方法を説明します。

COM インターフェイスに関する C++/WinRT の既定の動作

C++/WinRT の winrt::implements テンプレートは、ランタイム クラスとアクティベーション ファクトリの直接的または間接的な派生元です。

既定では、winrt::implements によって従来の COM インターフェイスが警告なしで無視されます。 したがって、従来の COM インターフェイスに対するすべての QueryInterface (QI) の呼び出しは、E_NOINTERFACE で失敗します。 既定では、winrt::implements では C++/WinRT インターフェイスのみがサポートされます。

  • winrt::IUnknown は C++/WinRT インターフェイスであるため、winrt::implements では winrt::IUnknown ベースのインターフェイスがサポートされます。
  • 既定では、winrt::implements::IUnknown 自体はサポートされません。

既定でサポートされていないそのようなケースに対処する方法は後で説明しますが、 次に示すのは既定での動作を示すコード例です。

// Sample.idl
namespace MyProject 
{
    runtimeclass Sample
    {
        Sample();
        void DoWork();
    }
}

// Sample.h
#include "pch.h"
#include <shobjidl.h> // Needed only for this file.

namespace winrt::MyProject::implementation
{
    struct Sample : implements<Sample, IInitializeWithWindow>
    {
        IFACEMETHOD(Initialize)(HWND hwnd);
        void DoWork();
    }
}

次に示すのは、Sample クラスを使用するクライアントのコードです。

// Client.cpp
Sample sample; // Construct a Sample object via its projection.

// This next line doesn't compile yet.
sample.as<IInitializeWithWindow>()->Initialize(hwnd); 

従来の COM サポートの有効化

さいわい、winrt::implements で従来の COM インターフェイスをサポートするために必要なのは、C++/WinRT のヘッダーをインクルードする前に unknwn.h ヘッダー ファイルをインクルードすることだけです。

それを明示的に行うことも、ole2.h などの他のヘッダー ファイルをインクルードすることで間接的に行うこともできます。 推奨される 1 つの方法は、wil\cppwinrt.h ヘッダー ファイルをインクルードすることです。これは、Windows 実装ライブラリ (WIL) に含まれます。 wil\cppwinrt.h ヘッダー ファイルを使うと、winrt/base.h の前に unknwn.h が確実にインクルードされるだけでなく、C++/WinRT と WIL が相互の例外とエラー コードを認識するように設定されます。

その後、as<> を従来の COM インターフェイスに対してを呼び出すことができ、上の例のコードがコンパイルされます。

注意

上の例では、クライアントで従来の COM サポート (クラスを使用しているコード) を有効にした後でも、サーバー (クラスを実装するコード) で従来の COM サポートを有効にしていない場合、QI for IInitializeWithWindow は失敗するため、クライアントでの as<> の呼び出しはクラッシュします。

ローカル (射影されていない) クラス

ローカル クラスは、同じコンパイル単位 (アプリ、またはその他のバイナリ) で実装および使用されるものです。そのため、射影はありません。

従来の COM インターフェイスのみを実装するローカル クラスの例を次に示します。

struct LocalObject :
    winrt::implements<LocalObject, IInitializeWithWindow>
{
    ...
};

この例を実装しても、従来の COM サポートを有効にしない場合、次のコードは失敗します。

winrt::make<LocalObject>(); // error: ‘first_interface’: is not a member of ‘winrt::impl::interface_list<>’

ここでも、IInitializeWithWindow は COM インターフェイスとして認識されないため、C++/WinRT ではそれが無視されます。 LocalObject のケースで、COM インターフェイスが無視されるという結果は、LocalObject にインターフェイスがまったくないことを意味します。 ただし、すべての COM クラスでは、少なくとも 1 つのインターフェイスが実装される必要があります。

COM コンポーネントの簡単な例

C++/WinRT を使って記述された COM コンポーネントの簡単な例を次に示します。 これは小さいアプリケーションの完全なリストなので、新しい Windows コンソール アプリケーション (C++/WinRT) プロジェクトの pch.hmain.cpp に貼り付けて、コードを試してみることができます。

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"

struct __declspec(uuid("ddc36e02-18ac-47c4-ae17-d420eece2281")) IMyComInterface : ::IUnknown
{
    virtual HRESULT __stdcall Call() = 0;
};

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();

    struct MyCoclass : winrt::implements<MyCoclass, IPersist, IStringable, IMyComInterface>
    {
        HRESULT __stdcall Call() noexcept override
        {
            return S_OK;
        }

        HRESULT __stdcall GetClassID(CLSID* id) noexcept override
        {
            *id = IID_IPersist; // Doesn't matter what we return, for this example.
            return S_OK;
        }

        winrt::hstring ToString()
        {
            return L"MyCoclass as a string";
        }
    };

    auto mycoclass_instance{ winrt::make<MyCoclass>() };
    CLSID id{};
    winrt::check_hresult(mycoclass_instance->GetClassID(&id));
    winrt::check_hresult(mycoclass_instance.as<IMyComInterface>()->Call());
}

C++/WinRT での COM コンポーネントの使用」も参照してください。

より現実的な興味深い例

このトピックの残りの部分では、C++/WinRT を使用して基本的なコクラス (COM コンポーネント、または COM クラス) とクラス ファクトリを実装する、最小限のコンソール アプリケーション プロジェクトを作成する手順について説明します。 アプリケーション例では、コールバック ボタン付きのトースト通知を配信する方法を示しています。コクラス (INotificationActivationCallback COM インターフェイスを実装する) によって、アプリケーションが起動され、ユーザーがトーストでそのボタンをクリックしたときにコールバックされます。

トースト通知機能領域の詳細な背景情報については、「ローカル トースト通知の送信」を参照してください。 ただし、ドキュメントのそのセクションにあるコード例では C++/WinRT を使用していないため、このトピックに示すコードを使用することをお勧めします。

Windows コンソール アプリケーション プロジェクト (ToastAndCallback) を作成する

まず、Microsoft Visual Studio で、新しいプロジェクトを作成します。 Windows コンソール アプリケーション (C++/WinRT) プロジェクトを作成し、ToastAndCallback という名前を付けます。

pch.h を開き、任意の C++/WinRT ヘッダーのインクルードの前に #include <unknwn.h> を追加します。 結果を次に示します。pch.h の内容をこのリストで置き換えることができます。

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

main.cpp を開き、プロジェクト テンプレートによって生成された using ディレクティブを削除します。 代わりに、次のコード (ライブラリ、ヘッダー、必要な型名を指定する) を挿入します。 結果を次に示します。使用する main.cpp の内容をこのリストで置き換えることができます (以下のリストでは main のコードも削除されています。これは、その関数を後で置き換えるためです)。

// main.cpp : Defines the entry point for the console application.

#include "pch.h"

#pragma comment(lib, "advapi32")
#pragma comment(lib, "ole32")
#pragma comment(lib, "shell32")

#include <iomanip>
#include <iostream>
#include <notificationactivationcallback.h>
#include <propkey.h>
#include <propvarutil.h>
#include <shlobj.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace Windows::Data::Xml::Dom;
using namespace Windows::UI::Notifications;

int main() { }

プロジェクトはまだビルドできません。コードを追加し終わった後、ビルドして実行するように求められます。

コクラスとクラス ファクトリを実装する

C++/WinRT では、コクラスとクラス ファクトリを winrt::implements 基本構造体から派生させることによって実装します。 上に示した 3 つの using ディレクティブの直後 (かつ、main の前) にこのコードを貼り付けて、トースト通知 COM アクティベーター コンポーネントを実装します。

static constexpr GUID callback_guid // BAF2FA85-E121-4CC9-A942-CE335B6F917F
{
    0xBAF2FA85, 0xE121, 0x4CC9, {0xA9, 0x42, 0xCE, 0x33, 0x5B, 0x6F, 0x91, 0x7F}
};

std::wstring const this_app_name{ L"ToastAndCallback" };

struct callback : winrt::implements<callback, INotificationActivationCallback>
{
    HRESULT __stdcall Activate(
        LPCWSTR app,
        LPCWSTR args,
        [[maybe_unused]] NOTIFICATION_USER_INPUT_DATA const* data,
        [[maybe_unused]] ULONG count) noexcept final
    {
        try
        {
            std::wcout << this_app_name << L" has been called back from a notification." << std::endl;
            std::wcout << L"Value of the 'app' parameter is '" << app << L"'." << std::endl;
            std::wcout << L"Value of the 'args' parameter is '" << args << L"'." << std::endl;
            return S_OK;
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }
};

struct callback_factory : implements<callback_factory, IClassFactory>
{
    HRESULT __stdcall CreateInstance(
        IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        return make<callback>()->QueryInterface(iid, result);
    }

    HRESULT __stdcall LockServer(BOOL) noexcept final
    {
        return S_OK;
    }
};

上記のコクラスの実装は、「C++/WinRT での API の作成」に示されているのと同じパターンに従っています。 そのため、同じ手法を使用して、COM インターフェイスと Windows ランタイム インターフェイスを実装できます。 COM コンポーネントと Windows ランタイム クラスの機能は、インターフェイスを介して公開されます。 すべての COM インターフェイスは、最終的に IUnknown インターフェイス から派生します。 Windows ランタイムは COM に基づいています。1 つの違いは、Windows ランタイム インターフェイスは最終的に IInspectable インターフェイス から派生することです (IInspectableIUnknown から派生)。

上記のコードのコクラスで、INotificationActivationCallback::Activate メソッドを実装しています。これは、ユーザーがトースト通知のコールバック ボタンをクリックしたときに呼び出される関数です。 しかし、その関数を呼び出す前に、コクラスのインスタンスを作成する必要があります。これを行うのは IClassFactory::CreateInstance 関数です。

今実装したコクラスは、通知の COM アクティベーターとして知られており、上に示すように callback_guid 識別子 (GUID 型) という形式のクラス ID (CLSID) が付いています。 その識別子は、スタート メニューのショートカットおよび Windows レジストリ エントリの形式で後から使用します。 COM アクティベーターの CLSID、およびそれに関連付けられた COM サーバーへのパス (ここでビルドしている実行可能ファイルへのパス) は、コールバック ボタンがクリックされたときに、どのインスタンスを作成するか (通知がアクション センターでクリックされたかどうか) をトースト通知に認識させるためのメカニズムです。

COM メソッドを実装するためのベスト プラクティス

エラー処理とリソース管理の手法は密接に関連しています。 エラー コードよりも例外を使用する方が便利で実用的です。 リソース取得は初期化である (RAII) という考え方を採用すれば、エラー コードを明示的にチェックしてからリソースを明示的に解放することを回避できます。 このような明示的なチェックによってコードは必要以上に複雑になり、隠れたバグが多数の場所に発生しやすくなります。 そうではなく、RAII を使用し、例外をスローおよびキャッチします。 そうすることで、リソースの割り当ては例外セーフとなり、コードがシンプルになります。

ただし、COM メソッドの実装をエスケープする例外を許可することはできません。 COM メソッドで noexcept 指定子を使用することにより、それを保証することができます。 メソッドが終了する前に例外を処理する限り、メソッドの呼び出し先内のどこで例外がスローされてもかまいません。 noexcept を使用していても、メソッドをエスケープする例外を許可すると、アプリケーションは終了します。

ヘルパーの型と関数を追加する

このステップでは、コードの残りの部分で使用するヘルパーの型と関数をいくつか追加します。 まず、main の直前に以下を追加します。

struct prop_variant : PROPVARIANT
{
    prop_variant() noexcept : PROPVARIANT{}
    {
    }

    ~prop_variant() noexcept
    {
        clear();
    }

    void clear() noexcept
    {
        WINRT_VERIFY_(S_OK, ::PropVariantClear(this));
    }
};

struct registry_traits
{
    using type = HKEY;

    static void close(type value) noexcept
    {
        WINRT_VERIFY_(ERROR_SUCCESS, ::RegCloseKey(value));
    }

    static constexpr type invalid() noexcept
    {
        return nullptr;
    }
};

using registry_key = winrt::handle_type<registry_traits>;

std::wstring get_module_path()
{
    std::wstring path(100, L'?');
    uint32_t path_size{};
    DWORD actual_size{};

    do
    {
        path_size = static_cast<uint32_t>(path.size());
        actual_size = ::GetModuleFileName(nullptr, path.data(), path_size);

        if (actual_size + 1 > path_size)
        {
            path.resize(path_size * 2, L'?');
        }
    } while (actual_size + 1 > path_size);

    path.resize(actual_size);
    return path;
}

std::wstring get_shortcut_path()
{
    std::wstring format{ LR"(%ProgramData%\Microsoft\Windows\Start Menu\Programs\)" };
    format += (this_app_name + L".lnk");

    auto required{ ::ExpandEnvironmentStrings(format.c_str(), nullptr, 0) };
    std::wstring path(required - 1, L'?');
    ::ExpandEnvironmentStrings(format.c_str(), path.data(), required);
    return path;
}

残りの関数と wmain エントリ ポイント関数を実装する

main 関数を削除し、代わりにこのコード リストを貼り付けます。これには、コクラスを登録してから、アプリケーションをコールバックできるトーストを配信するコードが含まれています。

void register_callback()
{
    DWORD registration{};

    winrt::check_hresult(::CoRegisterClassObject(
        callback_guid,
        make<callback_factory>().get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_SINGLEUSE,
        &registration));
}

void create_shortcut()
{
    auto link{ winrt::create_instance<IShellLink>(CLSID_ShellLink) };
    std::wstring module_path{ get_module_path() };
    winrt::check_hresult(link->SetPath(module_path.c_str()));

    auto store = link.as<IPropertyStore>();
    prop_variant value;
    winrt::check_hresult(::InitPropVariantFromString(this_app_name.c_str(), &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ID, value));
    value.clear();
    winrt::check_hresult(::InitPropVariantFromCLSID(callback_guid, &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, value));

    auto file{ store.as<IPersistFile>() };
    std::wstring shortcut_path{ get_shortcut_path() };
    winrt::check_hresult(file->Save(shortcut_path.c_str(), TRUE));

    std::wcout << L"In " << shortcut_path << L", created a shortcut to " << module_path << std::endl;
}

void update_registry()
{
    std::wstring key_path{ LR"(SOFTWARE\Classes\CLSID\{????????-????-????-????-????????????})" };
    ::StringFromGUID2(callback_guid, key_path.data() + 23, 39);
    key_path += LR"(\LocalServer32)";
    registry_key key;

    winrt::check_win32(::RegCreateKeyEx(
        HKEY_CURRENT_USER,
        key_path.c_str(),
        0,
        nullptr,
        0,
        KEY_WRITE,
        nullptr,
        key.put(),
        nullptr));
    ::RegDeleteValue(key.get(), nullptr);

    std::wstring path{ get_module_path() };

    winrt::check_win32(::RegSetValueEx(
        key.get(),
        nullptr,
        0,
        REG_SZ,
        reinterpret_cast<BYTE const*>(path.c_str()),
        static_cast<uint32_t>((path.size() + 1) * sizeof(wchar_t))));

    std::wcout << L"In " << key_path << L", registered local server at " << path << std::endl;
}

void create_toast()
{
    XmlDocument xml;

    std::wstring toastPayload
    {
        LR"(
<toast>
  <visual>
    <binding template='ToastGeneric'>
      <text>)"
    };
    toastPayload += this_app_name;
    toastPayload += LR"(
      </text>
    </binding>
  </visual>
  <actions>
    <action content='Call back )";
    toastPayload += this_app_name;
    toastPayload += LR"(
' arguments='the_args' activationKind='Foreground' />
  </actions>
</toast>)";
    xml.LoadXml(toastPayload);

    ToastNotification toast{ xml };
    ToastNotifier notifier{ ToastNotificationManager::CreateToastNotifier(this_app_name) };
    notifier.Show(toast);
    ::Sleep(50); // Give the callback chance to display.
}

void LaunchedNormally(HANDLE, INPUT_RECORD &, DWORD &);
void LaunchedFromNotification(HANDLE, INPUT_RECORD &, DWORD &);

int wmain(int argc, wchar_t * argv[], wchar_t * /* envp */[])
{
    winrt::init_apartment();

    register_callback();

    HANDLE consoleHandle{ ::GetStdHandle(STD_INPUT_HANDLE) };
    INPUT_RECORD buffer{};
    DWORD events{};
    ::FlushConsoleInputBuffer(consoleHandle);

    if (argc == 1)
    {
        LaunchedNormally(consoleHandle, buffer, events);
    }
    else if (argc == 2 && wcscmp(argv[1], L"-Embedding") == 0)
    {
        LaunchedFromNotification(consoleHandle, buffer, events);
    }
}

void LaunchedNormally(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    try
    {
        bool runningAsAdmin{ ::IsUserAnAdmin() == TRUE };
        std::wcout << this_app_name << L" is running" << (runningAsAdmin ? L" (administrator)." : L" (NOT as administrator).") << std::endl;

        if (runningAsAdmin)
        {
            create_shortcut();
            update_registry();
        }

        std::wcout << std::endl << L"Press 'T' to display a toast notification (press any other key to exit)." << std::endl;

        ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
        if (towupper(buffer.Event.KeyEvent.uChar.UnicodeChar) == L'T')
        {
            create_toast();
        }
    }
    catch (winrt::hresult_error const& e)
    {
        std::wcout << L"Error: " << e.message().c_str() << L" (" << std::hex << std::showbase << std::setw(8) << static_cast<uint32_t>(e.code()) << L")" << std::endl;
    }
}

void LaunchedFromNotification(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    ::Sleep(50); // Give the callback chance to display its message.
    std::wcout << std::endl << L"Press any key to exit." << std::endl;
    ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
}

アプリケーション例をテストする方法

アプリケーションをビルドし、それを管理者として少なくとも 1 回実行して、登録とその他のセットアップのコードを実行します。 それを行う 1 つの方法として、Visual Studio を管理者として実行し、その後、Visual Studio からアプリを実行します。 タスク バーの Visual Studio を右クリックしてジャンプ リストを表示し、ジャンプ リストの Visual Studio を右クリックして、[管理者として実行] をクリックします。 プロンプトに同意し、プロジェクトを開きます。 アプリケーションを実行するとき、アプリケーションが管理者として実行されているかどうかを示すメッセージが表示されます。 そうなっていない場合は、登録とその他のセットアップは実行されません。 アプリケーションの正常な動作のためには、その登録とその他のセットアップが少なくとも 1 回は実行される必要があります。

管理者としてアプリケーションを実行しているかどうかにかかわらず、トーストを表示させるには T キーを押します。 次に、ポップアップ表示されるトースト通知から直接またはアクション センターから ToastAndCallback コールバック ボタンをクリックすると、トースト通知が起動され、コクラスがインスタンス化されて、INotificationActivationCallback::Activate メソッドが実行されます。

インプロセス COM サーバー

上記の ToastAndCallback のアプリ例は、ローカル (またはプロセス外の) COM サーバーとして機能します。 これは、コクラスの CLSID を登録するために使用する Windows レジストリ キー LocalServer32 によって示されます。 ローカル COM サーバーによって、実行可能バイナリ (.exe) 内にコクラスがホストされます。

別の方法 (そして、おそらく可能性の高い方法) として、ダイナミック リンク ライブラリ (.dll) 内にコクラスをホストする選択もできます。 DLL 形式の COM サーバーは、インプロセス COM サーバーと呼ばれ、Windows レジストリ キー InprocServer32 を使用して登録される CLSID によって示されます。

Microsoft Visual Studio で新しいプロジェクトを作成することによって、インプロセス COM サーバーの作成作業を開始できます。 [Visual C++]>[Windows デスクトップ]>[ダイナミック リンク ライブラリ (DLL)] プロジェクトを作成します。

新しいプロジェクトに C++WinRT サポートを追加するには、「Windows デスクトップ アプリケーション プロジェクトを変更して C++/WinRT のサポートを追加する」に説明されている手順に従います。

コクラス、クラス ファクトリ、インプロセス サーバーのエクスポートを実装する

dllmain.cpp を開き、下に示すコード リストをそれに追加します。

C++/WinRT の Windows ランタイム クラスを実装する DLL が既にある場合は、下に示す DllCanUnloadNow 関数が既にあります。 その DLL にコクラスを追加する場合は、DllGetClassObject 関数を追加することができます。

互換性を維持したい既存の Windows ランタイム C++ テンプレート ライブラリ (WRL) コードがない場合は、示されているコードから WRL の部分を削除することができます。

// dllmain.cpp

struct MyCoclass : winrt::implements<MyCoclass, IPersist>
{
    HRESULT STDMETHODCALLTYPE GetClassID(CLSID* id) noexcept override
    {
        *id = IID_IPersist; // Doesn't matter what we return, for this example.
        return S_OK;
    }
};

struct __declspec(uuid("85d6672d-0606-4389-a50a-356ce7bded09"))
    MyCoclassFactory : winrt::implements<MyCoclassFactory, IClassFactory>
{
    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) noexcept override
    {
        try
        {
            return winrt::make<MyCoclass>()->QueryInterface(riid, ppvObject);
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }

    HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) noexcept override
    {
        // ...
        return S_OK;
    }

    // ...
};

HRESULT __stdcall DllCanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(GUID const& clsid, GUID const& iid, void** result)
{
    try
    {
        *result = nullptr;

        if (clsid == __uuidof(MyCoclassFactory))
        {
            return winrt::make<MyCoclassFactory>()->QueryInterface(iid, result);
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetClassObject(clsid, iid, result);
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...)
    {
        return winrt::to_hresult();
    }
}

弱参照のサポート

C++/WinRT の弱参照」も参照してください。

C++/WinRT (具体的には、winrt::implements 基本構造体テンプレート) では、使用している型で IInspectable (または IInspectable から派生する任意のインターフェイス) を実装する場合、自動的に IWeakReferenceSource が実装されます。

これは、IWeakReferenceSourceIWeakReference が Windows ランタイム型用に設計されているためです。 したがって、winrt::Windows::Foundation::IInspectable (または IInspectable から派生するインターフェイス) を実装に追加するだけで、コクラスに対する弱参照サポートを有効にすることができます。

struct MyCoclass : winrt::implements<MyCoclass, IMyComInterface, winrt::Windows::Foundation::IInspectable>
{
    //  ...
};

他から派生する COM インターフェイスを実装する

インターフェイスの派生は従来の COM の機能です (Windows ランタイムではこれは意図的に存在しません)。 次に示すのは、インターフェイスの派生の例です。

IFileSystemBindData2 : public IFileSystemBindData { /* ... */  };

実装する必要があるクラスを記述する場合は (IFileSystemBindDataIFileSystemBindData2 の両方など)、それを表現する最初の手順は、次のように、派生インターフェイスのみを実装することを宣言することです。

// pch.h
#pragma once
#include <Shobjidl.h>
...

// main.cpp
...
struct MyFileSystemBindData :
    implements<MyFileSystemBindData,
    IFileSystemBindData2>
{
    // IFileSystemBindData
    IFACEMETHOD(SetFindData)(const WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFindData)(WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };

    // IFileSystemBindData2
    IFACEMETHOD(SetFileID)(LARGE_INTEGER liFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFileID)(LARGE_INTEGER* pliFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(SetJunctionCLSID)(REFCLSID clsid) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetJunctionCLSID)(CLSID* pclsid) override { /* ... */ return S_OK; };
};
...
int main()
...

次の手順として、IID_IFileSystemBindData ("基本" インターフェイス) 用に (直接または間接的に)、MyFileSystemBindData のインスタンスに対して呼び出されるときに、Query­Interface が確実に成功するようにします。 これを行うには、winrt::is_guid_of 関数テンプレートの特殊化を提供します。

winrt::is_guid_of は可変個引数であるため、インターフェイスの一覧をそれに提供できます。 IFileSystemBindData2 のチェックに IFileSystemBindData のテストも含まれるように、特殊化を提供する方法を次に示します。

// pch.h
...
namespace winrt
{
    template<>
    inline bool is_guid_of<IFileSystemBindData2>(guid const& id) noexcept
    {
        return is_guid_of<IFileSystemBindData2, IFileSystemBindData>(id);
    }
}

// main.cpp
...
int main()
{
    ...
    auto mfsbd{ winrt::make<MyFileSystemBindData>() };
    auto a{ mfsbd.as<IFileSystemBindData2>() }; // Would succeed even without the **is_guid_of** specialization.
    auto b{ mfsbd.as<IFileSystemBindData>() }; // Needs the **is_guid_of** specialization in order to succeed.
}

winrt::is_guid_of の特殊化は、プロジェクト内のすべてのファイルで同一である必要があり、インターフェイスが winrt::implements または winrt::delegate テンプレートによって使用される時点で表示されます。 通常は、これを共通のヘッダー ファイルに格納します。

重要な API