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::ITypedEventHandlerABI::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::ITypedEventHandlerABI::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::ITypedEventHandlerABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*,ABI::Windows::Foundation::ITypedEventHandlerABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*,Microsoft::WRL::NoCheck,T>(TLambda &&) noexcept’ being compiled with [ TDelegateInterface=ABI::Windows::Foundation::ITypedEventHandlerABI::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::ITypedEventHandlerABI::Windows::UI::ViewManagement::InputPane*,ABI::Windows::UI::ViewManagement::InputPaneVisibilityEventArgs*, T=ABI::Windows::Foundation::ITypedEventHandlerABI::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::ITypedEventHandlerABI::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::ITypedEventHandlerABI::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::ITypedEventHandlerABI::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, Remove­Reference<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.


From The Old New Thing via this RSS feed