Skip to content

C++/WinRT deferred destruction and safe QI during destruction#108

Merged
kennykerr merged 2 commits into
masterfrom
kennykerr2
Dec 13, 2018
Merged

C++/WinRT deferred destruction and safe QI during destruction#108
kennykerr merged 2 commits into
masterfrom
kennykerr2

Conversation

@kennykerr

Copy link
Copy Markdown
Contributor

Xaml apps can get themselves into knots because they need to perform a QI in a destructor to call some cleanup implementation up or down the hierarchy. This call involves a QI after the object's reference count has already reached zero. This update adds support for debouncing the reference count, ensuring that once it reaches zero it can never be resurrected but still allows QI for temporaries required during destruction. This all seems rather messy but is unavoidable in certain Xaml apps/controls and C++/WinRT should be resilient to this. The downside of debouncing is that it makes AddRef and Release a bit more expensive. The up side is that C++/WinRT is hardened and far more resilient to such abuses.

Destruction may be deferred by providing a static final_release function and moving ownership of the unique_ptr to some other context.

struct Sample : implements<Sample, IStringable>
{
    hstring ToString()
    {
        return L"Sample";
    }

    ~Sample()
    {
        // Called when the unique_ptr below is reset.
    }

    static void final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        // Move 'ptr' as needed to delay destruction.
    }
};

In the example below once the MainPage is released (for the final time), final_release is called, which spends 5s waiting (on the thread pool) then resumes using the page’s Dispatcher (which requires QI/AddRef/Release to work). It then clears the unique_ptr, which causes the MainPage destructor to actually get called. Even here DataContext is called, which requires a QI for IFrameworkElement.

Obviously you don’t have to implement your final_release as a coroutine but that does work and makes it very simple to move destruction to a different thread.

struct MainPage : PageT<MainPage>
{
    MainPage()
    {
    }

    ~MainPage()
    {
        DataContext(nullptr);
    }

    static IAsyncAction final_release(std::unique_ptr<MainPage> ptr)
    {
        co_await 5s;

        co_await resume_foreground(ptr->Dispatcher());

        ptr = nullptr;
    }
};

@Scottj1s Scottj1s left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@kennykerr kennykerr merged commit 0fe6ed5 into master Dec 13, 2018
{
// It's safe to QI/AddRef/Release inside destructor.
IStringable s;
check_hresult(QueryInterface(guid_of<IStringable>(), put_abi(s)));

@DefaultRyan DefaultRyan Dec 13, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some test cases involving IWeakReferenceSource and IWeakReference would be more valuable than IStringable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well IStringable proves that QI et al works, but agreed that adding test for IWeakReferenceSource is important to show that Resolve does not work. 😉

@kennykerr kennykerr deleted the kennykerr2 branch December 13, 2018 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants