C++/CX Part 1 of [n]: A Simple Class

C++/CX Part 1 of [n]: A Simple Class

Rate This
  • Comments 7

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(intresult)
    {
        // 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([outretval] 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
    {
        [defaultinterface 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.

    class Number : public RuntimeClass<INumber>
    {
        InspectableClass(RuntimeClass_WRLNumberComponent_Number, BaseTrust)
 
    public:
 
        Number() : _value(0) { }

        virtual HRESULT STDMETHODCALLTYPE GetValue(INT32valueoverride
        {
            *value = _value;
            return S_OK;
        }

        virtual HRESULT STDMETHODCALLTYPE SetValue(INT32 valueoverride
        {
            _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.

Attachment: ASimpleClassSolution.zip
  • It reminds me of CDocument, where every dataobject should inherit from this, and then it would get all kinds of free bonuses. Except that it could not be reused any other places besides in MFC applications.

    Interfaces should be interfaces. Datamodel should be datamodel. But still very nice syntax sugar.

  • in the last code paste (def of the Number class), why are the 2 functions marked virtual? isn't the Number class supposed not to be able to serve as a base class anyway (ie it's "sealed")?

  • @Zouzou:  In the WRL example, the two functions (GetValue and SetValue) are first declared by the interface, INumber, from which the Number class derives.  In the interface, they are declared as pure virtual member functions, so regardless whether we use the 'virtual' keyword when declaring them in the Number class, they would be virtual and would override the pure virtual members declared by the interface.

    In Windows Runtime, all member functions that can be called across the ABI boundary are virtual, because an object is only ever used via a pointer to an interface that it implements.

  • It looks to me like the C++ documentation for "Windows Metro style Apps" is missing two sections:

    1."Using Static Libraries for WinRT applications".  

    2."Using DLLs for WinRT applications".

    For example, I found it odd that no .lib was created from a newly created DLL for Metro apps project.  Isn't there a use case for creating a .DLL which also has a .lib?

    More generally, it seems that if I want to provide reusable C++ code that uses both WinRT types and Native types, I need to divide it into two projects;

    1. A static library project with /ZW for all functions and classes that use native types like std::wstring in public methods.

    2. A Windows Runtime Component for all "public ref classes"

    And the reasons that require this division are:

    1. The compiler will raise an error if a Windows Runtime component has a type such as std::wstring as a return type or parameter in a public method.

    2.Authoring new WinRT components in a static library and linking + consuming them in another exe/dll is not a supported scenarios for VS 2012.

    Am I incorrect in any of these assumptions?

  • @AndrewDover:  No import library (or header files, for that matter) are required to consume a Windows Runtime Component.  Types are described in the Windows Metadata file, eliminating the need for including header files.  Types are instantiated via the Windows Runtime (e.g., 'ref new' in C++/CX ends up making a call to RoActivateInstance or RoGetActivationFactory) and well-known named exports (DllGetActivationFactory), so there's no need for the usual import library.

    A C++/CX Windows Runtime Component can link with ordinary static libraries (including import libraries).  You can use ordinary C++ code or ordinary libraries from C++/CX, but the public interface of the component cannot make use of non-Windows Runtime types.  The private implementation details of your component DLL can use any code that you'd like.  This is one reason that the 'ref', 'interface', and 'value' keywords and the visibility modifiers like 'public' are used when declaring Windows Runtime types in C++/CX:  they distinguish Windows Runtime types from ordinary C++ types.

    If you want to write a (non-Windows Runtime) DLL that can be loaded by a Windows Store app and by an ordinary desktop app, you can do so, and that's probably the best route to take if you want to share code.  You can reference such a DLL from a Windows Store app (or Windows Runtime component) the same way that you would any other project.

  • To clarify, I was talking about a "Windows Metro Style" project of type DLL(Metro style apps), not "Windows Runtime Component", when I was puzzled by the lack of a .lib output.

    I think I understand the mechanism for "Windows Runtime Component", as you wrote.  But since I can only use Windows Runtime types in that project, I have to use another project for code that consumes Windows Runtime Types and exposes native types in its public interface.  So I have a choice of using:

    1) Static library (Metro style apps) or

    2) DLL( Metro style apps)

    But #2 lacks the .lib needed for my application to easily connect to that library.

  • I fully understand the importance of Native COM(Component Object Model). That is why the C++/CX right here.

    But the root weakness of COM is

    The COM uses v-table to hide the implementation. But the v-table itself is an implementation (v-table is just a struct). So we need maintain the invariance of the v-table, a lot of QueryInterface have to be called.

    Although we have libraries or language extensions to simplify the development of COM, due to this constraints, we still can't make COM development simple, flexible and extreme efficient.

    My group has been using another way for 5 years to across different versions of the C++ compiler without code recompiled.

    If you can provide your email, I would be very grateful. I want to send you the short demo code.

    My email is goecerfun@hotmail.com

    Thanks

Page 1 of 1 (7 items)