The API behind the API

Despite what anyone might tell you, the Windows Runtime API is not a clean break from the past. Like .NET before it, WinRT includes a backdoor without which it would be practically useless. The Common Language Runtime’s backdoor was called Platform Invocation Services or P/Invoke for short. It was amazingly powerful, but also complex and troublesome. WinRT’s backdoor is a lot simpler. It’s called reinterpret_cast.

As I’ve already illustrated in The Road to Windows 8 and Windows 8, where’d you put my HWND?!, WinRT is projected into C++ through a set of extensions that, among other things, allows the compiler to insert code to automatically manage reference counts as if a COM smart pointer class were used. For example, every WinRT application (or at least those that run within an app container) has a CoreWindow:

auto w = CoreWindow::GetForCurrentThread();

If you were feeling verbose, you might write:

CoreWindow ^ cw = CoreWindow::GetForCurrentThread();

The ^, pronounced “hat”, defines a handle-to-object, which the compiler treats as a pointer-to-object with intrusive reference counting provided by the object’s IUnknown interface and administered by the compiler.

Without WinRT’s backdoor however, no Windows Store or Windows Phone app would be able to function. In the case of CoreWindow, the application ultimately needs to bind the application’s swap chain or composition target to the HWND that the CoreWindow represents. Without reinterpret_cast, this would not be possible. I’m obviously speaking of reinterpret_cast in the proverbial sense. You don’t need to tell me that a C-style cast will do or that it could all be done without /ZW.

CoreWindow is not alone. While ICoreWindowInterop may not be documented, another WinRT type, namely SwapChainBackgroundPanel, openly flaunts its “other” API. It is after all, the only reason for this XAML type’s existence. The ISwapChainBackgroundPanelNative interface provides the only panel method that you really need to use.

SwapChainBackgroundPanel ^ panel = …
IUnknown * unknown = reinterpret_cast<IUnknown *>(panel);
ComPtr<ISwapChainBackgroundPanelNative> native;
HR(unknown->QueryInterface(native.GetAddressOf()));

There are other examples, but the point is that there is again an API behind the API.

As developers begin to use C++/CX more, I felt it would be useful to offer a littler helper to make access to this backdoor a little more convenient, and safe.

template <typename To>
ComPtr<To> winrt_cast(Object ^ from)
{
ComPtr<To> to;
HR(reinterpret_cast<IUnknown *>(from)->QueryInterface(to.GetAddressOf()));
return to;
}

With the winrt_cast function template, you can quickly and easily reach in and retrieve the native or interop interface that a particular WinRT type might provide.

auto native = winrt_cast<ISwapChainBackgroundPanelNative>(panel);

auto interop = winrt_cast<ICoreWindowInterop>(window);

The winrt_cast function ensures that the resulting “hat-less” COM pointer is safely wrapped inside WRL’s excellent smart pointer. Since ComPtr is move-aware, returning it in this way is guaranteed not to introduce an unnecessary reference-counting heartbeat. Error handling is also not an issue. Although QueryInterface is traditionally analogous to dynamic_cast in the sense that it allows feature discovery at run-time, in this case the WinRT types are guaranteed to provide the particular interfaces. Without this capability, they would be useless. Still, it’s up to you to decide how best to deal with errors in your application or library. I tend to define HR as follows.

#ifdef DEBUG
#define HR(expression) ASSERT(S_OK == (expression))
#else
inline void HR(HRESULT hr) { if (S_OK != hr) throw Exception::CreateException(hr); }
#endif

If I’ve done something wrong, I’m treated to an assertion during development. If something goes horribly wrong at run-time, my application is quickly torn down. Obviously, as with P/Invoke, this is not something to be used lightly or without thinking. Don’t go using winrt_cast on a String^, which holds an HSTRING rather than IUnknown pointer. You can for example disable this helper for strings as follows:

template <typename To> ComPtr<To> winrt_cast(String ^);

Indeed, this solution is for those cases where you know a particular WinRT type exposes a particular COM interface outside of the discoverable type system. James McNellis offers a slightly more verbose solution, but one that is more widely applicable here. He also discusses hats in a lot more detail here.

Hope this helps.

 

6 thoughts on “The API behind the API

  1. Tom Kirby-Green

    I like the winrt_cast function, it reminds me somewhat of the marshal_as family of template functions for .NET / native thunking. Generally speaking I have somewhat fewer concerns about ’compiler look the other way please’ casts now that Visual Studio finally ships with decent native Static Code Analysis to help blance out these kinds of type shenanigans.

    Reply
    1. James McNellis (@JamesMcNellis)

      reinterpret_cast is still quite dangerous because static code analysis often has no choice but to assume the author of the code knows what he is doing. I strongly encourage wrapping the use of heavyweight casts (like reinterpret_cast or C casts) in cast function templates (like Kenny’s winrt_cast or my AsInspectable) to restrict use of the casts to well-defined use cases.

      For example: In this case, it’s guaranteed that a Platform::Object^ can be reinterpret_cast’ed to an IInspectable* (or an IUnknown*). But you can’t reinterpret_cast an arbitrary T^ to IInspectable* because not all T^’s are represented by an IInspectable*. A Platform::String^ is represented by an HSTRING, and delegate interfaces are derived directly from IUnknown* (not IInspectable*). By using winrt_cast (or an equivalent function), you can easily avoid common errors caused by overgeneralizing a valid use to invalid uses.

      Reply
  2. Tom KirbyGreen

    When I say ‘now has’ I mean available to all C++ devs and not just those who can afford Visual Studio Ultra Expensive Edition :-)

    Reply
  3. Sole42

    Why make it simple when it can be complicated?
    I bet OSX or Android developers don’t have to start from zero with every new distro.

    Reply
    1. Tom Kirby-Green

      Speaking as someone with approximately 20 years experience programming against various Microsoft runtimes and who’s also a registered iOS develper too I feel comfortable saying that WinRT is not more complex or difficult to grasp than say Cocoa Touch, not is it a ‘new distro’ (in that its just a increment on a existing technology).

      When wearing my iOS developer hat I often wish that Apple had 1% of the learning materials that Microsoft makes available. Through MSDN you have access to hundreds of generally high quality code samples and in Channel 9 you have an education resource that is frankly unmatched by any of the other large tech companies out there (and to Channel 9s credit they often commission and host material that is truly cross platform in nature, ie of relevance and value to OS X, iOS, Android and Whatever OS as it is to just ‘Windows’ developers).

      Reply
  4. Kenny Kerr Post author

    It was mostly about how rather than why. I’ve described why before. I also go into why in a lot of detail in part 2 of Direct2D Fundamentals. I will also be covering why in some of my MSDN Magazine columns this year. For now, if you don’t need it, then all the better. But when you do, you’ll know how. :)

    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