C++/CX Part 4 of [n]: Static Member Functions

C++/CX Part 4 of [n]: Static Member Functions

Rate This
  • Comments 3

See C++/CX Part 0 of [n]: An Introduction for an introduction to this series and a table of contents with links to each article in the series.

In this article, we'll take a look at static member functions and how they are supported by the Windows Runtime. A Windows Runtime reference type (also called a ref class in C++/CX, or a runtime class) can have static member functions. In C++/CX, the syntax used to declare a static member function in a runtime class is exactly the same as the syntax used in an ordinary C++ class. To demonstrate this, here is a runtime class with one static member function:

    public ref class KnownValues sealed
    {
    public:
        static int GetZero() { return 0; }
    private:
        KnownValues(); // This type can't be constructed
    };

(Note that we have declared a private default constructor to ensure that it is not possible to create an instance of this class. If we define a ref class and don't declare any constructors, the compiler will provide a public default constructor for the type, just as it would for an ordinary C++ class. It's possible to define a type that is constructible and has static members; we've just made this type non-constructible to make the next examples a bit simpler.)

Similarly, the syntax used to call a static member function declared by a runtime class is exactly the same as the ordinary C++ syntax. Here's how we'd call GetZero:

    int x = KnownValues::GetZero(); 

So, at least syntactically, there's nothing special about static member functions in C++/CX. However, the mechanism via which static member functions are supported by the Windows Runtime deserves some comment.

Implementation of the Static Member Function

A call to a static member function is made independent of any instance of the class that declares that function. A static member function has no this pointer. We don't need to create a KnownValues object in order to call its GetZero static member function. In order to allow a runtime class to have static member functions, we need some sort of method that allows us to call a function without first creating an instance of its declaring type.

It turns out that we've already solved this problem, in Part 3: Under Construction, when we implemented constructors using an activation factory. To summarize that article, we implemented support for constructors by:

  1. converting each constructor into a function that returns a new instance of the type,
  2. defining an interface, called a factory interface, that declares all of those construction functions,
  3. defining a runtime class, called an activation factory, that implements the factory interface, and
  4. providing a well-defined way to get an instance of the activation factory for an arbitrary type.

An activation factory allows us to implement functions associated with a runtime class that can be called without first creating an instance of that runtime class. A particular runtime class can only have one activation factory associated with it, but that activation factory can implement multiple interfaces. In addition to implementing zero or more factory interfaces, which declare construction functions, an activation factory can also implement zero or more static interfaces, which declare static member functions.

We'll re-implement the KnownValues type using C++ and WRL, but we won't go into too much detail; the previous article covers activation factories in depth and there aren't many differences here. First, here are the IDL declarations for the runtime class and its static interface, which are quite straightforward:

    [exclusiveto(KnownValues)]
    [uuid(ca8c9b14-f2a3-4f1e-aa50-49bfa3a5dbd3)]
    [version(1.0)]
    interface IKnownValuesStatics : IInspectable
    {
        HRESULT GetZero([out] [retvalint* value);
    }
    [static(IKnownValuesStatics, 1.0)]
    [version(1.0)]
    runtimeclass KnownValues
    {
    }

The static attribute on KnownValues specifies that the IKnownValueStatics interface is a static interface for the KnownValues runtime class. Note that the KnownValues type does not declare that it implements any instance interfaces (i.e., its body is empty). This is because no instance of the KnownValues runtime class will ever be created. This runtime class is really just a container used to define static member functions (in C# terminology, this would be called a static class).

The activation factory implementation is also straightforward:

    class KnownValuesFactory : public ActivationFactory<IKnownValuesStatics>
    {
        InspectableClassStatic(RuntimeClass_WRLKnownValuesComponent_KnownValuesBaseTrust)
    public:
 
        STDMETHODIMP GetZero(intvalueoverride
        {
            *value = 0;
            return S_OK;
        }
    };
    ActivatableStaticOnlyFactory(KnownValuesFactory)

Note that because we will never create an instance of KnownValues, we don't actually need to define a KnownValues class in C++. We only need to define the activation factory, which implements the IKnownValueStatics static interface.

All activation factories must also implement the IActivationFactory interface. The ActivationFactory base class template that we use provides a default implementation of this interface, which does the right thing for a non-activatable type. A particular runtime class may both be activatable and have static member functions. In that case, its activation factory would implement both a factory interface and a static interface.

Calling the Static Member Function

Since static member functions are implemented in the same way as constructors, it should come as no surprise that the process of calling a static member function is exactly the same as the process of calling a constructor. Two steps are required: first, we need to get the activation factory for the type, then we can call the function. The WRL code to invoke GetZero is as follows:

    HStringReference classId(RuntimeClass_WRLKnownValuesComponent_KnownValues);
    
    ComPtr<IKnownValuesStatics> statics;
    RoGetActivationFactory(
        classId.Get(),
        __uuidof(IKnownValuesStatics),
        reinterpret_cast<void**>(statics.GetAddressOf()));
    
    int x = 0;
    statics->GetZero(&x);

Aside from the error handling which has been omitted for brevity, this code is equivalent to the C++/CX invocation of GetZero from above:

    int x = KnownValues::GetZero(); 
  • And now look how "difficult" is this in real C++:

    code.google.com/.../App.cpp

    What? It's one-liner like the one presented here? No ref sealed classes, no hats, no WRL? ISO C++ conformant? That's right boy. With a little effort C++/CX can go where it belongs.

  • Hi Tomas,

    That is hardly an accurate description of the code to which you link:  it certainly does utilize WRL and the ABI-level API, inside of the generated wrappers.

    It's been noted several times--in this series of articles and elsewhere--that generating wrappers around the ABI layer is a viable approach to Windows Runtime development using C++.  There are advantages and disadvantages both with that approach and with the language extensions approach taken with C++/CX.  These advantages and disadvantages have been discussed at length, both on this blog and on Channel 9.

  • James

    I have to react again because I want to avoid any misunderstanding here. From other comments under this series I see that my opinions are shared with other people so it might be helpful for you to understand my rather critical view of C++/CX.

    I participated in both discussions you mention so I know exactly what was discussed and which conclusion were made. Let me recapitulate. The main problem I and others raised is that you implemented WinRT support as a set of extesnsions to your c++ compiler. This has clear disadvantages: 1) compilers from other vendors and even older MS compilers are out of the game. 2) Noone knows how long will this be supported - remember some of your previous technologies like managed c++, c++/cli and where they are now. Bot of these are very serious problems with many consequences to developers like me.

    Now let's talk about advantages. The important channel9 discussion was moderated by Charles and his main argument was that by using c++ you will never be able to get as concise and readable code as by using c++/cx. Well people tended to disagree and I decided to prove that by building small example mentioned above. Yes it does utilize WRL inside generated wrappers. But that's not the point. User doesn't need to know about HRESULTs, WRL and other low-level things. He just interacts with these wrapers exclusively. And the resulting code is exactly as concise and readable as using c++/cx but without any language extensions. So Charles's argument is dead. This is my contribution to the discussion.

    There was also second argument made by Herb Sutter about additional optimizations done by c++/cx compiler and about "cleaner" callstack when debugging. Well I doubt about it. I don't know why c++ generator approach should be any slower than c++/cx. And having callstack shorter by couple of calls is not really an issue for anybody.

    So at the end the disadvantages are clear. But what exactly are the advantages here? You are trying to sold us this technology but you failed to explain its benefit to us c++ developers (if there are any). If MS would invest its money to conformant c++ compiler instead it would make all us much happier then we are now.

Page 1 of 1 (3 items)