Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
winrt::implements struct şablonu, kendi C++/WinRT gerçeklemelerinizin (çalışma zamanı sınıfları ve etkinleştirme fabrikaları için) doğrudan veya dolaylı olarak türediği tabandır.
Bu konuda, C++/WinRT 2.0'da winrt::implements uzantısı noktaları açıklanmaktadır. Denetlenebilir nesnelerin (IInspectable arabirimi açısından incelenebilir) varsayılan davranışını özelleştirmek için bu uzantı noktalarını uygulama türlerinize uygulamayı seçebilirsiniz.
Bu uzantı noktaları, uygulama türlerinizin yok edilmesini ertelemenize, yok edilme sırasında güvenli sorgulamalar yapmanıza ve yansıtılan yöntemlerinize giriş ile bu yöntemlerden çıkış noktalarına kanca eklemenize olanak tanır. Bu konu başlığı altında bu özellikler açıklanır ve bunları ne zaman ve nasıl kullanacağınız hakkında daha fazla bilgi ve bulabilirsiniz.
Ertelenen yıkım
Doğrudan ayırmaları tanılama konusunda, uygulama türünüzün özel bir yıkıcıya sahip olmadığını belirtmiş olduk.
Genel bir yıkıcıya sahip olmanın avantajı, nesnenizde son IUnknown::Release çağrısını algılama ve ardından bu nesnenin sahipliğini alarak yok edilmesini süresiz olarak erteleme olanağı sunan ertelenmiş yok etme özelliğini etkinleştirmesidir.
Klasik COM nesnelerinin içsel olarak başvuru sayıldığını hatırlayın; başvuru sayısı IUnknown::AddRef ve IUnknown::Release işlevleri aracılığıyla yönetilir. Geleneksel bir Release uygulamasında, başvuru sayısı 0'a ulaştığında klasik bir COM nesnesinin C++ yıkıcısı çağrılır.
uint32_t WINRT_CALL Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
delete this;
}
return remaining;
}
delete this;, nesnenin kapladığı belleği serbest bırakmadan önce nesnenin yıkıcısını çağırır. Yıkıcınızda ilginç bir şey yapmanıza gerek olmadığı şartıyla bu işlem yeterince iyi çalışır.
using namespace winrt::Windows::Foundation;
...
struct Sample : implements<Sample, IStringable>
{
winrt::hstring ToString() const;
~Sample() noexcept
{
// Too late to do anything interesting.
}
};
İlginç derken ne demek istiyoruz? Bir kere, bir yıkıcı doğası gereği eşzamanlıdır. İş parçacıkları arasında geçiş yapamazsınız; belki de iş parçacığına özgü bazı kaynakları farklı bir bağlamda yok edebilirsiniz. Belirli kaynakları boşaltmak için ihtiyacınız olabilecek başka bir arabirim için nesneyi güvenilir bir şekilde sorgulayamazsınız. Liste devam ediyor. Yıkımınızın önemsiz olmadığı durumlarda daha esnek bir çözüme ihtiyacınız vardır. Burada C++/WinRT'nin final_release işlevi devreye girer.
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.
}
};
Release'in C++/WinRT uygulamasını, nesnenizin başvuru sayacı 0'a geçtiği anda final_release'inizi çağıracak şekilde güncelledik. Bu durumda nesne, geçerli başka referans kalmadığından emin olabilir ve artık kendisi üzerinde münhasır sahipliğe sahiptir. Bu nedenle sahipliğini statik final_release işlevine aktarabilir.
Başka bir deyişle, nesne kendisini paylaşılan sahipliği destekleyen bir nesneden özel mülkiyete dönüştürmüştür. std::unique_ptr nesnenin özel mülkiyetine sahiptir ve dolayısıyla std::unique_ptr kapsamın dışına çıktığında (bundan önce başka bir yere taşınmaması koşuluyla) nesneyi semantiğinin bir parçası olarak doğal olarak yok eder; dolayısıyla genel yıkıcıya ihtiyaç duyulduğunda). Anahtar da bu. std::unique_ptr nesnesini canlı tutması koşuluyla, nesnesini süresiz olarak kullanabilirsiniz. Aşağıda nesneyi başka bir yere nasıl taşıyabileceğinizi gösteren bir çizim gösterilmiştir.
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));
}
};
Bu kod, nesnesini batch_cleanup adlı bir koleksiyona kaydeder ve bu koleksiyonda işleri, uygulamanın çalışma zamanının gelecekteki bir noktasındaki tüm nesneleri temizlemek olacaktır.
Normalde, std::unique_ptr yok edildiğinde nesne de yok edilir; ancak std::unique_ptr::reset işlevini çağırarak onun daha erken yok edilmesini sağlayabilir veya std::unique_ptr’yi bir yerde saklayarak bunu erteleyebilirsiniz.
Belki de daha pratik ve daha güçlü olarak, final_release fonksiyonunu bir korutine dönüştürebilir ve gerektiğinde askıya alıp iş parçacıkları arasında geçiş yapabilirken, sonunda yok edilmesini tek bir yerde ele alabilirsiniz.
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.
}
};
Askıya alma noktası, başlangıçta IUnknown::Release işlevine yapılan çağrıyı başlatan çağrı iş parçacığının geri dönmesine neden olur ve böylece çağırana bir kez tuttuğu nesnenin artık bu arabirim işaretçisi aracılığıyla kullanılamadığını bildirir. UI çerçevelerinin genellikle nesneleri başlangıçta oluşturan belirli ui iş parçacığında yok edilmesini sağlaması gerekir. Bu özellik, imha nesnenin serbest bırakılmasından ayrıldığından bu tür bir gereksinimin yerine getirilmesini önemsiz hale getirir.
final_release geçirilen nesnenin yalnızca bir C++ nesnesi olduğunu unutmayın; artık bir COM nesnesi değil. Örneğin, nesneye yönelik mevcut COM zayıf başvuruları artık çözümlenmez.
İmha sırasında güvenli sorgular
Ertelenmiş yok etme kavramının bir uzantısı da, yok etme sırasında arabirimlerin güvenle sorgulanabilmesidir.
Klasik COM, iki merkezi kavramı temel alır. Birincisi başvuru sayma, ikincisi ise arabirimleri sorgulamaktır. AddRef ve Release'a ek olarak, IUnknown arabirimi QueryInterface sağlar. Bu yöntem, XAML gibi bazı UI çerçeveleri tarafından, birleştirilebilir tür sistemini simüle ederken XAML hiyerarşisinde gezinmek için yaygın olarak kullanılır. Basit bir örnek düşünün.
struct MainPage : PageT<MainPage>
{
~MainPage()
{
DataContext(nullptr);
}
};
Bu zararsız görünebilir. Bu XAML sayfası, kendi yıkıcısında veri bağlamını temizlemek istiyor. Ancak DataContext , FrameworkElement temel sınıfının bir özelliğidir ve ayrı IFrameworkElement arabiriminde bulunur. Sonuç olarak, C++/WinRT'nin DataContext özelliğini çağırabilmek için doğru vtable'ı aramak için QueryInterface'e bir çağrı eklemesi gerekir. Ama destructor’ın içinde olmamızın nedeni, referans sayısının 0’a düşmüş olmasıdır. Burada QueryInterface çağrısı, başvuru sayacını geçici olarak artırır; yeniden 0’a döndüğünde de nesne yeniden yok edilir.
C++/WinRT 2.0 bunu desteklemek için sağlamlaştırılmıştır. İşte Release'in basitleştirilmiş haliyle C++/WinRT 2.0 uygulaması.
uint32_t Release() noexcept
{
uint32_t const remaining{ subtract_reference() };
if (remaining == 0)
{
m_references = 1; // Debouncing!
T::final_release(...);
}
return remaining;
}
Tahmin etmiş olabileceğiniz gibi, önce referans sayacını azaltır, ardından yalnızca kalan referans yoksa işlem yapar. Ancak, bu başlıkta daha önce açıkladığımız statik final_release fonksiyonunu çağırmadan önce, referans sayısını 1'e ayarlayarak sabitler. Buna debouncing diyoruz (elektrik mühendisliğinden ödünç alınmış bir terimle). Bu, son referansın yayınlanmasını önlemek için kritik öneme sahiptir. Böyle olduğunda başvuru sayacı istikrarsız hâle gelir ve QueryInterface çağrısını güvenilir biçimde destekleyemez.
Son referans serbest bırakıldıktan sonra QueryInterface çağrısı yapmak tehlikelidir, çünkü referans sayısı o noktadan sonra teorik olarak süresiz biçimde artabilir. Yalnızca nesnenin ömrünü uzatmayacak bilinen kod yollarını çağırmak sizin sorumluluğunuzdadır. C++/WinRT, bu QueryInterface çağrılarının güvenilir bir şekilde yapılabilmesini sağlayarak sizi yarı yolda karşılar.
Bunu, referans sayısını kararlı hâle getirerek yapar. Son referans serbest bırakıldıktan sonra, gerçek referans sayısı ya 0 olur ya da tamamen öngörülemez bir değer alır. İkinci durum, zayıf başvurular söz konusu olduğunda ortaya çıkabilir. Her iki durumda da, QueryInterface’e sonraki bir çağrı gerçekleşirse bu durum sürdürülebilir değildir; çünkü bu, referans sayısının geçici olarak artmasına yol açacaktır—debouncing’e yapılan gönderme de bundandır. Bunu 1 olarak ayarlamak , Release için son bir çağrının bu nesnede bir daha asla gerçekleşmemesini sağlar. Tam olarak istediğimiz budur çünkü std::unique_ptr artık nesnenin sahibidir, ancak QueryInterface/Release çiftlerine yapılan sınırlanmış çağrılar güvenli olacaktır.
Daha ilginç bir örnek düşünün.
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;
}
};
İlk olarak , final_release işlevi çağrılır ve uygulamaya temizleme zamanının geldiğini bildirir. Burada, final_release bir coroutine’dir. İlk askıya alma noktasını simüle etmek için, iş parçacığı havuzunda birkaç saniye bekleyerek başlar. Ardından sayfanın dispatcher queue iş parçacığında yürütülmeye devam eder.
DispatcherQueue,DependencyObject temel sınıfından erişilebilir olduğundan bu son adım bir sorgu içerir. Son olarak, sayfa, nullptr öğesinin std::unique_ptr'ye atanmasıyla gerçekten silinir. Bu da sayfanın yok edicisini çağırır.
Yıkıcının içinde veri bağlamını temizleriz; bildiğimiz gibi FrameworkElement temel sınıfı için bir sorgu gerektirir.
Tüm bunlar, C++/WinRT 2.0’ın sağladığı referans sayısı sekme önleme (veya referans sayısının kararlı hâle getirilmesi) sayesinde mümkündür.
Yöntem girişi ve çıkış kancaları
Daha az yaygın kullanılan bir uzantı noktası , abi_guard yapısı ve abi_enter ve abi_exit işlevleridir.
Uygulama türünüz abi_enter adlı bir işlev tanımlıyorsa, bu işlev yansıtılmış arabirim yöntemlerinizin her birinin girişinde çağrılır (IInspectable yöntemleri hariç).
Benzer şekilde, abi_exit tanımlarsanız, bu tür her yöntemden çıkışta çağrılır; ancak abi_enter bir özel durum oluşturursa çağrılmaz. Yansıtılan arabirim yönteminizin kendisi tarafından bir özel durum oluşturulursa yine çağrılır.
Örneğin, bir istemci nesne kullanılamaz duruma getirildikten sonra (örneğin, bir ShutDown veya Disconnect yöntemi çağrısından sonra) bir nesne kullanmayı denerse, varsayımsal bir invalid_state_error özel durumu oluşturmak için abi_enter kullanabilirsiniz. C++/WinRT yineleyici sınıfları, temel alınan koleksiyon değiştiyse abi_enter işlevinde geçersiz durum özel durumu atmak için bu özelliği kullanır.
Basit abi_enter ve abi_exit işlevlerine ek olarak, abi_guard adlı iç içe bir tür tanımlayabilirsiniz. Bu durumda, yansıtılan arabirim yöntemlerinizin her birine (IInspectable olmayan) girişte bir abi_guard örneği oluşturulur ve nesneye oluşturucu parametresi olarak başvuru yapılır. abi_guard daha sonra yöntemden çıkıldığında imha edilir. İstediğiniz herhangi bir ek durumu abi_guard türünüzün içine yerleştirebilirsiniz.
Kendi abi_guard öğenizi tanımlamazsanız, oluşturulurken abi_enter ve yok edilirken abi_exit çağıran varsayılan bir tane vardır.
Bu korumalar yalnızca yansıtılan arabirim aracılığıyla bir yöntem çağrıldığında kullanılır. Yöntemleri doğrudan uygulama nesnesi üzerinde çağırırsanız, bu çağrılar herhangi bir koruma olmadan doğrudan uygulamaya gider.
İşte bir kod örneği.
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