Skip to content

Commit

Permalink
two-phase initialization support to prevent double-destruction on han…
Browse files Browse the repository at this point in the history
…ding out this pointer in ctor (#1130)

* two-phase initialization support to prevent double-destruction on handing out this pointer in ctor

* PR feedback - primarily to hide/downplay the need to support Xaml with two-phase init

* should Release on exception

* remove unnecessary test case

* PR feedback

* use smart pointer instead of raw delete
  • Loading branch information
Scottj1s authored Mar 31, 2022
1 parent b69f40d commit 0400867
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 27 deletions.
2 changes: 2 additions & 0 deletions cppwinrt/component_writers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions nuget/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
35 changes: 29 additions & 6 deletions strings/base_implements.h
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,29 @@ namespace winrt::impl
};
#endif

template<typename T>
class has_initializer
{
template <typename U, typename = decltype(std::declval<U>().InitializeComponent())> static constexpr bool get_value(int) { return true; }
template <typename> static constexpr bool get_value(...) { return false; }

public:
static constexpr bool value = get_value<T>(0);
};

template<typename T, typename... Args>
T* create_and_initialize(Args&&... args)
{
com_ptr<T> instance{ new heap_implements<T>(std::forward<Args>(args)...), take_ownership_from_abi };

if constexpr (has_initializer<T>::value)
{
instance->InitializeComponent();
}

return instance.detach();
}

inline com_ptr<IStaticLifetimeCollection> get_static_lifetime_map()
{
auto const lifetime_factory = get_activation_factory<impl::IStaticLifetime>(L"Windows.ApplicationModel.Core.CoreApplication");
Expand All @@ -1233,7 +1256,7 @@ namespace winrt::impl

if constexpr (!has_static_lifetime_v<D>)
{
return { to_abi<result_type>(new heap_implements<D>), take_ownership_from_abi };
return { to_abi<result_type>(create_and_initialize<D>()), take_ownership_from_abi };
}
else
{
Expand All @@ -1247,7 +1270,7 @@ namespace winrt::impl
return { result, take_ownership_from_abi };
}

result_type object{ to_abi<result_type>(new heap_implements<D>), take_ownership_from_abi };
result_type object{ to_abi<result_type>(create_and_initialize<D>()), take_ownership_from_abi };

static slim_mutex lock;
slim_lock_guard const guard{ lock };
Expand Down Expand Up @@ -1293,17 +1316,17 @@ WINRT_EXPORT namespace winrt
}
else if constexpr (impl::has_composable<D>::value)
{
impl::com_ref<I> result{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
impl::com_ref<I> result{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
return result.template as<typename D::composable>();
}
else if constexpr (impl::has_class_type<D>::value)
{
static_assert(std::is_same_v<I, default_interface<typename D::class_type>>);
return typename D::class_type{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
return typename D::class_type{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
}
else
{
return impl::com_ref<I>{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
return impl::com_ref<I>{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
}
}

Expand All @@ -1325,7 +1348,7 @@ WINRT_EXPORT namespace winrt
}
else
{
return { new impl::heap_implements<D>(std::forward<Args>(args)...), take_ownership_from_abi };
return { impl::create_and_initialize<D>(std::forward<Args>(args)...), take_ownership_from_abi };
}
}

Expand Down
119 changes: 119 additions & 0 deletions test/test/initialize.cpp
Original file line number Diff line number Diff line change
@@ -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<typename D>
struct InitializeT : implements<D, IStringable>
{
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>
{
Initialize(bool& initialize_called) : InitializeT(initialize_called)
{
}
};

struct ThrowingDerived : InitializeT<ThrowingDerived>
{
ThrowingDerived(bool& initialize_called) : InitializeT(initialize_called)
{
throw some_exception();
}
};

struct OverriddenInitialize : InitializeT<OverriddenInitialize>
{
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>(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<ThrowingDerived>(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<OverriddenInitialize>(initialize_called);
}
catch (some_exception const&)
{
exception_caught = true;
}
REQUIRE(initialize_called);
REQUIRE(!exception_caught);
}
}
1 change: 1 addition & 0 deletions test/test/test.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@
</ClCompile>
<ClCompile Include="hstring_empty.cpp" />
<ClCompile Include="iid_ppv_args.cpp" />
<ClCompile Include="initialize.cpp" />
<ClCompile Include="inspectable_interop.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
Expand Down
5 changes: 0 additions & 5 deletions vsix/ItemTemplates/BlankPage/BlankPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml;

namespace winrt::$rootnamespace$::implementation
{
$safeitemname$::$safeitemname$()
{
InitializeComponent();
}

int32_t $safeitemname$::MyProperty()
{
throw hresult_not_implemented();
Expand Down
6 changes: 5 additions & 1 deletion vsix/ItemTemplates/BlankPage/BlankPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 0 additions & 5 deletions vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml;

namespace winrt::$rootnamespace$::implementation
{
$safeitemname$::$safeitemname$()
{
InitializeComponent();
}

int32_t $safeitemname$::MyProperty()
{
throw hresult_not_implemented();
Expand Down
6 changes: 5 additions & 1 deletion vsix/ItemTemplates/BlankUserControl/BlankUserControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ using namespace $safeprojectname$;
using namespace $safeprojectname$::implementation;

/// <summary>
/// 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().
/// </summary>
App::App()
{
InitializeComponent();
Suspending({ this, &App::OnSuspending });

#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
Expand Down
1 change: 0 additions & 1 deletion vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace winrt::$safeprojectname$::implementation
struct App : AppT<App>
{
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&);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ using namespace Windows::UI::Xaml;

namespace winrt::$safeprojectname$::implementation
{
MainPage::MainPage()
{
InitializeComponent();
}

int32_t MainPage::MyProperty()
{
throw hresult_not_implemented();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ namespace winrt::$safeprojectname$::implementation
{
struct MainPage : MainPageT<MainPage>
{
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);
Expand Down

0 comments on commit 0400867

Please sign in to comment.