Under the Covers of WinRT Using C++

September 17, 2011

5 comments

The WinRT type system relies strongly on WinRT components, which are COM objects implementing a specific set of interfaces and adhering to a certain ABI (Application Binary Interface). We will examine here this ABI and how C++ compiler extensions help reference that ABI without exposing the nitty-gritty details of dealing with COM interfaces and COM activation.

A WinRT component implements the IInspectable interface, which derives from IUnknown (however, WinRT components do not have dual interfaces, i.e. they do not implement IDispatch). The IInspectable interface contains three methods: GetIids, which returns the interfaces implemented by the component; GetRuntimeClassName, which returns the fully-qualified name of the component; and GetTrustLevel, which returns the component’s trust level that can be used to control component activation.

IInspectable is used by the dynamic language bindings from JavaScript. Given a reference to a WinRT component, the JavaScript interpreter can call the IInspectable::GetRuntimeClassName method and obtain the fully-qualified class name of the WinRT component. Using the namespace name the interpreter can ask Windows to load the metadata file (.winmd) describing the component, and from the metadata determine how to use the interfaces implemented by the component.

When WinRT components are accessed from C++ or from .NET languages, there is no need for the IInspectable interface – IUnknown::QueryInterface suffices for all intents and purposes. In fact, .NET “interop” with WinRT relies on simple COM interop – an RCW created by the CLR manages the lifetime of the underlying WinRT component. For C++ language bindings, the story is somewhat more complicated, and the language extensions come into play.

The C++ language extensions (enabled by the /ZW switch) map WinRT to standard C++ patterns like constructors, destructors, class methods, and exceptions. For example, the language extensions hide the COM-style activation (RoActivateInstance) behind a constructor call (albeit with the ref new keyword), and hide reference counting (IUnknown::AddRef and Release) by automatically managing references. The syntax of these extensions is very similar to C++/CLI – if you know the latter, you will certainly feel at home with the former. Some highlights:

public ref class SomeClass sealed – defines a COM object implementing IInspectable (and IUnknown). You can implement interfaces by deriving from an “interface class”.

property SomeType Prop { SomeType get(); void set(SomeType); } – defines a property, very similar to a property in C++/CLI.

delegate SomeType Foo(SomeOtherType …) – defines a delegate, which is a glorified function pointer, that can subsequently be used to define events.

…and the list goes on and on – on one hand, the syntax is very similar to C++/CLI, and is indeed inspired by .NET in every detail, and on the other hand, the result is a native C++ class that implements pure COM interfaces through a lot of language magic.

The C++ compiler generates inline functions on the consumer side and the component side, as necessary: on the consumer side, COM HRESULTs are converted to exceptions and COM “retval” arguments are converted to return values; on the component side, WinRT and C++ exceptions are converted to HRESULTs and return values are converted to “retval” arguments.

//Consumer-side stub pseudo-code
inline int Computer::Compute(int first, int second) {
    int result;
    HRESULT hr = ___impl_Compute(this, first, second, &result);
    if (hr != 0) ___impl_throw_for_hr(hr);
    return result;
}
//Component-side stub pseudo-code
HRESULT __stdcall ___impl_Compute(Computer* cmp,
 
int first, int second, int* result) {
    try { *result = cmp->Compute(first, second); }
    catch(Platform::Exception^ e) { return e->HResult; }
    return S_OK;
}

Note that the only HRESULT, which does not represent an exceptional condition, is S_OK (numeric value 0). No more positive HRESULTs (such as S_FALSE) that need to be checked explicitly by the caller. By the way, the inline wrappers can be called directly, bypassing the C++ language extensions.

An even more hardcore alternative is to reach directly into the vtable behind the WinRT object reference. Indeed, the ABI dictates that WinRT object references are simply pointers to a bunch of vtables, so all that remains is choosing the right vtable and indexing within it the desired method:

//Assuming 32-bit pointers
Computer^ computer = ref new Computer;
int* vtable_array = (int*)computer;
int* icomputer_vtable = (int*)vtable_array[0];
int* compute_will_be_fptr = (int*)icomputer_vtable[6];
typedef HRESULT (__stdcall *compute_fptr_t)(Computer*,
 
int, int, int*);
compute_fptr_t compute_fptr = (compute_fptr_t)compute_will_be_fptr;
//…use compute_fptr freely :-)

Another way of putting it is: “a hat is a pointer to a pointer to an array of function pointers”. You can inspect the precise layout of the WinRT component’s C++ class in memory using the /d1reportSingleClassLayout<CLASSNAME> undocumented compiler switch. For example, for the default component created by the Visual Studio C++ WinRT Component project template, this switch reports the following layout and vtable structure:

//The WinRT class:
public ref class WinRTComponent sealed {
    int _propertyAValue;
public:
    WinRTComponent();
    ~WinRTComponent();
    property int PropertyA {
        int get() { return _propertyValue; }
        void set(int propertyAValue) { _propertyAValue = propertyAValue; }
    }
    int Method(int i);
    event SomeEventHandler^ someEvent;
};

//The output of the /d1reportSingleClassLayoutWinRTComponent switch:
class WinRTComponent    size(40):
    +—
    | +— (base class __IWinRTComponentPublicNonVirtuals)
    | | +— (base class Object)
0   | | | {vfptr}
    | | +—
    | +—
    | +— (base class __IWinRTComponentProtectedNonVirtuals)
    | | +— (base class Object)
4   | | | {vfptr}
    | | +—
    | +—
    | +— (base class IDisposable)
    | | +— (base class Object)
8   | | | {vfptr}
    | | +—
    | +—
    | +— (base class Object)
12  | | {vfptr}
    | +—
16  | _propertyAValue
20  | EventSource <backing_store>someEvent
32  | __cli_MultiThreadedRefCount __cli_refcount
36  | __cli_disposed
    | <alignment member> (size=3)
    +—

WinRTComponent::$vftable@__IWinRTComponentProtectedNonVirtuals@:
     | -4
0    | &thunk: this-=4; goto WinRTComponent::__cli_QueryInterface
1    | &thunk: this-=4; goto WinRTComponent::__cli_AddRef
2    | &thunk: this-=4; goto WinRTComponent::__cli_Release
3    | &thunk: this-=4; goto WinRTComponent::__cli_GetIids
4    | &thunk: this-=4; goto WinRTComponent::__cli_GetRuntimeClassName
5    | &thunk: this-=4; goto WinRTComponent::__cli_GetTrustLevel

WinRTComponent::$vftable@__IWinRTComponentPublicNonVirtuals@:
     | &WinRTComponent_meta
     |  0
0    | &WinRTComponent::__cli_QueryInterface
1    | &WinRTComponent::__cli_AddRef
2    | &WinRTComponent::__cli_Release
3    | &WinRTComponent::__cli_GetIids
4    | &WinRTComponent::__cli_GetRuntimeClassName
5    | &WinRTComponent::__cli_GetTrustLevel
6    | &WinRTComponent::__cli_WinRTComponentDll1___IWinRTComponentPublicNonVirtuals____cli_get_PropertyA
7    | &WinRTComponent::__cli_WinRTComponentDll1___IWinRTComponentPublicNonVirtuals____cli_set_PropertyA
8    | &WinRTComponent::__cli_WinRTComponentDll1___IWinRTComponentPublicNonVirtuals____cli_Method
9    | &WinRTComponent::__cli_WinRTComponentDll1___IWinRTComponentPublicNonVirtuals____cli_add_someEvent
10    | &WinRTComponent::__cli_WinRTComponentDll1___IWinRTComponentPublicNonVirtuals____cli_remove_someEvent
11    | &WinRTComponent::Method

WinRTComponent::$vftable@IDisposable@:
     | -8
0    | &thunk: this-=8; goto WinRTComponent::__cli_QueryInterface
1    | &thunk: this-=8; goto WinRTComponent::__cli_AddRef
2    | &thunk: this-=8; goto WinRTComponent::__cli_Release
3    | &thunk: this-=8; goto WinRTComponent::__cli_GetIids
4    | &thunk: this-=8; goto WinRTComponent::__cli_GetRuntimeClassName
5    | &thunk: this-=8; goto WinRTComponent::__cli_GetTrustLevel
6    | &thunk: this-=8; goto WinRTComponent::__cli_Platform_IDisposable____cli_<Dispose>
7    | &thunk: this-=8; goto WinRTComponent::<Dispose>

