Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

23 May 2020

C++/WinRT学习笔记(十):扩展作成类

Extension points for your implementation types

winrt::implements提供了一些自定义的行为,让你可以自定义inspectable 对象的行为。

winrt::implements实现的类不能有私有的析构函数,其析构函数必须是公有的。公有的析构有一个特殊的好处,就是可以捕获最后一个IUnknown::Release 调用,从而接管类对象的生命周期。你只要提供一个final_release ,就可以在引用计数归0时被c++winrt自动调用,例子:

struct Sample : implements<Sample, IStringable>
{
    winrt::hstring ToString() const;
 
    static void final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        // This is the first stop...
    }
 
    ~Sample() noexcept
    {
        // ...And this happens only when *unique_ptr* finally deletes the object.
    }
};

你可以在final_release里面将对象的指针挪作:

    static void final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        gc.push_back(std::move(ptr));
    }

final_release甚至可以是一个coroutine:

    static winrt::fire_and_forget final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        co_await winrt::resume_background(); // Unwind the calling thread.
 
        // Safely perform complex teardown here.
    }

传统的COM基于引用计数,AddRef增加计数,Release减少计数。但是还有一个QueryInterface会隐性增加计数,不小心的话就会中招:

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

上面的例子中,析构函数~MainPage执行的时候,引用计数已经变为0了。但是执行DataContext(nullptr)的时候,会调用QueryInterface查询IFrameworkElement 接口,导致引用计数增加。当引用计数再次降为零的时候,会导致二次调用~MainPage。C++/WinRT对此的处理办法也很简单:

uint32_t Release() noexcept
{
    uint32_t const remaining{ subtract_reference() };
 
    if (remaining == 0)
    {
        m_references = 1; // Debouncing!
        T::final_release(...);
    }
 
    return remaining;
}

就是在final_release的时候将Debounce一下,将m_references 设置为1,保证final_release里面的QueryInterface能够正常执行,避免二次析构,所以下面的例子也是可行的:

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

另它几个扩展点是在abi_guard,以及abi_enter 和abi_exit 函数。如果定义了abi_enter,那么这个函数会在每次投射类(除IInspectable)执行某个方法之前被调用。相应的,abi_exit会在前述方法执行之后调用,前提是abi_enter不抛出异常。abi_guard 的话,是一个类型,会在每次执行投射类方法的时候生成,用于保存自定义的状态。

下面是一些示例:

struct Sample : SampleT<Sample, IClosable>
{
    void abi_enter();
    void abi_exit();

    void Close();
};

void example1()
{
    auto sampleObj1{ winrt::make<Sample>() };
    sampleObj1.Close(); // Calls abi_enter and abi_exit.
}

void example2()
{
    auto sampleObj2{ winrt::make_self<Sample>() };
    sampleObj2->Close(); // Doesn't call abi_enter nor abi_exit.
}

// A guard is used only for the duration of the method call.
// If the method is a coroutine, then the guard applies only until
// the IAsyncXxx is returned; not until the coroutine completes.

IAsyncAction CloseAsync()
{
    // Guard is active here.
    DoWork();

    // Guard becomes inactive once DoOtherWorkAsync
    // returns an IAsyncAction.
    co_await DoOtherWorkAsync();

    // Guard is not active here.
}

其他

(本篇完)

comments powered by Disqus