Meet C++/WinRT 2.0: Diagnosing Direct Allocations

Yesterday we officially announced C++/WinRT 2.0 at Build 2019 here in Seattle! It wasn’t exactly a very well-kept secret as we’ve been developing C++/WinRT in the open for some time over on GitHub. Still, it was nice to finally get in front of a crowd of C++ developers and talk about some of the amazing improvements we’ve been working on over the last year.

Admittedly a lot of the work is of the fit-and-finish variety but that makes sense given the level of maturity that we have achieved with C++/WinRT. C++/WinRT now powers most new Windows APIs and is used to build everything from the Windows shell to Office apps to HoloLens to the Xbox, not to mention many well-known third-party apps that many of you use every day.

Still, there is a lot to talk about, so I thought I’d once again share what’s new. And I’ll begin with diagnosing or detecting direct allocations. It doesn’t sound too exciting, but if you’ve ever spent a sleepless night debugging a mysterious crash or corruption bug, you’ll appreciate this one very much.

First, let’s consider how we even got in this situation. Consider a simple implementation of IStringable:

struct Stringable : implements<Stringable, IStringable>
    hstring ToString() const { return L"Stringable"; }

Now imagine you need to call a function from within the implementation that expects an IStringable as an argument:

void Print(IStringable const& stringable)
    printf("%ls\n", stringable.ToString().c_str());

The trouble is that Stringable is not an IStringable. The former is an implementation of the IStringable interface, whereas the IStringable type is a projected type. The nebulous space between an implementation and the projection can be confusing. To try to make the implementation feel a bit more like the projection, the implementation provides implicit conversions to each of the projected types that it implements. Of course, we cannot simply do this:

struct Stringable : implements<Stringable, IStringable>
    hstring ToString() const;

    void Call()

Instead, we need to get a reference so that conversion operators may be used as candidates for resolving this call:

void Call()

So this works great. An implicit conversion provides a (very efficient) conversion from the implementation type to the projected type and this is very convenient for many scenarios. Without this facility, it would be very cumbersome authoring many implementation types. Provided you only use the make function template (or make_self) to allocate the implementation all is well:

IStringable stringable = make<Stringable>();

Still, implicit conversions can land you in some trouble. Consider this very bad helper function:

IStringable MakeStringable()
    return Stringable(); // Bad

Or even just this seemingly harmless statement:

IStringable  stringable = Stringable(); // Also bad

Unfortunately, this compiled with C++/WinRT 1.0 because of that implicit conversion. This is very bad because we are now potentially returning a projected type that points to a reference-counted object whose backing memory is on the ephemeral stack. Here’s something else that compiled with version 1:

Stringable* stringable = new Stringable(); // Just don't

Raw pointers are bad news. Just don’t do it. C++/WinRT goes out of its way to make everything insanely efficient without ever forcing you into using pointers directly. Here’s something else that compiled with version 1:

auto stringable = std::make_shared<Stringable>(); // Really bad

This doesn’t even make sense. Now we have two different reference counts for the same object. WinRT (and COM before it) is based on intrinsic reference counting. This is not compatible with shared_ptr. There’s nothing wrong with shared_ptr but it is completely unnecessary when sharing COM or WinRT objects. Finally, this also worked with version 1:

auto stringable = std::make_unique<Stringable>(); // Really?

This is again rather questionable because the unique ownership is in opposition to the shared lifetime of the Stringable’s intrinsic reference count.

The good news is that with C++/WinRT 2.0 all of these attempts to directly allocate implementation types leads to a compiler error. That’s the best kind of error and certainly infinitely better than a mysterious runtime bug. Whenever you need to make an implementation you can simply use the make function template. And now, if you forget to do so, you will be greeted with a compiler error alluding to this with a reference to an abstract function named use_make_function_to_create_this_object. It’s not exactly a static_assert but it’s close. Still, this is the most reliable way of detecting all these abuses.

It does mean that we need to place a few minor constraints on the implementation. Given that we’re relying on the absence of an override to detect direct allocation, the make function template must somehow satisfy the abstract virtual function with an override. It does so by deriving from the implementation with a final class that provides the override. There are a few things to observe about this process.

First, the virtual function is only present in debug builds. This means that detection is not going to affect the size of the vtable in optimized builds.

Second, since the derived class that the make function uses is final it means that any devirtualization that the optimizer can possibly deduce will take place even if you previously chose not to mark your implementation class as final. So this is an improvement. The converse is that your implementation cannot be final. Again, this is of no consequence because the instantiated type will always be final.

Third, nothing prevents you from marking any virtual functions in your implementation as final. Of course, C++/WinRT is very different to COM and implementations like WRL, where everything about your implementation tends to be virtual. In C++/WinRT, the virtual dispatch is limited to the ABI (which is always final) and your implementation methods rely on compile-time or static polymorphism. This avoids unnecessary runtime polymorphism and also means there’s precious little reason for virtual functions in your C++/WinRT implementation. This is very good thing and leads to far more predictable inlining.

Fourth, since the make function injects a derived class, your implementation cannot have a private destructor. Private destructors were popular with COM implementations because again everything was virtual and it was common to deal directly with raw pointers and thus was easy to accidentally call delete instead of Release. As I’ve stated, C++/WinRT goes out of its way to make it hard for you to deal directly with raw pointers. You would really have to go out of your way to get a raw pointer in C++/WinRT that you could potentially call delete on. Value semantics means that you are dealing with values and references and rarely with pointers.

So, C++/WinRT challenges our preconceived notions of what it means to write COM code. And that’s perfectly reasonable because WinRT is not COM. COM is the assembly language of the Windows Runtime. It should not be the code you write every day. Instead, C++/WinRT gets you to write code that is more like modern C++ and far less like COM.

That’s all for today. Stay tuned for next time where I’ll show you another great feature that also benefits from having a public destructor.

4 thoughts on “Meet C++/WinRT 2.0: Diagnosing Direct Allocations

  1. PELock

    You’re out of touch with regular programmers dude. All that hustle and spaghetti code just to use a simple string? Really? Wow. That really makes me NOT to use this technology at all.

    1. Kenny Kerr Post author

      IStringable is just a foundational interface that I like to use in examples because it is simple enough not to detract from the point I’m trying to make, which is not about strings. 😉

  2. max

    Hey kenny, I’ve been using C++/WinRT 1.0 to write multiple Directx 11 and 12 apps and it’s been fantastic so I want to say thanks to you and your team. I have a question about something you mention in your presentation. How and where is the init_apartment call being made for us now that it’s unnecessary to call ourselves?

    Do you plan on releasing some kind of C++/WinRT internals course in the future? Maybe? 🙂 I’ve been trying to understand how it works under the hood but it’s really hard to navigate through so much generic template code. It would be very interesting to learn about it now that it’s open source.

    1. Kenny Kerr Post author

      Thanks Max! The implicit MTA is created if and when the call to load a given activation factory fails. This will only fail if you don’t already have an apartment initialized, hence the on-demand nature. Activation factories are loaded (and cached) whenever you make a call in C++ to a constructor or static method, so this covers most scenarios where you would previously have had to call init_apartment explicitly. You can see the implementation of this here.

      I’d love to do an internals course on C++/WinRT – just need to find the time… 🙂


Leave a Reply

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

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

Google photo

You are commenting using your Google 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 )

Connecting to %s