September 2015

Volume 30 Number 9

Windows with C++ - Classy Types in the Windows Runtime

By Kenny Kerr | September 2015

Kenny KerrThe Windows Runtime (WinRT) allows component developers to present a classy type system to app developers. Given that the Windows Runtime is implemented entirely with COM interfaces, this might seem quite absurd to a developer familiar with C++ and classic COM. COM is, after all, a programming model centered on interfaces rather than classes. The COM activation model allows classes to be constructed with CoCreateInstance and friends, but this can plausibly support only something akin to a default constructor. The WinRT RoActivateInstance does nothing to change this perception. So how does it work?

It isn’t actually as incongruous as it might at first appear. The COM activation model already provides the fundamental abstractions necessary for supporting a classy type system. It merely lacks the metadata necessary to describe it to a consumer. The COM activation model defines class objects and instances. An instance of some class is never created directly by a consumer. Even if an app developer calls CoCreateInstance to create some instance of a class, the OS still has to get the class object in order to retrieve the desired instance.

The Windows Runtime calls class objects by a new name but the mechanics are the same. An activation factory is a consumer’s first port of call into a component. Rather than implementing DllGetClassObject, a component exports a function called DllGet­ActivationFactory. The way that classes are identified has changed, but the result is just an object that might be queried further in connection with the given class. A traditional class object, henceforth called an activation factory, might have implemented the IClassFactory interface that allows the caller to create instances of said class. In the new world, activation factories in the Windows Runtime must implement the new IActivationFactory interface—effectively providing the same service.

As I explored in the last two installments of my column, a runtime class in the Windows Runtime that’s decorated with the activatable attribute produces metadata that instructs a language projection to permit default construction. The assumption is that the activation factory for the attributed class will provide such a default constructed object via IActivationFactory’s ActivateInstance method. Here’s a simple runtimeclass in IDL that represents a class with a default constructor:

[version(1)]
[activatable(1)]
runtimeclass Hen
{
  [default] interface IHen;
}

Additional constructors may also be described in IDL with, you guessed it, interfaces housing collections of methods that represent additional constructors with different sets of parameters. Just as the IActivationFactory interface defines default construction, component- and class-specific interfaces might describe parameterized construction. Here’s a simple factory interface for creating Hen objects given a specific number of clucks:

runtimeclass Hen;
[version(1)]
[uuid(4fa3a693-6284-4359-802c-5c05afa6e65d)]
interface IHenFactory : IInspectable
{
  HRESULT CreateHenWithClucks([in] int clucks,
                              [out,retval] Hen ** hen);
}

Like IActivationFactory, the IHenFactory interface must inherit directly from IInspectable for no apparent reason. Each of this factory interface’s methods is then projected as an additional constructor with the given parameters, and each must conclude with a logical return value whose type is that of the class. This factory interface is then explicitly associated with the runtime class via another activatable attribute indicating the interface name:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
runtimeclass Hen
{
  [default] interface IHen;
}

If a default constructor makes no sense for your class then simply omit the original activatable attribute, and language projections of your class will likewise lack a default constructor. What if I ship a version of my poultry component and then realize it lacks the ability to create hens with large combs? I certainly can’t change the IHenFactory interface now that I’ve shipped it to customers because COM interfaces are necessarily immutable binary and semantic contracts. No problem, I can simply define another interface housing any additional constructors:

[version(2)]
[uuid(9fc40b45-784b-4961-bc6b-0f5802a4a86d)]
interface IHenFactory2 : IInspectable
{
  HRESULT CreateHenWithLargeComb([in] float width,
                                 [in] float height,
                                 [out, retval] Hen ** hen);
}

I can then associate this second factory interface with the Hen class as before:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
[activatable(IHenFactory2, 2)]
runtimeclass Hen
{
  [default] interface IHen;
}

Language projections take care of the amalgamation and the app developer simply sees a number of constructor overloads, as depicted in Figure 1.

IDL-Defined Factory Methods in C#
Figure 1 IDL-Defined Factory Methods in C#

The component developer is careful to implement these factory interfaces, along with the prerequisite IActivationFactory interface:

struct HenFactory : Implements<IActivationFactory,
                               ABI::Sample::IHenFactory,
                               ABI::Sample::IHenFactory2>
{
};

