May 24th, 2021

On static methods in the Windows Runtime and C++/WinRT

The Windows Runtime supports static members, which are members that apply to a class as a whole, rather than to particular instances of a class. The expectation is that these members are exposed via the language projection as static members of some language-specific representation of the class.

Under the covers, though, there are no static members. That’s because static members have to belong to a class, but the Windows Runtime uses COM as its low-level interface, and all COM members belong to COM interfaces, and COM interfaces are implemented by objects.

For illustration purposes, say we have this Windows Runtime class:

runtimeclass Widget
{
    Widget(); // default constructor
    Widget(String name); // nondefault constructor

    void InstanceMethod();

    static void StaticMethod();
}

Accessing the Instance­Method() from a Widget object is easy: The object implements IWidget, and you call the IWidget::Instance­Method() method on it.

But it’s less obvious how you get to the constructors and static methods. Because those start from nothing; there is no object in your hand from which to call the methods.

The solution is to fabricate another object, known as the activation factory. This object contains all the operations that are not dependent upon an existing instance. You can think of this object as representing the class itself.

from nothing
RoGetActivationFactory(“Widget”)
Widget factory
IActivationFactory
ActivateInstance()
IWidgetFactory
CreateInstance(name)
IWidgetStatics
StaticMethod()

Every activation factory implements IActivation­Factory at a minimum. This interface provides the default constructor, known as IActivation­Factory::Activate­Instance(). Even if an object doesn’t have a default constructor, the IActivation­Factory interface will still be there; its Activate­Instance method will just return E_NOTIMPL.

If a class has nondefault constructors, they exist on a separate IWidgetFactory method. By convention, these nondefault constructor methods are named Create­Instance or some variation thereof.

And if a class has static members, then they exist on an IWidgetStatics method.

For example, under the covers, calling a static method works like this:

IWidgetStatics* statics;
RoGetActivationFactory(L"Widget", IID_PPV_ARGS(&statics));
statics->StaticMethod();

Okay, so how does C++/WinRT represent static members?

At the projection level, they look like static class members.

winrt::Widget::StaticMethod();

C++/WinRT does the under-the-covers thing we described above, though with the bonus feature of caching the activation factory object for better performance.

At the implementation level, what happens depends on what version of C++/WinRT you’re using.

In C++/WinRT version 1 (or C++/WinRT without the /optimize option), the implementation mirrors the under-the-covers behavior:

namespace winrt::factory_implementation::Widget
{
    struct Widget : WidgetT<Widget>
    {
        // instance method on factory object
        void SomeMethod() { ... }
    }
}

C++/WinRT autogenerates the IActivation­Factory and IWidget­Factory by having the corresponding methods construct an instance via a corresponding public constructor of the implementation::Widget type. But the static members are up to you to implement, and they are members of the factory object.

When the Widget object is consumed by the projection, it goes through the factory:

winrt::Widget::StaticMethod
RoGetActivationFactory(“Widget”)
Widget factory
IActivationFactory
ActivateInstance()
(autogenerated)
IWidgetFactory
CreateInstance(name)
(autogenerated)
IWidgetStatics
StaticMethod()

(I put the static method call in a dotted box to emphasize that there is no object involved here. It’s a free function.)

In the case where your static methods are stateless, this creates an inefficiency in the projection when used from within the same module: They still go through the formality of obtaining a factory and calling the nominally static method as a member method of the IWidget­Statics interface. But if the static method is stateless, then it has no use for the factory object. We went through the effort of locating it, and then making a virtual method call on it, when we could have just gone straight to the implementation.

C++/WinRT version 2 with the /optimize option fixes this. Calls to static methods are forwarded to the corresponding static method on the implementation class, rather than to the instance method on the factory implementation class.

namespace winrt::implementation::Widget
{
    struct Widget : WidgetT<Widget>
    {
        // static method on instance object
        static void SomeMethod() { ... }
    }
}

Furthermore, the factory implementation also forwards its instance members (corresponding to Windows Runtime static members) to the static members of the implementation type.

    winrt::Widget::StaticMethod
     
External consumer Widget factory   Widget instance static members
    IActivationFactory    
    ActivateInstance()
(autogenerated)
    IWidgetFactory
    CreateInstance(name)
(autogenerated)
    IWidgetStatics
    StaticMethod() StaticMethod()

Sending the projection’s static method straight to the instance static method avoids the hassle of obtaining the widget factory object, which we never use anyway. It avoids the virtual call through the factory’s COM interfaces, and thereby opens inlining opportunities for very simple static methods.

But what if your static methods are stateful? Well, you could just keep that state in global variables, but that’s a problem if some of the state involves COM objects, because you now have a COM object in a global variable: The global variable will destruct when the DLL unloads, which is likely to be after COM has shut down.

This is where the COM static store comes to the rescue. You can ask C++/WinRT to put the class factory in the COM static store by adding static_lifetime to the template parameter list:

namespace winrt::factory_implementation::Widget
{
    struct Widget : WidgetT<Widget, static_lifetime>
    {
        ...
    }
}

Now you can put your state in the factory object, and it will be destructed when COM tears down.

But how do you access the factory object from the static method in the instance object? You’ll just have to get it manually.

namespace winrt::implementation::Widget
{
    struct Widget : WidgetT<Widget>
    {
        // static method on instance object
        // forward to factory object
        static void SomeMethod() {
            get_activation_factory<winrt::Widget, IWidgetStatics>()
                ->StaticMethod();
        }
    }
}

At this point, you start to get a little dizzy because there’s this game of “Where’s the static method?” being played. The instance implementation is forwarding to the factory implementation, but the factory implementation is forwarding to the instance implementation:

You break the infinite loop by implementing the method in the factory implementation, just like you did back in C++/WinRT version 1:

namespace winrt::factory_implementation::Widget
{
    struct Widget : WidgetT<Widget>
    {
        // instance method on factory object
        void SomeMethod() { ... }
    }

    // state variables go here
    int32_t m_state;
}

An explicit implementation in the factory implementation object overrides the default implementation, thereby breaking the cycle.

    winrt::Widget::StaticMethod
     
External consumer Widget factory   Widget instance static members
    IActivationFactory    
    ActivateInstance()
(autogenerated)
    IWidgetFactory
    CreateInstance(name)
(autogenerated)
    IWidgetStatics
    StaticMethod() StaticMethod()

Bonus chatter: Even if you never call the static method yourself, you still have to include a declaration for it, so that the projection short-circuit (the diagonal arrow) can call it. You don’t have to implement it, though.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.