Last time, we smuggled an arbitrary C++ value inside an IInspectable
but noted the lack of safety. So let’s add some safety.
To ensure that the object is in-process, you can declare the interface as “local”, meaning that the interface is for use only within a single process, and the MIDL compiler will not generate proxies for it. Even easier is just defining the interface manually, without involving the MIDL compiler at all.
struct DECLSPEC_UUID("⟦some guid⟧") IValueAsInspectable : ::IUnknown { };
The trick here is that manually defining the interface lets us put C++ stuff inside it.
template<typename T> struct ValueAsInspectable; struct DECLSPEC_UUID("⟦some guid⟧") IValueAsInspectable : ::IUnknown { std::type_info const* type; template<typename T> T& get_value() { if (*type != typeid(T)) { throw std::bad_cast(); } return static_cast<ValueAsInspectable<T>*>(this) ->value; } }; template<typename T> struct ValueAsInspectable : winrt::implements<ValueAsInspectable<T>, winrt::Windows::Foundation::IInspectable, IValueAsInspectable> { T value; template<typename...Args> ValueAsInspectable(Args&&... args) : value{ std::forward<Args>(args)... } { this->>type = std::addressof(typeid(T)); } };
The IValueAsInspectable
has a COM interface ID, so it can be queried for. But once you get it, you can call the C++ method get_value<T>()
. That method first validates that you are using the matching T
, throwing a bad_cast
exception if not. If you pass that test, then it returns a reference to the value hiding inside.
And for convenience, we can add these helpers. The first two we have seen already:
template<typename T, typename...Args> winrt::Windows::Foundation::IInspectable MakeValueAsInspectable(Args&&... args) { return winrt::make<ValueAsInspectable<T>>( std::forward<Args>(args)...); } template<typename T> winrt::Windows::Foundation::IInspectable MakeValueAsInspectable(T&& arg) { return winrt::make<ValueAsInspectable< std::remove_reference_t<T>>>( std::forward<T>(arg)); }
Next are functions for extracting the value from an object that we believe to be a ValueÂAsÂInspectable<T>
.
template<typename T> T& ValueRefFromInspectable( winrt::Windows::Foundation::IInspectable const& arg) { return arg.as<IValueAsInspectable>()-> get_value<T>(); } template<typename T> T CopyValueFromInspectable( winrt::Windows::Foundation::IInspectable const& arg) { return ValueRefFromInspectable<T>(arg); } template<typename T> T MoveValueFromInspectable( winrt::Windows::Foundation::IInspectable && arg) { return std::move(ValueRefFromInspectable<T>(arg)); }
The basic function is ValueÂRefÂFromÂInspectable<T>
which produces an lvalue reference from an inspectable that we assume represents a ValueÂAsÂInspectable<T>
. (If we’re wrong, it throws a bad_cast
exception.) Building on that are two functions which either copy or move the value out of the ValueÂAsÂInspectable<T>
.
I gave the function that returns an lvalue name that emphasizes that you get a reference from the inspectable, hoping to prevent people from getting a reference to an expiring inspectable:
// Code in italics is wrong // Don't do this. auto& value = ValueRefFromInspectablt<Widget>( co_await DoSomethingAsync());
Note also that if you modify the value reference or move the value out of the inpectable, this affects the underlying object, which means that other people that have a reference to the same inspectable will see the object change state.
void MutateTheWidget(winrt::Windows::Foundation::IInspectable obj) { auto& widget = ValueRefFromInspectable<Widget>(obj); ⟦ do something that modifies the widget ⟧ } winrt::fire_and_forget Sample() { auto obj = co_await DoSomethingAsync(); MutateTheWidget(obj); auto& widget = ValueRefFromInspectable<Widget>(obj); // this code sees that the widget has been mutated }
But really, if you need a coroutine that produces a non-Windows Runtime type, then use a non-Windows Runtime coroutine library. I’m personally partial to wil::task
(and its COM-aware buddy wil::com_task
), seeing as I wrote it.
0 comments
Be the first to start the discussion.