Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A winrt::implements struktúrasablon az az alap, amelyből az Ön saját C++/WinRT-megvalósításai (a futásidejű osztályok és az aktiválási gyárak) közvetlenül vagy közvetve származnak.
Ez a témakör a winrt::implementálások bővítménypontjait ismerteti a C++/WinRT 2.0-s verziójában. Ezeket a bővítménypontokat implementálhatja a megvalósítási típusokon, hogy testre szabhassa a vizsgálható objektumok alapértelmezett viselkedését (az IInspectable felület értelmében vizsgálható).
Ezek a bővítménypontok lehetővé teszik a megvalósítási típusok megsemmisítésének elhalasztását, a biztonságos lekérdezést a megsemmisítés során, valamint a be- és kilépést a tervezett módszerekhez. Ez a témakör ismerteti ezeket a funkciókat, és további tájékoztatást nyújt arról, hogy mikor és hogyan használná őket.
Halasztott megsemmisítés
A közvetlen foglalások diagnosztizálása témakörben említettük, hogy a megvalósítási típus nem rendelkezhet privát destruktorsal.
A nyilvános destruktor előnye, hogy lehetővé teszi a késleltetett megsemmisítést, vagyis azt, hogy észlelhető legyen az objektumon végrehajtott végső IUnknown::Release hívás, majd átvegye az objektum tulajdonjogát, így annak megsemmisítése határozatlan időre elhalasztható.
Ne feledje, hogy a klasszikus COM-objektumok eleve referenciaszámlálásúak; a referenciaszámot az IUnknown::AddRef és IUnknown::Release függvények kezelik. A kiadás hagyományos implementációjában a rendszer meghívja a klasszikus COM-objektum C++ destruktorát, amint a referenciaszám eléri a 0-t.
uint32_t WINRT_CALL Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
delete this;
}
return remaining;
}
A delete this; meghívja az objektum destruktorát, mielőtt felszabadítja az objektum által elfoglalt memóriát. Ez elég jól működik, feltéve, hogy nem kell semmi érdekeset tennie a destruktorban.
using namespace winrt::Windows::Foundation;
...
struct Sample : implements<Sample, IStringable>
{
winrt::hstring ToString() const;
~Sample() noexcept
{
// Too late to do anything interesting.
}
};
Mit értünk az alatt, hogy érdekes? Egyrészt a destruktor eleve szinkron módon működik. Nem válthat szálakra – talán azért, hogy egy másik környezetben tönkretegyen néhány szálspecifikus erőforrást. Bizonyos erőforrások felszabadításához nem kérdezheti le megbízhatóan az objektumot más, esetleg szükséges felülethez. A lista folytatódik. Azokban az esetekben, amikor a megsemmisítés nem triviális, rugalmasabb megoldásra van szüksége. Itt jön be a C++/WinRT final_release függvénye.
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.
}
};
Frissítettük a Release C++/WinRT-implementációját, hogy pontosan akkor hívja meg az Ön final_release függvényét, amikor az objektum referenciaszáma 0-ra vált. Ebben az állapotban az objektum biztos lehet abban, hogy nincsenek további függőben lévő hivatkozások, és most már kizárólagos tulajdona van önmagának. Emiatt a statikus final_release függvényre ruházhatja át saját tulajdonát.
Más szóval, az objektum átalakította magát egy olyanból, amely támogatja a megosztott tulajdonjogot egy kizárólagos tulajdonúvá. Az std::unique_ptr kizárólagos tulajdonosa az objektumnak, így természetesen elpusztítja az objektumot szemantikája részeként – ezért szükség van egy nyilvános destruktorra –, amikor az std::unique_ptr kiesik a hatókörből (feltéve, hogy ezt megelőzően nem helyezték át máshová). És ez a kulcs. Az objektum határozatlan ideig használható, feltéve, hogy az std::unique_ptr életben tartja az objektumot. Íme egy ábra, amely bemutatja, hogyan helyezheti át az objektumot máshová.
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));
}
};
Ez a kód egy batch_cleanup nevű gyűjteménybe menti az objektumot, amelynek egyik feladata az összes objektum törlése az alkalmazás futási idejének egy későbbi pontján.
Az objektum általában akkor semmisül meg, amikor az std::unique_ptr megsemmisül, de az std::unique_ptr::reset meghívásával hamarabb is megsemmisítheti; vagy el is halaszthatja, ha elmenti valahová az std::unique_ptr-t.
Talán gyakorlatiasabb és hatékonyabb megoldás, ha a final_release függvényt korutinná alakítja, és annak későbbi megsemmisítését egy helyen kezeli, miközben szükség szerint felfüggesztheti a végrehajtást és szálak között válthat.
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.
}
};
A felfüggesztés hatására a hívó szál – amely eredetileg az IUnknown::Release függvény hívását kezdeményezte – visszatér, és így jelzi a hívónak, hogy az egykor megtartott objektum már nem érhető el ezen az interfészmutatón keresztül. A felhasználói felületi keretrendszereknek gyakran gondoskodniuk kell arról, hogy az objektumok megsemmisüljenek az objektumot eredetileg létrehozó adott felhasználói felületi szálon. Ez a funkció egy ilyen követelmény teljesítését triviálissá teszi, mivel a megsemmisítés elkülönül az objektum felszabadításától.
Vegye figyelembe, hogy a final_release átadott objektum csupán egy C++ objektum; már nem COM-objektum. Például az objektumra vonatkozó meglévő COM-gyenge hivatkozások már nem oldódnak fel.
Biztonságos lekérdezések a megsemmisítés során
A késleltetett megsemmisítés fogalmára épül az a képesség is, hogy a megsemmisítés közben biztonságosan lehessen interfészeket lekérdezni.
A klasszikus COM két központi fogalomon alapul. Az első a hivatkozásszámlálás, a második pedig a felületek lekérdezése. Az AddRef és a Release mellett az IUnknown felület a QueryInterface szolgáltatást is biztosítja. Ezt a módszert bizonyos felhasználói felületi keretrendszerek – például az XAML – erősen használják az XAML-hierarchián való áthaladáshoz, miközben a rendszer a rendszer összeállítható típusrendszerét szimulálja. Vegyünk egy egyszerű példát.
struct MainPage : PageT<MainPage>
{
~MainPage()
{
DataContext(nullptr);
}
};
Ez ártalmatlannak tűnhet . Ez az XAML-oldal törölni szeretné az adatkörnyezetét a destruktorban. A DataContext azonban a FrameworkElement alaposztály tulajdonsága, és a különálló IFrameworkElement felületen található. Ennek eredményeképpen a C++/WinRT-nek be kell injektálnia egy hívást a QueryInterface-be , hogy megkeresse a megfelelő virtuális táblát, mielőtt meghívhatja a DataContext tulajdonságot. De az oka annak, hogy egyáltalán a destruktorban vagyunk, az, hogy a referenciaszámláló nullára csökkent. A QueryInterface itt történő meghívása ideiglenesen megnöveli a hivatkozásszámot; és amikor az ismét 0-ra csökken, az objektum újra megsemmisül.
A C++/WinRT 2.0-t felkészítették arra, hogy ezt támogassa. Íme a Kiadás C++/WinRT 2.0-s implementációja, egyszerűsített formában.
uint32_t Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
m_references = 1; // Debouncing!
T::final_release(...);
}
return remaining;
}
Ahogy azt előre jelezhette, először a hivatkozásszám csökken, majd csak akkor működik, ha nincsenek függőben lévő hivatkozások. Mielőtt azonban meghívja a jelen témakörben korábban ismertetett statikus final_release függvényt, az 1 értékre állításával stabilizálja a hivatkozás számát. Ezt debouncingnak nevezzük (hogy az elektrotechnikából kölcsönvegyünk egy kifejezést). Ez kritikus fontosságú a végső hivatkozás kiadásának megakadályozása érdekében. Ha ez megtörténik, a referenciaszám instabil, és nem képes megbízhatóan támogatni a QueryInterface hívását.
A QueryInterface meghívása a végleges hivatkozás kiadása után veszélyes, mert a hivatkozásszám ezután meghatározhatatlanul növekedhet. Az Ön felelőssége, hogy csak olyan ismert kódútvonalakat hívjon meg, amelyek nem hosszabbítják meg az objektum élettartamát. A C++/WinRT félúton találkozik Önrel, és biztosítja, hogy ezek a QueryInterface-hívások megbízhatóan kezdeményezhetők legyenek.
Ezt a hivatkozásszám stabilizálásával teszi. A végső hivatkozás kiadásakor a tényleges referenciaszám vagy 0, vagy valami vadul kiszámíthatatlan érték. Ez utóbbi eset akkor fordulhat elő, ha gyenge hivatkozásokról van szó. Akárhogy is, ez nem fenntartható, ha később sor kerül egy QueryInterface-hívásra, mert az szükségszerűen átmenetileg megnöveli a referenciaszámlálót — innen a pattogásmentesítésre való utalás. Az 1 értékre állítása biztosítja, hogy a kiadás végleges hívása soha többé ne történjen meg ezen az objektumon. Pontosan ezt szeretnénk, mivel az std::unique_ptr most már az objektum tulajdonosa, de a QueryInterface/Release párokra irányuló kötött hívások biztonságosak lesznek.
Vegyünk egy érdekesebb példát.
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;
}
};
Először meghívja a final_release függvényt, amely értesíti a megvalósítást, hogy ideje megtisztítani. Itt a final_release történetesen egy korutin. Az első felfüggesztési pont szimulálásához néhány másodpercig várnia kell a szálkészletre. Ezután folytatódik a lap kézbesítői üzenetsor-szálán. Ez az utolsó lépés egy lekérdezést is magában foglal, mivel a DispatcherQueue elérhető a DependencyObject alaposztályból. Végül a lap ténylegesen törlődik azáltal, hogy a nullptr hozzá van rendelve a std::unique_ptr-hoz. Ez viszont meghívja az oldal destruktorát.
A destruktoron belül töröljük az adatkörnyezetet; amely, mint tudjuk, egy lekérdezést igényel a FrameworkElement alaposztályhoz.
Mindez a C++/WinRT 2.0 által biztosított hivatkozásszám-kisimításnak (vagy hivatkozásszám-stabilizálásnak) köszönhetően lehetséges.
Metódus belépési és kilépési pontjai
A ritkábban használt bővítménypont a abi_guard szerkezet, valamint a abi_enter és abi_exit függvény.
Ha a megvalósítási típus definiál egy abi_enter függvényt, akkor a rendszer ezt a függvényt minden egyes leképezett felületmetódus elején meghívja (az IInspectable metódusait nem számítva).
Hasonlóképpen, ha abi_exit definiál, akkor ezt minden ilyen módszerből való kilépéskor hívjuk meg; de nem lesz meghívva, ha a abi_enter kivételt jelez. A rendszer akkor is meghívja, ha a kivetített felületi metódus kivételt jelez.
Például használhatja a abi_enter függvényt arra, hogy egy feltételezett invalid_state_error kivételt dobjon, ha egy kliens egy objektumot próbál használni, miután az objektum használhatatlan állapotba került — például egy ShutDown vagy Disconnect metódushívás után. A C++/WinRT iterátorosztályok ezzel a funkcióval érvénytelen állapotkivételt eredményeznek a abi_enter függvényben, ha az alapul szolgáló gyűjtemény megváltozott.
Az egyszerű abi_enter és abi_exitfüggvények felett definiálhat egy beágyazott típust abi_guard néven. Ebben az esetben a projektált felület minden egyes (nem IInspectable) metódusába való belépéskor létrejön a abi_guard egy példánya, amely az objektumra mutató hivatkozást kapja meg konstruktorparaméterként. A abi_guard ezután a metódusból való kilépéskor destrukálódik. Bármilyen extra állapotot elhelyezhet a abi_guard típusba.
Ha nem definiál saját abi_guard elemet, akkor egy alapértelmezett kerül használatra, amely létrehozáskor a abi_enter, megsemmisítéskor pedig a abi_exit függvényt hívja meg.
Ezeket az őröket csak akkor használja a rendszer, ha egy metódust a kivetített felületen keresztül hív meg. Ha közvetlenül a megvalósítási objektumon hívja meg a metódusokat, akkor ezek a hívások közvetlenül a megvalósításhoz kerülnek, őrök nélkül.
Íme egy példa kódra.
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