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:
- converting each constructor into a function that returns a new instance of the type,
- defining an interface, called a factory interface, that declares all of those construction functions,
- defining a runtime class, called an activation factory, that implements the factory interface, and
- 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] [retval] int* 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_KnownValues, BaseTrust)
public: STDMETHODIMP GetZero(int* value) override { *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();
0 comments