Meet C++/WinRT 2.0: Safe Queries During Destruction

Building on the notion of deferred destruction is the ability to safely query during destruction. COM is based on two central concepts. The first is reference counting and the second is querying for interfaces. IUnknown provides AddRef and Release, which we talked about last time, as well as QueryInterface. This function is heavily used by certain UI frameworks, like Xaml, to traverse the Xaml hierarchy as it simulates its composable type system. Consider a simple example:

struct MainPage : PageT<MainPage>
{
    ~MainPage()
    {
        DataContext(nullptr);
    }
};

This seems harmless, right? This Xaml page wants to clear its data context in its destructor, but DataContext is a property of the FrameworkElement base class and lives on the distinct IFrameworkElement interface. As a result, C++/WinRT must inject a call to QueryInteface to lookup the correct vtable before being able to call the DataContext property. Fortunately, C++/WinRT 2.0 has been hardened to support this. Let’s look at the C++/WinRT implementation of Release (in a slightly simplified form):

uint32_t Release() noexcept
{
    uint32_t const remaining = subtract_reference();

    if (remaining == 0)
    {
        m_references = 1; // Debouncing!
        T::final_release(...);
    }

    return remaining;
}

As you can imagine, it first decrements the reference count and only acts if there are no outstanding references. However before calling the static final_release function I described last time, it stabilizes the reference count by setting it to one. I like to call this debouncing, to borrow a term from electrical engineering. This is critical because once the final reference has been released, the reference count is unstable and unable to reliably support a call to QueryInterface.

Calling QueryInterface is dangerous because the reference count can conceivably grow indefinitely. Care must be taken only to call known code paths that will not prolong the life of the object. That’s up to you, but at least C++/WinRT will ensure that those QueryInterface calls can be made reliably. It does so through reference count stabilization. When the final reference has been released, the actual reference count is either zero or some wildly unpredictable value. The latter may occur if weak references are involved. Either way, this is unsustainable if a subsequent call to QueryInterface occurs because that will necessarily cause the reference count to increment temporarily – hence the reference to debouncing. Setting it to one ensures that a final call to Release will never again occur on this object, which is precisely what we want since the unique_ptr now owns the object, but bounded calls to QueryInterface/Release pairs will be safe. Consider a more interesting example:

struct MainPage : PageT<MainPage>
{
    ~MainPage()
    {
        DataContext(nullptr);
    }
    static fire_and_forget final_release(std::unique_ptr<MainPage> ptr)
    {
        co_await 5s;
        co_await resume_foreground(ptr->Dispatcher());
        ptr = nullptr;
    }
};

First up the final_release function is called, notifying the implementation that it’s time to clean up. This final_release happens to be a coroutine. It first waits on the thread pool for a few seconds – just for fun – before resuming on the page’s dispatcher thread. This involves a query since Dispatcher is a property of the DependencyObject base class. Now the page is finally deleted by virtue of assigning nullptr to the unique_ptr. This in turn calls the page’s destructor. Inside the destructor we clear the data context, which as we know requires a query for the FrameworkElement base class.

All of this possible because of the reference count debouncing or stabilization that is now provided by C++/WinRT 2.0! And that’s all for today. Stay tuned for more.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s