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.

9 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… 🙂

  3. Chris

    So, somewhat related but as an old school c/c++ win32 api developer, coming back to the windows world is an odd journey. I may have a need to write an advanced desktop app (will be dealing with large files, doing a lot of processing of that data while also needing to have a rich UI with some standard UI components but they will need to be heavily themed for the brand/company so UX polish is key, heavy multi-threading, etc) but it will need to support Win 7 and up. Win 10 support alone won’t cut it. And, it most likely will not be a windows store app either as it likely need to interface with a USB device but it would be nice to have some type of path without an entire app re-write to get the app on the windows store. Again, UWP is not a requirement more of a *possible* requirement, down the road.

    But, honestly I don’t even really understand the purpose behind WRT. Was it to make COM and Win32 API programming easier (potentially safer?). What versions of Windows can an app support if I target WRT? I want the leanest memory footprint, most backward compatible and fastest UX possible with this app. My gut tells me I should stick to plain ole Win32 and raw C++. But, again, maybe I’m missing the whole point of why we even have a WRT.

    Anyway, I echo sentiments from across this blog where comments express similar either confusion on what/why is offered for desktop app dev on Windows today. But, in case anyone even reads this and knows the answer, is the only drawback from using plain C++ (no frameworks) and direct Win32 API calls the inability to get that app on the app store? Is that the only ‘platform’ restriction for Win32/c++ apps today? And, if I want to get some type of (unclear) benefit by using WRT, one of those benefits would be the ability to ship on the app store but certain APIs would not be available to me? Would I be able to shell other EXEs and read their stdout or comm with them via IPC (mem mapped files or named pipes, etc)? Would I be restricted on the threading I could do and would I be forced to make my app a series or single COM object?

    I do see there’s some seemingly interested support for XAML with WRT c++ apps, that might be nice since I’m doing a lot UI work. But, does that tie my app to only working on Windows 10?

    Thanks….. honestly what a mess although I know the level of effort it’s taken to build this library. But, jeez, I can see at least in part why Windows is experiencing some major release / QA issues.

  4. Felix Patschkowski

    Hey Kenny, at the end of the presentation you also give an outlook of the future of XAML and C++ integration. Can you already give a rough timeline when this feature will be available?

    1. Kenny Kerr Post author

      I have started working on this, so you should begin to see commits over on GitHub that you can start to play with and provide feedback. The full Xaml experience may also require changes to the Xaml compiler, but I’m not sure how long that will take as that is owned by a different team with their own schedule.

      1. Tim Weis

        The demo code seems to have sacrificed {x:Bind} for {Binding}. Is this just an admission to the early development stage, or are we going to have to wait for C++ to get meta classes (or even just an update to the XAML compiler), to get back the compile-time diagnostics of {x:Bind}?

      2. Kenny Kerr Post author

        xBind was introduced to deal with performance issues in C# apps and is not nearly as important for native C++ apps. It also requires more work from the toolchain, not just the language projection, and thus may not be available at first. We would however like to support both eventually as xBind could provide better compile-time diagnostics as you mentioned.

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 )

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