See C++/CX Part 0 of [N]: An Introduction for an introduction to this series.
The hat (^
) is one of the most prominent features of C++/CX–it’s hard not to notice it when one first sees C++/CX code. So, what exactly is a ^
type? A hat type is a smart pointer type that (1) automatically manages the lifetime of a Windows Runtime object and (2) provides automatic type conversion capabilities to simplify use of Windows Runtime objects.
We’ll start off by discussing how Windows Runtime objects are used via WRL, then explain how the C++/CX hat works to make things simpler. For demonstration purposes, we’ll use the following modified version of the Number
class that we introduced in Part 1:
public interface struct IGetValue { int GetValue() = 0; };
public interface struct ISetValue { void SetValue(int value) = 0; };
public ref class Number sealed : public IGetValue, ISetValue { public: Number() : _value(0) { }
virtual int GetValue() { return _value; } virtual void SetValue(int value) { _value = value; }
private:
int _value;
};
In this modified Number
implementation, we define a pair of interfaces, IGetValue
and ISetValue
, that declare the two member functions of Number
; Number
then implements these two interfaces. Otherwise, things should look pretty familiar.
Note that Number
actually implements three Windows Runtime interfaces: in addition to IGetValue
and ISetValue
, the compiler still generates the __INumberPublicNonVirtuals
interface, which Number
implements. Because all of the members of Number
are declared by explicitly implemented interfaces (IGetValue
and ISetValue
), the compiler-generated __INumberPublicNonVirtuals
does not declare any members. This interface is still required, though, as it is the default interface for the Number
type. Every runtime type must have one default interface, and the default interface should almost always be unique to the class. We’ll see why the default interface is important a bit later.
Lifetime Management
Windows Runtime reference types use reference counting for object lifetime management. All Windows Runtime interfaces (including all three of the interfaces implemented by Number
) derive directly from the IInspectable
interface, which itself derives from the COM IUnknown
interface. IUnknown
declares three member functions that are used to control the lifetime of an object and to allow type conversion.
The MSDN article “Rules for Managing Reference Counts” has a thorough overview of how IUnknown
lifetime management works. The principles are quite straightforward, though: whenever you create a new reference to an object, you must call IUnknown::AddRef
to increment its reference count; whenever you “destroy” a reference to an object, you must call IUnknown::Release
to decrement the reference count. The reference count is initialized to zero, and after a series of calls to AddRef
and Release
, when the reference count reaches zero again, the object destroys itself.
Of course, when programming in C++, we should rarely–practically never–call AddRef
and Release
directly. Instead, we should prefer wherever possible to use a smart pointer that automatically makes these calls when they are required. Use of a smart pointer helps to ensure that objects are neither leaked due to a missed Release
nor prematurely destroyed due to a premature Release
or failure to AddRef
.
ATL includes CComPtr
and a family of related smart pointers that have long been used in COM programming for automatically managing the reference counting of objects that implement IUnknown
. WRL includes ComPtr
, which is an improved and modernized CComPtr
(an example improvement: ComPtr
does not overload the unary &
like CComPtr
does).
For those who have not done much COM programming and are unfamiliar with ComPtr
s: if you’ve used shared_ptr
(included as part of C++11, C++ TR1, and Boost), ComPtr
has effectively the same behavior with respect to lifetime management. The mechanism is different (ComPtr
makes use of the internal reference counting provided by IUnknown
, while shared_ptr
supports arbitrary types and must thus use an external reference count), but the lifetime management behavior is the same.
The C++/CX hat has exactly the same lifetime management semantics as a ComPtr
. When a T^
is copied, AddRef
is called to increment the reference count, and when a T^
goes out of scope or is reassigned, Release
is called to decrement the reference count. We can consider a simple example to demonstrate the reference counting behavior:
{ T^ t0 = ref new A(); T^ t1 = ref new B();
t0 = t1;
t0 = nullptr;
}
First, we create an A
object and give ownership of it to t0
. The reference count of this A
object is 1 because there is one T^
that has a reference to it. We then create a B
object and give ownership of it to t1
. The reference count of this B
object is also 1.
The end result of t0 = t1
is that both t0
and t1
point to the same object. This must be done in three steps. First, t1->AddRef()
is called to increment the reference count of the B
object, because t1
is gaining ownership of the object. Second, t0->Release()
is called to release t0
‘s ownership of the A
object. This causes the reference count of the A
object to drop to zero and the A
object destroys itself. This causes the reference count of the B
object to increase to 2. Third and finally, t1
is set to point to the B
object.
We then assign t0 = nullptr
. This “resets” t0
to be null, which causes it to release its ownership of the B
object. This calls t0->Release()
, causing the reference count of the B
object to decrease to 1.
Finally, execution will reach the closing brace of the block: }
. At this point, all of the local variables are destroyed, in reverse order. First, t1
is destroyed (the smart pointer, not the object pointed to). This calls t1->Release()
, causing the reference count of the B
object to drop to zero, so the B
object destroys itself. t0
is then destroyed, which is a no-op because it is null.
If lifetime management was our only concern, there wouldn’t really be a need for the ^
at all: ComPtr<T>
is sufficient to manage object lifetime.
Type Conversion
In C++, some type conversions involving class types are implicit; others may be performed using a cast or a series of casts. For example, if Number
and the interfaces it implements were ordinary C++ types and not Windows Runtime types, conversion from Number*
to IGetValue*
would be implicit, and we could convert from IGetValue*
to Number*
using a static_cast
or a dynamic_cast
.
These conversions do not work with Windows Runtime reference types because the implementation of a reference type is opaque and the layout of a reference type in memory is left unspecified. A reference type implemented in C# may be laid out in memory differently from an equivalent reference type implemented in C++. Thus, we cannot rely on C++ language specific features like implicit derived-to-base conversions and casts when working directly with Windows Runtime types.
To perform these conversions, we must instead use the third member function of the IUnknown
interface: IUnknown::QueryInterface
. This member function can be considered as a language-neutral dynamic_cast
: it attempts to perform a conversion to the specified interface and returns whether the conversion succeeded. Because each runtime type implements the IUnknown
interface and provides its own definition for QueryInterface
, it can do whatever is required to get the correct interface pointer in the language and framework with which it is implemented.
Using an object via WRL
Let’s take a look at how we’d use our Number
class via WRL. This example accepts an instance of Number
and calls SetValue
to set the value and GetValue
to get the value back. (Error checking has been omitted for brevity.)
void F(ComPtr<__INumberPublicNonVirtuals> const& numberIf) { // Get a pointer to the object's ISetValue interface and set the value: ComPtr<ISetValue> setValueIf; numberIf.As(&setValueIf); setValueIf->SetValue(42); // Get a pointer to the object's IGetValue interface and get the value: ComPtr<IGetValue> getValueIf; numberIf.As(&getValueIf); int value = 0; getValueIf->GetValue(&value); }
The As
member function template of the WRL ComPtr
simply encapsulates a call to IUnknown::QueryInterface
in a way that helps to prevent common programming errors. We use this first to get the ISetValue
interface pointer to call SetValue
then again to get the IGetValue
interface pointer to call GetValue
.
This would be a lot simpler if we obtained a Number*
and called SetValue
and GetValue
via that pointer. Unfortunately, we can’t do that: recall that the implementation of a reference type is opaque and that we only ever interact with an object via a pointer to one of the interfaces that it implements. This means that we can never have a Number*
; there is really no such thing. Rather, we can only refer to a Number
object via an IGetValue*
, an ISetValue*
, or an __INumberPublicNonVirtuals*
.
This is an awful lot of code just to call two member functions and this example demonstrates one of the key hurdles that we have to overcome to make it easier to use Windows Runtime types. Unlike COM, Windows Runtime does not allow an interface to derive from another Windows Runtime interface; all interfaces must derive directly from IInspectable
. Each interface is independent, and we can only interact with an object via interfaces, so if we are using a type that implements multiple interfaces (which many types do), we are stuck having to write a lot of rather verbose type conversion code so that we can obtain the correct interface pointer to make each function call.
Using an object via C++/CX
One of the key advantages of C++/CX is that the compiler knows which types are Windows Runtime types. It has access to the Windows Metadata (WinMD) file that defines each interface and runtime type, so–among other things–it knows the set of interfaces that each runtime type implements. For instance, the compiler knows that the Number
type implements the ISetValue
and IGetValue
interfaces, because the metadata specifies that it does. The compiler is able to use this type information to automatically generate type conversion code.
Consider the following C++/CX example, which is equivalent to the WRL example that we presented:
void F(Number^ number) { ISetValue^ setValueIf = number; setValueIf->SetValue(42); IGetValue^ getValueIf = number; int value = getValueIf->GetValue(); }
Because the compiler knows that the Number
type implements the ISetValue
and IGetValue
interfaces, it allows implicit conversion from Number^
to ISetValue^
and IGetValue^
. This implicit conversion causes the compiler to generate a call to IUnknown::QueryInterface
to get the right interface pointer. Aside from the cleaner syntax, there’s really no magic here: the compiler is just generating the type conversion code that we otherwise would have to write ourselves.
dynamic_cast
also works much as we’d expect: we can, for example, amend this example to obtain the IGetValue^
from the ISetValue^
:
void F(Number^ number) { ISetValue^ setValueIf = number; setValueIf->SetValue(42); IGetValue^ getValueIf = dynamic_cast<IGetValue^>(setValueIf); int value = getValueIf->GetValue(); }
This example has the same behavior as the first example, we just take different steps to get that same behavior. dynamic_cast
can return nullptr
if the cast fails (though we know that it will succeed in this specific case). C++/CX also provides safe_cast
, which throws a Platform::InvalidCastException
exception if the cast fails.
When we discussed the WRL example above, we noted that there’s no such thing as a Number*
: we only ever work with interface pointers. This begs the question: what is a Number^
? At runtime, a Number^
is a __INumberPublicNonVirtuals^
. A hat that refers to a runtime type (and not an interface) actually holds a pointer to the default interface of that runtime type.
At compile-time, though, the compiler treats Number^
as if it refers to the whole Number
object. The compiler aggregates all of the members of all of the interfaces implemented by Number
and allows all of those members to be called directly via a Number^
. We can use a Number^
as if it were an IGetValue^
or an ISetValue^
and the compiler will inject the required calls to QueryInterface
to perform the conversions required to make the function call.
Therefore, we can shorten our C++/CX program further:
void F(Number^ number) { number->SetValue(42); int value = number->GetValue(); }
This code does exactly the same thing as our first C++/CX example and as our WRL example. There’s still no magic: the compiler is simply generating all of the boilerplate to perform the type conversion required to make each function call.
You may have noticed that this example is much shorter and less verbose than the WRL example that we started with. 🙂 All of the boilerplate is gone and we’re left with code that–aside from the ^
and ref
that tell the compiler we are dealing with Windows Runtime types–looks exactly like similar C++ code that interacts with ordinary C++ types. This is the point, though: ideally our code that uses Windows Runtime types should be as similar as possible to code that uses C++ types.
Final Notes
Both ComPtr<T>
and T^
are “no-overhead” smart pointers: the size of each is the same as the size of an ordinary pointer, and operations using them do not do any unnecessary work. If you need to interoperate between code that uses C++/CX and code that uses WRL, you can simply use reinterpret_cast
to convert a T^
to a T*
:
ABI::ISetValue* setValuePtr = reinterpret_cast(setValueIf);
(The ABI level definitions of types are defined in namespaces under the ABI
namespace so that they do not conflict with the “high-level” definitions of the types used by C++/CX, which are defined under the global namespace.)
In addition to its type conversion capabilities, the hat provides other benefits that could not otherwise be accomplished via use of an ordinary smart pointer like ComPtr
. One of the most important of these benefits is that the hat can be used uniformly, everywhere. A member function that takes an interface pointer as an argument is declared as taking a raw pointer (this is part of the Windows Runtime ABI, which is designed to be simple and language-neutral, and thus knows nothing of what a C++ smart pointer is). So, while one can use ComPtr
most places, raw pointers still need to be used at the ABI boundary, and there is room for subtle (and not-so-subtle) programming errors.
With C++/CX, the compiler already transforms member function signatures to translate between exceptions and HRESULTs and the compiler is also able to inject conversions from T^
to T*
where required, substantially reducing the opportunity for programming errors.
This is a good guide.
But the code snippets are rendering raw HTML not the rendered HTML