A colleague was trying to create a Windows Runtime delegate with WRL. Here’s a simplified version of what they wrote.
namespace ABI
{
using namespace ABI::Windows::UI::ViewManagement;
}
struct MyClass :
Microsoft::WRL::RuntimeClass<⟦...⟧>
{
HRESULT OnInputPaneShowing(ABI::InputPane* sender,
ABI::InputPaneVisibilityEventArgs* args);
void RegisterShowing(ABI::InputPane* inputPane);
EventRegistrationToken m_showingToken{};
};
When they tried to create the delegate,
void MyClass::RegisterShowing(ABI::InputPane* inputPane)
{
m_showingToken = inputPane->put_Showing(
Microsoft::WRL::Callback<ABI::ITypedEventHandler<
ABI::InputPane*,
ABI::InputPaneVisibilityEventArgs*>>(
&MyClass::OnInputPaneShowing).Get());
}
they got a quite lengthy error message.
wrl\event.h(348,165): error C2516: 'Microsoft::WRL::Details::RemoveReference<TCallback>::Type': is not a legal base class
with
[
TCallback=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
(compiling source file 'test.cpp')
wrl\internal.h(96,19):
see declaration of 'Microsoft::WRL::Details::RemoveReference<TCallback>::Type'
with
[
TCallback=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
wrl\event.h(348,165):
the template instantiation context (the oldest one first) is
test.cpp(134,30):
see reference to function template instantiation 'Microsoft::WRL::ComPtr<TDelegateInterface> Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,HRESULT(__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)>(TLambda &&) noexcept' being compiled
with
[
TDelegateInterface=ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,
TLambda=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
wrl\event.h(460,45):
see reference to function template instantiation 'Microsoft::WRL::ComPtr<TDelegateInterface> Microsoft::WRL::Details::DelegateArgTraits<HRESULT (__cdecl ABI::Windows::Foundation::ITypedEventHandler_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPane *>,ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *>>::* )(ABI::Windows::UI::ViewManagement::IInputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)>::Callback<ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,Microsoft::WRL::NoCheck,T>(TLambda &&) noexcept' being compiled
with
[
TDelegateInterface=ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,
T=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *),
TLambda=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
wrl\event.h(367,9):
while compiling class template member function 'Microsoft::WRL::ComPtr<TDelegateInterface>::ComPtr(Microsoft::WRL::ComPtr<U> &&,Details::EnableIf<Microsoft::WRL::Details::IsConvertible<U*,T*>::value,void*>::type *) noexcept'
with
[
TDelegateInterface=ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,
T=ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>
]
wrl\event.h(367,9):
see reference to class template instantiation 'Microsoft::WRL::Details::IsConvertible<Microsoft::WRL::Details::DelegateArgTraits<HRESULT (__cdecl ABI::Windows::Foundation::ITypedEventHandler_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPane *>,ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *>>::* )(ABI::Windows::UI::ViewManagement::IInputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)>::DelegateInvokeHelper<ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,T,Microsoft::WRL::NoCheck,ABI::Windows::UI::ViewManagement::IInputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *> *,ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*> *>' being compiled
with
[
T=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
wrl\internal.h(67,35):
see reference to class template instantiation 'Microsoft::WRL::Details::DelegateArgTraits<HRESULT (__cdecl ABI::Windows::Foundation::ITypedEventHandler_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPane *>,ABI::Windows::Foundation::Internal::AggregateType<ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *>>::* )(ABI::Windows::UI::ViewManagement::IInputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)>::DelegateInvokeHelper<ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*>,T,Microsoft::WRL::NoCheck,ABI::Windows::UI::ViewManagement::IInputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *>' being compiled
with
[
T=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
As is typical of C++ error messages, the interesting things are at the start and the end.
For the Microsoft Visual C++ compiler, the error message starts with the point where the compiler noticed the error, and it ends with a description of what piece of the original source code triggered that error. Though in this case, the “original source code” got buried in the middle because the compiler chose to show the instantiations oldest first.
The compiler ran into a problem trying to create a derived class and realizing that the base class is invalid.
error C2516: 'Microsoft::WRL::Details::RemoveReference<TCallback>::Type': is not a legal base class
with
[
TCallback=HRESULT (__cdecl MyClass::* )(ABI::Windows::UI::ViewManagement::InputPane *,ABI::Windows::UI::ViewManagement::IInputPaneVisibilityEventArgs *)
]
The TCallback
is the type of the &MyClass::OnInputPaneShowing
, and from the name, RemoveReference<T>
probably removes reference qualifiers. This is not reference-qualified, so it’s a nop, so the error message is saying
'decltype(&MyClass::Completed)': is not a legal base class
And that’s true. Pointers to member functions are not valid base classes.
If we look at the line in question:
template<typename TDelegateInterface, typename ...TArgs>
class DelegateArgTraits<
HRESULT (STDMETHODCALLTYPE TDelegateInterface::*)(TArgs...)>
{
template<typename TOtherDelegateInterface,
typename TCallback,
DelegateCheckMode checkMode,
typename... TOtherArgs>
struct DelegateInvokeHelper WrlFinal :
public ::Microsoft::WRL::RuntimeClass<
RuntimeClassFlags<Delegate>, TOtherDelegateInterface>,
RemoveReference<TCallback>::Type
{
DelegateInvokeHelper(TCallback&& callback) throw() :
RemoveReference<TCallback>::Type(
Details::Forward<TCallback>(callback)) {}
HRESULT STDMETHODCALLTYPE Invoke(TOtherArgs... args) throw() override
{
return DelegateTraits<checkMode>::CheckReturn(
(*this)(Details::Forward<TOtherArgs>(args)...));
}
};
⟦...⟧
};
we see that the DelegateInvokeHelper
removes references from TCallback
and then derives from it. In our case, the TCallback
was a pointer to a member function, and you can’t derive from that.
Even if we got past that problem, the next issue is that the Invoke
method tries to use the function call operator operator()
to invoke the TCallback
. But you can’t invoke pointers to member functions that way. You have to use the function call syntax in conjunction with an explicit object: (obj.*callback)(args)
.
Okay, so we see from the point of failure that the TCallback
parameter must satisfy two criteria.
- Must be a class type that can be derived from.
- Must have a function call operator that can be invoked with the delegate arguments.
It seems that the code wants to invoke the OnCompleted
method on the this
object, and we need to do that in the function call operator of a class type. Fortunately, C++ provides a handy syntax for this: A lambda.
void MyClass::RegisterShowing(ABI::InputPane* inputPane)
{
m_showingToken = inputPane->put_Showing(
Microsoft::WRL::Callback<ABI::ITypedEventHandler<
ABI::InputPane*,
ABI::InputPaneVisibilityEventArgs*>>(
[this](auto&&... args) {
return OnInputPaneShowing(args...);
}).Get());
}
(We can be lazy about how we forward the arguments because we know that the ABI parameters don’t have move constructors, so forwarding is the same as copying.)
Or if we look at the other overloads of WRL::Callback
, we see a family of callbacks that do exactly what we want.
template<
typename TDelegateInterface,
typename TCallbackObject,
typename... TArgs
>
ComPtr<TDelegateInterface> Callback(
_In_ TCallbackObject *object,
_In_ HRESULT (TCallbackObject::* method)(TArgs...)
);
Here is the implementation in the library:
template<typename TDelegateInterface,
typename TCallbackObject,
typename... TArgs>
ComPtr<typename Details::DelegateArgTraitsHelper<
TDelegateInterface>::Interface>
Callback(_In_ TCallbackObject *object,
_In_ HRESULT(TCallbackObject::* method)(TArgs...)) throw()
{
return Callback<TDelegateInterface>(
[=](auto&& ...args)
{ return ((*object).*(method))(args ...); });
}
The lambda is identical to the one we created manually, so we can just use
void MyClass::RegisterCompletion(ABI::IAsyncAction* action)
{
m_showingToken = inputPane->put_Showing(
Microsoft::WRL::Callback<ABI::ITypedEventHandler<
ABI::InputPane*,
ABI::InputPaneVisibilityEventArgs*>>(
this, &MyClass::OnInputPaneShowing).Get());
}
The purpose of the discussion was not to diagnose this specific use of the WRL::Callback
function but rather as an exercise in reading compiler error messages and reconciling the error text (that talks about what the compiler sees) with the original code in order to understand what the code was expecting, and why we failed to meet that expectation.
Final note: Since this
is captured as a raw pointer, we have to ensure that the MyClass
object is not destroyed if there is a chance that the event handler could be called (or is in the middle of a call). We get away with it here because the input pane raises its events on the UI thread, so we don’t have to worry about an event being in flight or on its way at the time we unregister it.
The post Learning to read C++ compiler errors: Not a legal base class appeared first on The Old New Thing.