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.
}
其他
- WinRT Extensions using RuntimeBroker
- ReactNative for Windows Marshalling Data
- RoGetBufferMarshaler function
(本篇完)