See C++/CX Part 0 of [N]: An Introduction for an introduction to this series.
In this article we'll consider the basics of C++/CX by looking at a simple Windows Runtime class; we'll skim over some of the details, but don't worry: we'll come back and cover them in future posts. The code in this post is complete, though some namespace qualification is omitted for brevity; attached to this article is a Visual Studio solution containing the complete code for both the C++/CX and the WRL component, along with a simple test app that uses both.
Alright. Here's our simple class:
public ref class Number sealed { public: Number() : _value(0) { } int GetValue() { return _value; } void SetValue(int value) { _value = value; } private: int _value; };
This is a rather silly class, but it is sufficient to demonstrate some of the fundamentals of both C++/CX and Windows Runtime. Aside from the public ref
and sealed
in the class declaration, this class looks exactly like an ordinary C++ class. This is a good thing: our C++/CX code should look similar to similar C++ code, but should be sufficiently different that we (and the compiler!) can identify it as being different.
So, what does it really mean that this class is a sealed, public, ref class? A ref class
is a reference type. Windows Runtime is built atop COM, and a Windows Runtime reference type is effectively a COM class type. Whenever we interact with a reference type object, we do so indirectly (by reference), via a pointer to an interface that the reference type implements. Since we never interact with a reference type by value, its implementation is opaque: so long as its existing interface remains unchanged, its implementation may be modified without affecting other components that depend on it (new functionality can be added, but not removed).
A public
type is visible outside of the component that defines it, and other components may refer to and use this type. Types can also be private
(which is the default); a private type can only be referred to from within the component that defines it. The Windows Metadata file for a C++/CX component only contains metadata for public types. There are fewer restrictions on private types than on public types because private types do not appear in metadata and thus are not constrained by some of the rules of Windows Runtime.
The sealed
simply specifies that this class may not be used as a base class. Most public Windows Runtime types are sealed (currently, the only public types that may be unsealed are those derived from Windows::UI::Xaml::DependencyObject
; these types are used by XAML apps).
The "Ref classes and structs (C++/CX)" page in the aforementioned Visual C++ Language Reference has a good roundup of other interesting facts about reference types.
Interfaces
I said above that whenever we interact with a reference type object, we do so via an interface that the reference type implements. You may have noticed that it looks like our Number
class doesn't implement any interfaces: it has no base class list at all. Thankfully, the compiler is going to help us out here so that our Number
class isn't useless.
The compiler will automatically generate an interface for our class, named __INumberPublicNonVirtuals
. As its name suggests, this interface declares all of the public, nonvirtual member functions of our Number
type. If we were to define this ourselves in C++/CX, it would look like so:
public interface struct __INumberPublicNonVirtuals { int GetValue() = 0; void SetValue(int) = 0; };
The Number
class is then transformed to declare this interface. If our class declared any new public virtual members (i.e., not overrides of base class virtual member functions) or any virtual or nonvirtual protected members, the compiler would generate interfaces for those as well.
Note that these automatically generated interfaces only declare members that aren't already declared by an interface that the class explicitly implements. So, for example, if we declared another interface:
public interface struct IGetNumberValue { int GetValue() = 0; };
and defined our Number
class as implementing this interface, the automatically generated __INumberPublicNonVirtuals
would only define the SetValue
member function.
Error Handling
In modern C++ code, exceptions are usually used for error reporting (not always, but they should be the default). A function is expected to return its result directly (as a return value) and throw an exception if a failure occurs. However, exceptions are not portable across different languages and runtimes; exception handling machinery varies quite a bit. Even within C++ code exceptions can be problematic, as different compilers may implement exceptions differently.
Since exceptions don't work well across different languages, Windows Runtime does not use them; instead, each function returns an error code (an HRESULT) indicating success or failure. If a function needs to return a value, the value is returned via an out parameter. This is the same convention used by COM. It's up to each language projection to translate between error codes and the natural error handling facility for that language.
There is overhead involved in catching and rethrowing exceptions, but the benefit is obvious even in simple scenarios involving interaction between components written in different languages. Consider, for example, a function in a C# component calling a function in a component implemented in C++/CX. The C++ code can throw an exception derived from Platform::Exception
, and if the exception is not handled, it will be caught at the ABI boundary and translated into an HRESULT, which is returned to the C# code. The CLR will then translate the error HRESULT into a managed exception, which will be thrown for the C# code to catch.
The important thing is that both components--the C# component and the C++ component--get to handle errors naturally using exceptions, even though they use different exception handling machinery. We'll cover the details of which exceptions get translated at the C++/CX boundary, and how the translation takes place, in a future article (this topic is sufficiently complex that it warrants separate treatment). For the moment, it is sufficient to know that the translation happens automatically.
Member Functions
We've intentionally made our Number
class very simple: neither GetValue
nor SetValue
can throw, and a call to either will therefore always succeed. We can therefore use them to demonstrate how the compiler transforms our C++/CX member functions into the real ABI functions. GetValue
is a bit more interesting since it returns a value, so let's take a look at it:
int GetValue() { return _value; }
The compiler will generate a new function with the correct ABI signature; for example,
HRESULT __stdcall __abi_GetValue(int* result) { // Error handling expressly omitted for exposition purposes *result = GetValue(); return S_OK; }
The wrapper function has an additional out parameter for the return value appended to the parameter list, and its actual return type is changed to HRESULT
. Windows Runtime functions use the stdcall calling convention on x86, so the __stdcall
annotation is required. By default, nonvariadic member functions ordinarily use the thiscall calling convention. (Calling convention annotations only really matter on x86; x64 and ARM each have a single calling convention.)
We've named our wrapper function __abi_GetValue
; this is the name that the compiler gives to the function on the interface that it generates; in the class it uses a much longer name with lots of underscores to ensure that it doesn't conflict with any user-declared functions or functions inherited from other classes or interfaces. The name of the function doesn't matter at runtime, so it doesn't really matter what name the function has. At runtime, functions are called via vtable lookup, and since the compiler is generating the wrapper functions, it knows which function pointers to place into the vtables.
A wrapper is generated for our SetValue
function following the same pattern, with the exception that no out parameter is added because it returns no value.
A Simple Class, without C++/CX
With what we've discussed so far, we already have enough information to implement our Number
class using C++ with the help of WRL and without using C++/CX.
When using C++/CX, the C++ compiler will generate both the Windows Metadata (WinMD) file for the component and the DLL that defines all of the types. When we don't use C++/CX, we need to use IDL to define anything that needs to end up in metadata and use midlrt to generate a C++ header file and a Windows Metadata file from the IDL. For those experienced with COM, this should be quite familiar.
First, we need to define an interface for our Number
type. This is equivalent to the __INumberPublicNonVirtuals
interface that the compiler generated for us automatically when we were using C++/CX. Here, we'll just name our interface INumber
:
[exclusiveto(Number)] [uuid(5b197688-2f57-4d01-92cd-a888f10dcd90)] [version(1.0)] interface INumber : IInspectable { HRESULT GetValue([out, retval] INT32* value); HRESULT SetValue([in] INT32 value); }
Our INumber
interface derives from the IInspectable
interface. This is the base interface from which all Windows Runtime interfaces derive; in C++/CX, every interface implicitly derives from IInspectable
.
We also need to define the Number
class itself in the IDL file, because it is public and therefore needs to end up in the metadata file:
[activatable(1.0), version(1.0)] runtimeclass Number { [default] interface INumber; }
The activatable
attribute used here specifies that this class is default constructible. The details of how object construction works will be covered in a future article. For the moment, it's sufficient to know that this activatable
attribute will generate the required metadata in the metadata file to report the Number
class as default constructible.
And, that's all that is required in the IDL file. If you compare the WinMD file produced by this IDL file with the one produced for our C++/CX component, you'll see that they are largely the same: the interface has a different name, and there are a few extra attributes applied to types in the C++/CX component, but they are otherwise the same.
The class definition in C++ is straightforward:
class Number : public RuntimeClass{ InspectableClass(RuntimeClass_WRLNumberComponent_Number, BaseTrust) public: Number() : _value(0) { } virtual HRESULT STDMETHODCALLTYPE GetValue(INT32* value) override { *value = _value; return S_OK; } virtual HRESULT STDMETHODCALLTYPE SetValue(INT32 value) override { _value = value; return S_OK; } private: INT32 _value; };
The RuntimeClass
class template and the InspectableClass
macro are both from WRL: together they handle much of the mundane, repetitive work to implement a Windows Runtime class. The RuntimeClass
class template takes as its arguments a set of interfaces that the class will implement and it provides a default implementation of the IInspectable
interface member functions. The InspectableClass
macro takes as its arguments the name of the class and the trust level of the class; these are required to implement pieces of the IInspectable
interface.
The two member functions are defined as is expected, given the discussion above: instead of directly using the __stdcall
modifier, we use the STDMETHODCALLTYPE
, which expands to __stdcall
when using Visual C++ and the Windows headers, but could be changed to expand to something else if you were using a different compiler. You could also use the STDMETHOD
macro.
Finally, because our Number
type is default constructible, and because the default constructor is public, meaning that other components can create an instance of this class, we need to implement the logic required to enable other components to call the constructor of our class. This involves implementing a factory class and registering the factory so that we can return an instance of the factory when asked. There's a fair bit of work required here, but since we only have a default constructor, we can simply use the ActivatableClass
macro from WRL:
ActivatableClass(Number)
And, that's it! The WRL component requires a bit more than three times as much code as the C++/CX component, but this is just a small, simple component. As things get more complex, and as we use more features of Windows Runtime, we'll see that the WRL-based code both grows and becomes more complex much faster than the C++/CX-based code.
In our next post, we'll discuss hats (^
) in detail, and in future posts we'll cover the details of construction, exception handling, and other interesting topics.