diff --git a/cppwinrt/component_writers.h b/cppwinrt/component_writers.h index f562dabef..fcb9f5ac7 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -860,7 +860,9 @@ catch (...) { return winrt::to_hresult(); } { auto format = R"( #if defined(WINRT_FORCE_INCLUDE_%_XAML_G_H) || __has_include("%.xaml.g.h") + #include "%.xaml.g.h" + #else namespace winrt::@::implementation diff --git a/nuget/readme.md b/nuget/readme.md index 566a4fd18..4d925d06d 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -77,6 +77,41 @@ To customize common C++/WinRT project properties: * expand the Common Properties item * select the C++/WinRT property page +## InitializeComponent + +In older versions of C++/WinRT, Xaml objects called InitializeComponent from constructors. This can lead to memory corruption if InitializeComponent throws an exception. + +```cpp +void MainPage::MainPage() +{ + // This pattern should no longer be used + InitializeComponent(); +} +``` + +C++/WinRT now calls InitializeComponent automatically and safely, after object construction. Explicit calls to InitializeComponent from constructors in existing code should now be removed. Multiple calls to InitializeComponent are idempotent. + +If a Xaml object needs to access a Xaml property during initialization, it should override InitializeComponent: + +```cpp +void MainPage::InitializeComponent() +{ + // Call base InitializeComponent() to register with the Xaml runtime + MainPageT::InitializeComponent(); + // Can now access Xaml properties + MyButton().Content(box_value(L"Click")); +} +``` + +A non-Xaml object can also participate in two-phase construction by defining an InitializeComponent method. + +```cpp +void MyComponent::InitializeComponent() +{ + // Execute initialization logic that may throw +} +``` + ## Troubleshooting The msbuild verbosity level maps to msbuild message importance as follows: diff --git a/strings/base_implements.h b/strings/base_implements.h index e2c6f3a12..212f2ecf7 100644 --- a/strings/base_implements.h +++ b/strings/base_implements.h @@ -1218,6 +1218,29 @@ namespace winrt::impl }; #endif + template + class has_initializer + { + template ().InitializeComponent())> static constexpr bool get_value(int) { return true; } + template static constexpr bool get_value(...) { return false; } + + public: + static constexpr bool value = get_value(0); + }; + + template + T* create_and_initialize(Args&&... args) + { + com_ptr instance{ new heap_implements(std::forward(args)...), take_ownership_from_abi }; + + if constexpr (has_initializer::value) + { + instance->InitializeComponent(); + } + + return instance.detach(); + } + inline com_ptr get_static_lifetime_map() { auto const lifetime_factory = get_activation_factory(L"Windows.ApplicationModel.Core.CoreApplication"); @@ -1233,7 +1256,7 @@ namespace winrt::impl if constexpr (!has_static_lifetime_v) { - return { to_abi(new heap_implements), take_ownership_from_abi }; + return { to_abi(create_and_initialize()), take_ownership_from_abi }; } else { @@ -1247,7 +1270,7 @@ namespace winrt::impl return { result, take_ownership_from_abi }; } - result_type object{ to_abi(new heap_implements), take_ownership_from_abi }; + result_type object{ to_abi(create_and_initialize()), take_ownership_from_abi }; static slim_mutex lock; slim_lock_guard const guard{ lock }; @@ -1293,17 +1316,17 @@ WINRT_EXPORT namespace winrt } else if constexpr (impl::has_composable::value) { - impl::com_ref result{ to_abi(new impl::heap_implements(std::forward(args)...)), take_ownership_from_abi }; + impl::com_ref result{ to_abi(impl::create_and_initialize(std::forward(args)...)), take_ownership_from_abi }; return result.template as(); } else if constexpr (impl::has_class_type::value) { static_assert(std::is_same_v>); - return typename D::class_type{ to_abi(new impl::heap_implements(std::forward(args)...)), take_ownership_from_abi }; + return typename D::class_type{ to_abi(impl::create_and_initialize(std::forward(args)...)), take_ownership_from_abi }; } else { - return impl::com_ref{ to_abi(new impl::heap_implements(std::forward(args)...)), take_ownership_from_abi }; + return impl::com_ref{ to_abi(impl::create_and_initialize(std::forward(args)...)), take_ownership_from_abi }; } } @@ -1325,7 +1348,7 @@ WINRT_EXPORT namespace winrt } else { - return { new impl::heap_implements(std::forward(args)...), take_ownership_from_abi }; + return { impl::create_and_initialize(std::forward(args)...), take_ownership_from_abi }; } } diff --git a/test/test/initialize.cpp b/test/test/initialize.cpp new file mode 100644 index 000000000..dbd95c3fe --- /dev/null +++ b/test/test/initialize.cpp @@ -0,0 +1,119 @@ +#include "pch.h" + +using namespace winrt; +using namespace Windows::Foundation; + +namespace +{ + class some_exception : public std::exception + { + public: + some_exception() noexcept + : exception("some_exception", 1) + { + } + }; + + template + struct InitializeT : implements + { + bool& m_initialize_called; + + InitializeT(bool& initialize_called) : m_initialize_called(initialize_called) + { + } + + ~InitializeT() + { + } + + void InitializeComponent() + { + m_initialize_called = true; + throw some_exception(); + } + + hstring ToString() + { + return {}; + } + }; + + struct Initialize : InitializeT + { + Initialize(bool& initialize_called) : InitializeT(initialize_called) + { + } + }; + + struct ThrowingDerived : InitializeT + { + ThrowingDerived(bool& initialize_called) : InitializeT(initialize_called) + { + throw some_exception(); + } + }; + + struct OverriddenInitialize : InitializeT + { + OverriddenInitialize(bool& initialize_called) : InitializeT(initialize_called) + { + } + + void InitializeComponent() + { + m_initialize_called = true; + } + }; +} + +TEST_CASE("initialize") +{ + // Ensure that failure to initialize is failure to instantiate, with no side effects + { + bool initialize_called{}; + bool exception_caught{}; + try + { + make(initialize_called); + } + catch (some_exception const&) + { + exception_caught = true; + } + REQUIRE(initialize_called); + REQUIRE(exception_caught); + } + + // Ensure that base is never initialized if exception thrown from derived/base constructor + { + bool initialize_called{}; + bool exception_caught{}; + try + { + make(initialize_called); + } + catch (some_exception const&) + { + exception_caught = true; + } + REQUIRE(!initialize_called); + REQUIRE(exception_caught); + } + + // Support for overriding initialization for post-processing (e.g., accessing Xaml properties) + { + bool initialize_called{}; + bool exception_caught{}; + try + { + make(initialize_called); + } + catch (some_exception const&) + { + exception_caught = true; + } + REQUIRE(initialize_called); + REQUIRE(!exception_caught); + } +} diff --git a/test/test/test.vcxproj b/test/test/test.vcxproj index dc6c96cea..8a570d258 100644 --- a/test/test/test.vcxproj +++ b/test/test/test.vcxproj @@ -369,6 +369,7 @@ + NotUsing NotUsing diff --git a/vsix/ItemTemplates/BlankPage/BlankPage.cpp b/vsix/ItemTemplates/BlankPage/BlankPage.cpp index 9af284cec..7410a619b 100644 --- a/vsix/ItemTemplates/BlankPage/BlankPage.cpp +++ b/vsix/ItemTemplates/BlankPage/BlankPage.cpp @@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml; namespace winrt::$rootnamespace$::implementation { - $safeitemname$::$safeitemname$() - { - InitializeComponent(); - } - int32_t $safeitemname$::MyProperty() { throw hresult_not_implemented(); diff --git a/vsix/ItemTemplates/BlankPage/BlankPage.h b/vsix/ItemTemplates/BlankPage/BlankPage.h index f1dc903e8..58c0f3648 100644 --- a/vsix/ItemTemplates/BlankPage/BlankPage.h +++ b/vsix/ItemTemplates/BlankPage/BlankPage.h @@ -6,7 +6,11 @@ namespace winrt::$rootnamespace$::implementation { struct $safeitemname$ : $safeitemname$T<$safeitemname$> { - $safeitemname$(); + $safeitemname$() + { + // Xaml objects should not call InitializeComponent during construction. + // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent + } int32_t MyProperty(); void MyProperty(int32_t value); diff --git a/vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp b/vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp index 9af284cec..7410a619b 100644 --- a/vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp +++ b/vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp @@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml; namespace winrt::$rootnamespace$::implementation { - $safeitemname$::$safeitemname$() - { - InitializeComponent(); - } - int32_t $safeitemname$::MyProperty() { throw hresult_not_implemented(); diff --git a/vsix/ItemTemplates/BlankUserControl/BlankUserControl.h b/vsix/ItemTemplates/BlankUserControl/BlankUserControl.h index 7b994b1df..0ab08377a 100644 --- a/vsix/ItemTemplates/BlankUserControl/BlankUserControl.h +++ b/vsix/ItemTemplates/BlankUserControl/BlankUserControl.h @@ -10,7 +10,11 @@ namespace winrt::$rootnamespace$::implementation { struct $safeitemname$ : $safeitemname$T<$safeitemname$> { - $safeitemname$(); + $safeitemname$() + { + // Xaml objects should not call InitializeComponent during construction. + // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent + } int32_t MyProperty(); void MyProperty(int32_t value); diff --git a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp index 8806e25cb..2f3132d65 100644 --- a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp +++ b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp @@ -14,12 +14,11 @@ using namespace $safeprojectname$; using namespace $safeprojectname$::implementation; /// -/// Initializes the singleton application object. This is the first line of authored code +/// Creates the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// App::App() { - InitializeComponent(); Suspending({ this, &App::OnSuspending }); #if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION diff --git a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.h b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.h index 8208308c8..b7fe65ef7 100644 --- a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.h +++ b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.h @@ -6,7 +6,6 @@ namespace winrt::$safeprojectname$::implementation struct App : AppT { App(); - void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&); void OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&); void OnNavigationFailed(IInspectable const&, Windows::UI::Xaml::Navigation::NavigationFailedEventArgs const&); diff --git a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.cpp b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.cpp index 5caaa46e6..af94324c3 100644 --- a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.cpp +++ b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.cpp @@ -7,11 +7,6 @@ using namespace Windows::UI::Xaml; namespace winrt::$safeprojectname$::implementation { - MainPage::MainPage() - { - InitializeComponent(); - } - int32_t MainPage::MyProperty() { throw hresult_not_implemented(); diff --git a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.h b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.h index 96c433917..92e0a689d 100644 --- a/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.h +++ b/vsix/ProjectTemplates/VC/Windows Universal/BlankApp/MainPage.h @@ -6,7 +6,11 @@ namespace winrt::$safeprojectname$::implementation { struct MainPage : MainPageT { - MainPage(); + MainPage() + { + // Xaml objects should not call InitializeComponent during construction. + // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent + } int32_t MyProperty(); void MyProperty(int32_t value);