Here again I’m relying on the Implements class template I described in my December 2014 column (msdn.microsoft.com/magazine/dn879357). Even if I chose to prohibit default construction, the hen’s activation factory would still need to implement the IActivationFactory interface because the DllGetActivationFactory function that components must implement is hardcoded to return an IActivationFactory interface. This means that a language projection must first call QueryInterface on the resulting pointer if the app needs anything other than default construction. Still, an implementation of IActivationFactory’s ActivateInstance virtual function is required, but a no-op such as this will suffice:

virtual HRESULT __stdcall ActivateInstance(IInspectable ** instance) noexcept override
{
  *instance = nullptr;
  return E_NOTIMPL;
}

The additional construction methods can be implemented in whatever way makes sense for the given implementation, but a simple solution would be to simply forward the arguments to the internal class itself. Something like this:

virtual HRESULT __stdcall CreateHenWithClucks(int clucks,
                                              ABI::Sample::IHen ** hen) noexcept override
{
  *hen = new (std::nothrow) Hen(clucks);
  return *hen ? S_OK : E_OUTOFMEMORY;
}

This assumes the C++ Hen class doesn’t have a throwing constructor. I might need to allocate a C++ vector of clucks in that constructor. In that case, a simple exception handler will suffice:

try
{
  *hen = new Hen(clucks);
  return S_OK;
}
catch (std::bad_alloc const &)
{
  *hen = nullptr;
  return E_OUTOFMEMORY;
}

What about static class members? You shouldn’t be surprised to learn that this, too, is implemented with COM interfaces on the activation factory. Back in IDL, I can define an interface to house any and all static members. How about something to report on the number of layer hens in the backyard:

[version(1)]
[uuid(60086441-fcbb-4c42-b775-88832cb19954)]
interface IHenStatics : IInspectable
{
  [propget] HRESULT Layers([out, retval] int * count);
}

I then need to associate this interface with my same runtime class with the static attribute:

[version(1)]
[activatable(1)]
[activatable(IHenFactory, 1)]
[activatable(IHenFactory2, 2)]
[static(IHenStatics, 1)]
runtimeclass Hen
{
  [default] interface IHen;
}

The implementation in C++ is equally straightforward. I’ll update the Implements variadic template as follows:

struct HenFactory : Implements<IActivationFactory,
                               ABI::Sample::IHenFactory,
                               ABI::Sample::IHenFactory2,
                               ABI::Sample::IHenStatics>
{
};

And provide a suitable implementation of the get_Layers virtual function:

virtual HRESULT __stdcall get_Layers(int * count) noexcept override
{
  *count = 123;
  return S_OK;
}

I’ll leave it to you to provide a more accurate head count … I mean hen count.

On the consumer side, inside the app, it all appears very simple and concise. As illustrated in Figure 1, the IntelliSense experience is quite helpful. I can use the CreateHenWithClucks constructor as if it were just another constructor on a C# class:

Sample.Hen hen = new Sample.Hen(123); // Clucks

Of course, the C# compiler and Common Language Runtime (CLR) have a fair amount of work to do in order for this to run. At run time, the CLR calls RoGetActivationFactory, which calls LoadLibrary followed by DllGetActivationFactory to retrieve the Sample.Hen activation factory. It then calls QueryInterface to retrieve the factory’s IHenFactory interface implementation, and only then can it call the CreateHenWithClucks virtual function to create the Hen object. This is not to mention the many, many additional calls to QueryInterface the CLR insists on as it prods each and every object that enters the CLR to discover various attributes and semantics of said objects.

And calling a static member appears equally simple:

int layers = Sample.Hen.Layers;

But here again the CLR must call RoGetActivationFactory to get the activation factory, followed by QueryInterface to retrieve the IHenStatics interface pointer before it can call the get_Layers virtual function to retrieve this static property’s value. The CLR does, however, cache activation factories so the cost is somewhat amortized across many such calls. On the other hand, it also makes various attempts at factory caching within a component rather pointless and unnecessarily complex. But that’s a topic for another day. Join me next month as we continue to explore the Windows Runtime from C++.


Kenny Kerr is a computer programmer based in Canada, as well as an author for Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow him on Twitter at twitter.com/kennykerr.


Thanks to the following Microsoft technical expert for reviewing this article: Larry Osterman