WinRTComponent::$vftable@Object@:
     | -12
0    | &thunk: this-=12; goto WinRTComponent::__cli_QueryInterface
1    | &thunk: this-=12; goto WinRTComponent::__cli_AddRef
2    | &thunk: this-=12; goto WinRTComponent::__cli_Release
3    | &thunk: this-=12; goto WinRTComponent::__cli_GetIids
4    | &thunk: this-=12; goto WinRTComponent::__cli_GetRuntimeClassName
5    | &thunk: this-=12; goto WinRTComponent::__cli_GetTrustLevel

Finally, another option is to use RoActivateInstance and work directly with COM interfaces. This is a rather masochistic approach, but it seems like the only way to target WinRT components from a language that “only speaks COM”. The following C++ code is a simple example of that:

//Error-checking elided for clarity
LPCWSTR str = L”ComputingStuff.Computer”;
HSTRING hstr;
HRESULT hr = WindowsCreateString(str, wcslen(str), &hstr);
IInspectable* ptr;
hr = RoActivateInstance(hstr, &ptr);
WindowsDeleteString(hstr);
IComputer* comp;
hr = ptr->QueryInterface(IID_IComputer, (void**)&comp);
int result;
hr = comp->Compute(42, 42, &result);
comp->Release();
ptr->Release();

The C++ bindings are very high-performance when the source code of both sides is available. For example, a cross-interface cast (of a component that implements multiple interfaces) will be identical to a C++ dynamic_cast, requiring only a shift through adjustor thunks. However, if the component is behind the full ABI wall, a cross-interface cast will be compiled to a QueryInterface call, as is always the case with COM.

There are also quite a few interesting things to be said about the WinRT type system. Although not covered in the sessions I attended so far, the docs are pretty clear on this subject, and expose a large variety of types that map partially to existing C++ types and are somewhat similar to the .NET type system. For example, all WinRT components are derived from Platform::Object, and all WinRT exceptions are derived from Platform::Exception. Primitive types, however, can be boxed by casting them to Platform::Object^. Even primitive types, however, feature the ToString() method, which converts them to their string representation. I am not ready at this time to write more about these topics, but will be exploring them further in the future.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

5 comments

  1. Ben Voigt [Visual C++ MVP]September 28, 2011 ב 3:12 AM

    I can see that there’s a lot of value added on the component authoring side, by having all the COM interfaces automatically implemented.

    But on the consumer side, it sounds like it would be feasible (and maybe even more desirable) to just use com_ptr_t to get automatic reference counting without all the overhead of translating between return values and exceptions.

    If anyone knows why all the extra non-standard syntactic sugar was added instead of sticking to templates, I’d like to hear the explanation.

    Reply
  2. MordachaiSeptember 29, 2011 ב 5:53 PM

    Thanks for posting this!
    I didn’t get a change to go to \\build\, but I’m blown away by what they’ve accomplished and the direction they’re moving in. It helps to get an “under the hood” perspective.

    Reply
  3. ArthurOctober 6, 2011 ב 5:51 PM

    I’m wondering whether we could override QueryInterface() to export COM interface?

    Reply
  4. AndyFebruary 14, 2012 ב 6:48 PM

    I’m blown away too…..

    why did they spend so much time creating such a overengineered, complex thing that basically takes the worst of COM and the worst of C++/CLI. Why don’t we have a native, simple, C++ runtime with a set of wrapper classes for other languages (like, say, SWIG). Instead we have this bastardisation of ‘native .net’ that looks like it’s going to be the worst of COM thunking ever.

    Reply
  5. DaniaelAugust 28, 2012 ב 1:15 AM

    because, for example, what you call by native simple c++ runtime would not have properties, events, you name it… just for starters…

    Reply