Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Šablona struktury winrt::implements je základem, z něhož vaše vlastní implementace C++/WinRT (běhových tříd a aktivačních továren) přímo nebo nepřímo dědí.
Toto téma popisuje body rozšíření winrt::implements v jazyce C++/WinRT 2.0. Tyto rozšiřující body můžete implementovat u typů implementace, abyste přizpůsobili výchozí chování kontrolovatelných objektů (kontrolovatelné ve smyslu rozhraní IInspectable ).
Tyto body rozšíření vám umožňují odložit zničení vašich implementačních typů, bezpečně provádět dotazy během ničení a navázat se na vstup do vašich projektovaných metod i na výstup z nich. Toto téma popisuje tyto funkce a vysvětluje další informace o tom, kdy a jak je používat.
Odložené zničení
V tématu Diagnostika přímých přidělení jsme zmínili, že váš typ implementace nemůže mít privátní destruktor.
Výhodou veřejného destruktoru je, že umožňuje odloženou destrukci, tedy možnost rozpoznat poslední volání IUnknown::Release u vašeho objektu a poté převzít vlastnictví tohoto objektu, aby bylo možné jeho destrukci odložit na neurčito.
Připomeňme si, že klasické objekty COM jsou svou podstatou založeny na počítání odkazů; tento počet odkazů se spravuje pomocí funkcí IUnknown::AddRef a IUnknown::Release. V tradiční implementaci Release se po dosažení počtu referencí 0 vyvolá destruktor klasického objektu COM v jazyce C++.
uint32_t WINRT_CALL Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
delete this;
}
return remaining;
}
delete this; zavolá destruktor objektu před uvolněním paměti obsazené objektem. To funguje dostatečně dobře, za předpokladu, že v destruktoru nemusíte dělat nic zajímavého.
using namespace winrt::Windows::Foundation;
...
struct Sample : implements<Sample, IStringable>
{
winrt::hstring ToString() const;
~Sample() noexcept
{
// Too late to do anything interesting.
}
};
Co myslíme zajímavým? Pro jednu věc je destruktor ze své podstaty synchronní. Nemůžete přepínat vlákna – například za účelem uvolnění některých prostředků vázaných na konkrétní vlákno v jiném kontextu. Nemůžete spolehlivě dotazovat objekt na některé jiné rozhraní, které možná budete potřebovat, aby bylo možné uvolnit určité prostředky. Seznam pokračuje. V případech, kdy zničení není triviální, potřebujete flexibilnější řešení. To je místo, kde přichází funkce final_release jazyka 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.
}
};
Aktualizovali jsme implementaci Release v C++/WinRT tak, aby volala vaši funkci final_release přesně ve chvíli, kdy počet odkazů vašeho objektu klesne na 0. V takovém stavu si objekt může být jistý, že již neexistují žádné další zbývající reference, a má tak výhradní vlastnictví sebe sama. Z tohoto důvodu může převést vlastnictví sebe sama na statickou funkci final_release .
Jinými slovy, objekt se transformoval z objektu, který podporuje sdílené vlastnictví na objekt, který je vlastněný výhradně. Std::unique_ptr má výhradní vlastnictví objektu, takže ho přirozeně zničí jako součást jeho sémantiky – tedy potřebu veřejného destruktoru – když std::unique_ptr zmizí z rozsahu (za předpokladu, že se předtím nepřesune jinde). A to je ten klíč. Objekt můžete použít po neomezenou dobu za předpokladu, že std::unique_ptr objekt zůstane aktivní. Tady je obrázek, jak můžete přesunout objekt jinam.
struct Sample : implements<Sample, IStringable>
{
winrt::hstring ToString() const;
static void final_release(std::unique_ptr<Sample> ptr) noexcept
{
batch_cleanup.push_back(std::move(ptr));
}
};
Tento kód uloží objekt do kolekce s názvem batch_cleanup, jejímž jedním z úkolů bude v určitém budoucím okamžiku během běhu aplikace odstranit všechny objekty.
Za normálních okolností se objekt zničí, když se zničí std::unique_ptr, ale jeho zničení můžete urychlit voláním std::unique_ptr::reset; nebo je můžete oddálit tím, že std::unique_ptr někam uložíte.
Možná praktičtěji a efektivněji můžete funkci final_release převést na korutinu a její případné zrušení řešit na jednom místě, přitom podle potřeby můžete pozastavovat běh a přepínat vlákna.
struct Sample : implements<Sample, IStringable>
{
winrt::hstring ToString() const;
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.
}
};
Bod pozastavení způsobí, že se volající vlákno, které původně iniciovalo volání funkce IUnknown::Release, vrátí, a tím signalizuje volajícímu, že objekt, který dříve drželo, již není prostřednictvím tohoto ukazatele na rozhraní k dispozici. Architektury uživatelského rozhraní často potřebují zajistit, aby byly objekty zničeny na konkrétním vlákně uživatelského rozhraní, které objekt původně vytvořilo. Díky této funkci je splnění takového požadavku triviální, protože zničení je odděleno od uvolnění objektu.
Všimněte si, že objekt předaný final_release je pouze objekt C++; už není objektEM COM. Například existující slabé odkazy modelu COM na objekt se již nepřeloží.
Bezpečné dotazy při rušení
Na koncept odložené destrukce navazuje možnost bezpečně zjišťovat dostupnost rozhraní během destrukce.
Klasický model COM je založený na dvou centrálních konceptech. Prvním je počítání referencí a druhým je dotazování na rozhraní. Kromě AddRef a Releaseposkytuje IUnknown rozhraní QueryInterface. Tato metoda je silně používána určitými architekturami uživatelského rozhraní , jako je XAML, k procházení hierarchie XAML, protože simuluje svůj kompozovatelný systém typů. Podívejte se na jednoduchý příklad.
struct MainPage : PageT<MainPage>
{
~MainPage()
{
DataContext(nullptr);
}
};
To může vypadat neškodně. Tato stránka XAML chce vymazat kontext dat ve svém destruktoru. Ale DataContext je vlastnost FrameworkElement základní třídy, a to žije v jedinečné IFrameworkElement rozhraní. V důsledku toho musí C++/WinRT vložit volání QueryInterface , aby se před voláním vlastnosti DataContext vyhledala správná vtable. Důvod, proč jsme se vůbec ocitli v destruktoru, je ten, že počet referencí klesl na 0. Volání QueryInterface zde dočasně zvýší čítač referencí; a když pak znovu klesne na 0, objekt se znovu zničí.
C++/WinRT 2.0 bylo upraveno tak, aby tuto funkci podporovalo. Zde je implementace Release v C++/WinRT 2.0 v zjednodušené podobě.
uint32_t Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
m_references = 1; // Debouncing!
T::final_release(...);
}
return remaining;
}
Jak jste možná předpokládali, nejprve sníží čítač referencí a poté provede akci pouze tehdy, pokud nezbývají žádné reference. Než však zavoláte statickou funkci final_release , kterou jsme popsali dříve v tomto tématu, stabilizuje počet odkazů nastavením na 1. Tomu říkáme debouncing (termín přejatý z elektrotechniky). To je zásadní, aby se zabránilo vydání finální reference. Jakmile k tomu dojde, počet odkazů je nestabilní a nedokáže spolehlivě podporovat volání QueryInterface.
Volání QueryInterface je nebezpečné poté, co byla uvolněna poslední reference, protože čítadlo referencí pak může neomezeně růst. Je vaší zodpovědností volat pouze známé cesty kódu, které neprodlouží životnost objektu. C++/WinRT vám vychází vstříc tím, že zajišťuje, aby bylo možné tato volání QueryInterface spolehlivě provádět.
To dělá tím, že stabilizuje počet odkazů. Po uvolnění poslední reference je skutečný počet referencí buď 0, nebo nějaká zcela nepředvídatelná hodnota. K druhému případu může dojít v případě, že jsou zapojeny slabé odkazy. V obou případech je to neudržitelné, pokud následně dojde k volání QueryInterface; to totiž nutně způsobí dočasné zvýšení počtu referencí — odtud zmínka o debouncingu. Nastavení na 1 zajistí, že u tohoto objektu už nikdy nedojde k závěrečnému volání Release. To je přesně to, co chceme, protože std::unique_ptr nyní přebírá vlastnictví objektu, ale omezené dvojice volání QueryInterface/Release budou bezpečné.
Představte si zajímavější příklad.
struct MainPage : PageT<MainPage>
{
~MainPage()
{
DataContext(nullptr);
}
static winrt::fire_and_forget final_release(std::unique_ptr<MainPage> ptr)
{
co_await 5s;
co_await winrt::resume_foreground(ptr->DispatcherQueue());
ptr = nullptr;
}
};
Za prvé se volá final_release funkce, která informuje implementaci, že je čas vyčistit. Tady final_release je korutin. Pokud chcete simulovat první bod pozastavení, začíná čekáním na fond vláken na několik sekund. Poté pokračuje ve vlákně fronty dispečera stránky. Tento poslední krok zahrnuje dotaz, protože DispatcherQueue je přístupný ze základní třídy DependencyObject . Nakonec je stránka skutečně smazána přiřazením nullptr do std::unique_ptr. To zase volá destruktor stránky.
Uvnitř destruktoru vymažeme kontext dat; které, jak víme, vyžaduje dotaz pro základní třídu FrameworkElement .
To vše je možné z důvodu debouncingu počtu odkazů (nebo stabilizace počtu odkazů), které poskytuje C++/WinRT 2.0.
Vstupní a výstupní háky metody
Méně často používaný bod rozšíření je struktura abi_guard a funkce abi_enter a abi_exit .
Pokud váš typ implementace definuje funkci abi_enter, pak se tato funkce volá na vstupu do každé z vašich projektovaných metod rozhraní (nepočítá metody IInspectable).
Podobně pokud definujete abi_exit, pak bude volána na výstupu z každé takové metody; ale nebude volána, pokud vaše abi_enter vyvolá výjimku. Bude stále volána i v případě, že vaše projektovaná metoda rozhraní sama vyvolá výjimku.
Například můžete použít abi_enter k vyvolání hypotetické invalid_state_error výjimky, pokud se klient pokusí použít objekt po umístění objektu do nepoužitelného stavu – například po volání metody ShutDown nebo Disconnect . Třídy iterátoru C++/WinRT používají tuto funkci k vyvolání neplatné výjimky stavu ve funkci abi_enter , pokud se podkladová kolekce změnila.
Nad jednoduchými funkcemi abi_enter a abi_exitmůžete definovat vnořený typ s názvem abi_guard. V takovém případě se při vstupu do každé vaší promítané metody rozhraní, která není typu IInspectable, vytvoří instance abi_guard s odkazem na objekt jako parametrem konstruktoru. Abi_guard se pak destrukuje při ukončení metody. Do typu abi_guard můžete zadat jakýkoliv další stav, který chcete.
Pokud si nedefinujete vlastní abi_guard, existuje výchozí implementace, která při konstrukci volá abi_enter a při zničení abi_exit.
Tyto ochrany se používají pouze tehdy, když je metoda volána prostřednictvím promítnutého rozhraní. Pokud voláte metody přímo na objektu implementace, tato volání jdou přímo na implementaci bez jakýchkoli ochranných kontrol.
Tady je příklad kódu.
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.
}
Windows developer