February 25th, 2025

C++/WinRT implementation inheritance: Notes on winrt::implements, part 5

Last time, we saw how you can derive from winrt::implements to form a new base class, in the case that the new base class is self-contained.

But what if the new base class isn’t self-contained? For example, maybe you want the base class to do the boring paperwork and defer to the derived class for the actual work.

namespace winrt
{
    using namespace winrt::Windows::Foundation;
    using namespace winrt::Windows::Web::Http;
    using namespace winrt::Windows::Web::Http::Filters;
}

struct ObserverFilter :
    winrt::implements<ObserverFilter, winrt::IHttpFilter>
{
    ObserverFilter(winrt::IHttpFilter const& downstream) :
        m_downstream(downstream) {}

    winrt::IAsyncActionWithProgress<
        winrt::HttpResponseMessage, winrt::HttpProgress>
        SendRequestAsync(winrt::HttpRequestMessage const& message)
    {
        ⟦ let derived class observe the message ⟧
        return m_downstream.SendRequestAsync(message);
    }
private:
    winrt::IHttpFilter m_downstream;
};

This Observer­Filter base class does the grunt work of being an HTTP filter (managing the downstream), with a place for the derived class to plug in and do the observing.

You can solve this problem by simply setting aside that you’re working in C++/WinRT and solving it using standard C++. In standard C++, you would use a pure virtual method.

struct ObserverFilter :
    winrt::implements<ObserverFilter, winrt::IHttpFilter>
{
    ObserverFilter(winrt::IHttpFilter const& downstream) :
        m_downstream(downstream) {}

    winrt::IAsyncActionWithProgress<
        winrt::HttpResponseMessage, winrt::HttpProgress>
        SendRequestAsync(winrt::HttpRequestMessage const& message)
    {
        ObserveMessage(message);
        return m_downstream.SendRequestAsync(message);
    }

    virtual void ObserveMessage(                      
        winrt::HttpRequestMessage const& message) = 0;
private:
    winrt::IHttpFilter m_downstream;
};

The derived class would then implement the Observe­Message method.

struct Derived : winrt::implements<Derived, ObserverFilter>
{
    virtual void ObserveMessage(
        winrt::HttpRequestMessage const& message) override
    {
        ⟦ observe the message ⟧
    }
};

If the helper base class wants to leave one of the interface methods completely to the derived class, you still have to define it in the base class. You can’t just leave it blank and say “Don’t worry, my derived class will take care of it.”

// A simple copy drop target provides no custom feedback
// and accepts anything by copy.
struct SimpleCopyDropTarget :
    winrt::implements<SimpleCopyDropTarget,
    winrt::ICoreDropOperationTarget>
{
    winrt::IAsyncOperation<winrt::DataPackageOperation>
        EnterAsync(winrt::CoreDragInfo const& info,
                   winrt::CoreDragUIOverride const&)
    {
        co_return GetOperation(info);
    }

    winrt::IAsyncOperation<winrt::DataPackageOperation>
        OverAsync(winrt::CoreDragInfo const& info,
                  winrt::CoreDragUIOverride const&)
    {
        co_return GetOperation(info);
    }

    winrt::IAsyncAction
        LeaveAsync(winrt::CoreDragInfo const&)
    {
        co_return;
    }

    // I want to let the derived class implement DropAsync

private:
    winrt::DataPackageOperation GetOperation(
        winrt::CoreDragInfo const& info)
    {
        return info.AllowedOperations() &&
               winrt::DataPackageOperation::Copy;
    }
};

This doesn’t work because C++/WinRT expects that if you declare that you implement an interface (in this case, ICore­Drop­Operation­Target), then you implement all of its methods. It requires this so that it can generate the ICore­Drop­Operation­Target vtable entries.

One solution is to declare the method as pure virtual, saying, “Trust me, the derived class will do it.”

struct SimpleCopyDropTarget :
    winrt::implements<SimpleCopyDropTarget,
    winrt::ICoreDropOperationTarget>
{
    ⟦ ... ⟧

    virtual winrt::IAsyncOperation<DataPackageOperation>
        DropAsync(winrt::CoreDragInfo const& info) = 0; 

private:
    ⟦ ... ⟧
};

This does have the downside of requiring the derived class to accept the parameters exactly as you specified, even if they might prefer a different form. For example, since Over­Async is a coroutine, the derived class might prefer to accept the info by value so that it can carry it across a suspension point.

There are many solutions to this problem. We’ll look at one of the next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments