A Windows Runtime ABI for Modern C++

Given some Windows Runtime type like Windows.UI.Core’s CoreWindow, you have the option to call its methods in the most natural way thanks to Modern C++ for the Windows Runtime:

using namespace Windows::UI::Core;
CoreWindow window = CoreWindow::GetForCurrentThread();

window.Activate();

The Activate method is a modern C++ projection of the underlying ABI call that the operating system expects, but because Modern is a library-based language projection you can easily call the underlying ABI directly simply by using the -> operator to reach down to the underlying interface pointer. What might be a little surprising is that the following won’t compile:

CoreWindow window = CoreWindow::GetForCurrentThread();

HRESULT hr = window->Activate();
// error C2039: 'Activate': is not a member of ...

That’s because there is no such method on the ABI interface. The method is actually called abi_Activate so you need to write the statement as follows:

HRESULT hr = window->abi_Activate();

Now why is that? That’s certainly not how the Windows SDK defines the ICoreWindow ABI.

The Modern compiler generates its own ABI so that’s not a problem, but why does it do that? Well there’s the actual reason and then there’s another good reason that Martyn Lovell came up with during a recent conversation while I was trying to explain my reason. I’ll start with his.🙂

Developers approaching the modern C++ language projection without a good understanding of modern C++ may not realize why object->Method is any different to object.Method. Indeed, given all the prior art thanks to C++/CX they could be forgiven for thinking that object->Method is the preferred approach. So had window->Activate compiled, they may not immediately realize the mistake (and the lack of error handling). On the other hand, forcing the developer to choose between window.Activate and window->abi_Activate makes the choice far more obvious. We might say that this is the design of least surprise.

OK, so that’s a good reason and I hadn’t thought of that. Now here’s the real reason. It is very difficult to project the Windows Runtime efficiently into standard C++ in a compile-time manner. Everything about the Modern language projection depends on compile-time type information to produce the fastest possible implementation that adds no run-time overhead. Now a language projection must deal with two rather disparate sets of abstractions. There are those involving binary COM interfaces that must be projected into C++, such as the ICoreWindow interface that is projected as the CoreWindow class. Then there are those involving C++ constructs like classes or lambdas that must be transported back through the runtime as COM interfaces. It is this latter camp where the trouble arises.

When implementing ABI interfaces in modern C++ it’s rather important that they be implemented to satisfy the vtable while also being projected into modern C++ without any additional overhead. Consider the IFrameworkViewSource interface as defined by the Windows SDK. I might need to implement both the virtual functions to satisfy the ABI as well as the modern implementation in the same class. This works reasonably well and might look something like this:

#include <windows.applicationmodel.core.h>
namespace abi = ABI::Windows::ApplicationModel::Core;

struct IFrameworkView {};

struct FrameworkViewSource : Implements<abi::IFrameworkViewSource>
{
    // ABI implementation ...
    virtual HRESULT __stdcall CreateView(abi::IFrameworkView ** view) noexcept override
    {
        // ...
        *view = detach(CreateView());
        // ...
    }

    // Modern implementation ...
    IFrameworkView CreateView()
    {
        // ...
    }
};

Incidentally, Implements is a variadic class template that implements both IUnknown and IInspectable and is included in the Modern library. Anyway, the compiler is happy and the implementation is clean and efficient. I then move on to the IFrameworkView interface and try to do the same thing and all of a sudden I have a problem:

struct FrameworkView : Implements<abi::IFrameworkView>
{
    // ABI implementation ...
    virtual HRESULT __stdcall Run() noexcept override
    {
        // ...
        Run();
        // ...
    }

    // Modern implementation ...
    void Run()
    {
        // ...
    }
};

The FrameworkView class suddenly has two methods with the same name lacking any parameters and thus the ABI’s virtual function and the modern projection differ only by return type and other factors that don’t affect overload resolution. This won’t compile. I would now have to tear apart the implementation to separate the vtable methods from the modern implementation and this adds additional complexity and run-time cost.

Instead, the Modern compiler produces an ABI for the Windows API that avoids the problem. It goes further and produces class templates employing the curiously recurring template pattern (CRTP) to implement the ABI while leaving a modern implementation up to the developer:

template <typename T>
struct IFrameworkViewT : Implements<IFrameworkView>
{
    // ...

    virtual HRESULT __stdcall abi_Run() noexcept override
    {
        return call([&]
        {
            static_cast<T *>(this)->Run();
        });
    }

    // ...
};

The app developer then doesn’t have to think about virtual functions and ABIs and can simply write a Run method in modern C++:

struct FrameworkView : IFrameworkViewT<FrameworkView>
{
    void Run()
    {
        using namespace Windows::UI::Core;

        CoreWindow window = CoreWindow::GetForCurrentThread();
        window.Activate();

        CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }
};

This same technique is used to power Windows Runtime components. The Modern compiler produces a modern projection of a component’s runtime classes that a developer would work on, but it also produces base class templates that implement all of the ABI virtual functions while delegating to a modern implementation.

Incidentally, the Modern compiler produced its own ABI long before I solved this problem with the abi_ preamble. It did so because the ABI provided by the Windows SDK is unnecessarily large due to the way the MIDL compiler produces definitions for both C and C++. The Modern ABI is much smaller and thus precompiles a whole lot quicker.

Now here’s another design question for you to ponder: why do the Modern library’s helper functions, such as call and detach above, start with a lower case letter when the convention for everything else mirrors that of the Windows Runtime? Shouldn’t they also start with an uppercase letter? Until next time, check out Modern C++ for the Windows Runtime!

4 thoughts on “A Windows Runtime ABI for Modern C++

  1. fourbadcats

    For the exercise: I suspect that there is a conflict with an interface member. With a search, I see IVdsOpenVDisk::Detach() so generating a modern projection which implements that interface could lead to overloads of Detach which could cause confusion.

    Still trying to wrap my head around how your projections work. I have to say this is quite impressive and I’ve d/lded and examined Modern just a bit. But… A question: Why is call() + lambda even necessary in the abi_Run()? Why isn’t the implementation simply return static_cast(this)->Run(); ??

    Reply
    1. Kenny Kerr Post author

      Because the modern implementation of Run may throw an exception. The call function catches any exception thrown by the lambda and returns it as an HRESULT.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s