C++/WinRT は、Windows ランタイム クラスの作成に役立つのと同様に、従来のコンポーネント オブジェクト モデル (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::implementswinrt::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();
}
}
サンプル クラスを使用するクライアント コードを次に示します。
// 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
の一部である ヘッダー ファイルを含める方法です。
wil\cppwinrt.h
ヘッダー ファイルは、unknwn.h
前に winrt/base.h
が含まれていることを確認するだけでなく、C++/WinRT と WIL が互いの例外とエラー コードを理解できるように設定します。
その後、を従来の COM インターフェイスの<> として使用でき、上記の例のコードは正常にコンパイルされます。
注
上記の例では、クライアント (クラスを使用するコード) で従来の COM サポートを有効にした後でも、サーバー (クラスを実装するコード) で従来の COM サポートを有効にしていない場合は、IInitializeWithWindow
ローカル (投影されていない) クラス
ローカル クラスは、同じコンパイル ユニット (アプリ、またはその他のバイナリ) で実装および使用されるクラスです。そのため、プロジェクションはありません。
クラシック 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 では無視されます。
COM コンポーネントの簡単な例
C++/WinRT を使用して記述された COM コンポーネントの簡単な例を次に示します。 これはミニ アプリケーションの完全な一覧であるため、新しい pch.h
プロジェクトの main.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 ディレクティブを削除します。 その代わりに、次のコードを挿入します (必要な lib、ヘッダー、型名を指定します)。 結果を次に示します。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を使用した
上記のコードのコクラスでは、INotificationActivationCallback::Activate メソッドを実装します。これは、ユーザーがトースト通知のコールバック ボタンをクリックしたときに呼び出される関数です。 ただし、その関数を呼び出す前に、コクラスのインスタンスを作成する必要があります。これは、IClassFactory::CreateInstance 関数のジョブです。
先ほど実装したコクラスは、通知用の COM アクティベーター と呼ばれ、上に示した callback_guid
識別子 (GUID型) の形式でクラス ID (CLSID) を持ちます。 この識別子は、後でスタート メニューのショートカットと Windows レジストリ エントリの形式で使用します。 COM アクティベーター CLSID、および関連付けられた COM サーバーへのパス (ここで構築している実行可能ファイルへのパス) は、トースト通知がコールバック ボタンがクリックされたときにインスタンスを作成するクラスを認識するメカニズムです (通知がアクション センターでクリックされたかどうか)。
COM メソッドを実装するためのベスト プラクティス
エラー処理とリソース管理の手法は、手を取り合うことができます。 エラー コードよりも、例外を使用する方が便利で実用的です。 そして、resource-acquisition-is-initialization (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,
®istration));
}
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 ボタンを
インプロセス COM サーバー
ToastAndCallback 上記のサンプル アプリは、ローカル (またはアウトプロセス) COM サーバーとして機能します。 これは、コクラスの CLSID の登録に使用する Windows レジストリ キー .exe
) 内でそのコクラスをホストします。
または 、ダイナミック リンク ライブラリ (.dll
) 内でコクラスをホストすることもできます (おそらく可能性が高くなります)。 DLL の形式の COM サーバーはインプロセス COM サーバーと呼ばれ、InprocServer32 Windows レジストリ キーを使用して登録されている CLSID によって示されます。
Dynamic-Link ライブラリ (DLL) プロジェクトを作成する
Microsoft Visual Studio で新しいプロジェクトを作成することで、インプロセス COM サーバーを作成するタスクを開始できます。 Visual C++>Windows Desktop>Dynamic-Link ライブラリ (DLL) プロジェクトを作成します。
新しいプロジェクトに C++/WinRT サポートを追加するには、「Windows デスクトップ アプリケーション プロジェクトを変更して C++/WinRT サポートを追加する」で説明されている手順に従います。
コクラス、クラス ファクトリ、およびインプロセス サーバーのエクスポートを実装する
dllmain.cpp
を開き、次に示すコード一覧を追加します。
C++/WinRT Windows ランタイム クラスを実装する DLL が既にある場合は、次に示す DllCanUnloadNow 関数が既にあります。 その DLL にコクラスを追加する場合は、DllGetClassObject 関数を追加できます。
互換性を維持する Windows ランタイム C++ テンプレート ライブラリ (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 (具体的には、
これは、
struct MyCoclass : winrt::implements<MyCoclass, IMyComInterface, winrt::Windows::Foundation::IInspectable>
{
// ...
};
別のインターフェイスから派生する COM インターフェイスを実装する
インターフェイスの派生は、従来の COM の機能です (Windows ランタイムからは意図的に存在しません)。 インターフェイスの派生の例を次に示します。
IFileSystemBindData2 : public IFileSystemBindData { /* ... */ };
IFileSystemBindData
// 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()
...
次の手順では、
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
- IInspectable インターフェイス
- IUnknown インターフェイス
- winrt::implements 構造体テンプレート
関連トピック
- C++/WinRT を使用して API を作成する
- C++/WinRT で COM コンポーネントを使用する
- ローカルトースト通知を送信する