Skip to content

Commit 0400867

Browse files
authored
two-phase initialization support to prevent double-destruction on handing 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
1 parent b69f40d commit 0400867

File tree

13 files changed

+202
-27
lines changed

13 files changed

+202
-27
lines changed

cppwinrt/component_writers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,9 @@ catch (...) { return winrt::to_hresult(); }
860860
{
861861
auto format = R"(
862862
#if defined(WINRT_FORCE_INCLUDE_%_XAML_G_H) || __has_include("%.xaml.g.h")
863+
863864
#include "%.xaml.g.h"
865+
864866
#else
865867
866868
namespace winrt::@::implementation

nuget/readme.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,41 @@ To customize common C++/WinRT project properties:
7777
* expand the Common Properties item
7878
* select the C++/WinRT property page
7979

80+
## InitializeComponent
81+
82+
In older versions of C++/WinRT, Xaml objects called InitializeComponent from constructors. This can lead to memory corruption if InitializeComponent throws an exception.
83+
84+
```cpp
85+
void MainPage::MainPage()
86+
{
87+
// This pattern should no longer be used
88+
InitializeComponent();
89+
}
90+
```
91+
92+
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.
93+
94+
If a Xaml object needs to access a Xaml property during initialization, it should override InitializeComponent:
95+
96+
```cpp
97+
void MainPage::InitializeComponent()
98+
{
99+
// Call base InitializeComponent() to register with the Xaml runtime
100+
MainPageT::InitializeComponent();
101+
// Can now access Xaml properties
102+
MyButton().Content(box_value(L"Click"));
103+
}
104+
```
105+
106+
A non-Xaml object can also participate in two-phase construction by defining an InitializeComponent method.
107+
108+
```cpp
109+
void MyComponent::InitializeComponent()
110+
{
111+
// Execute initialization logic that may throw
112+
}
113+
```
114+
80115
## Troubleshooting
81116

82117
The msbuild verbosity level maps to msbuild message importance as follows:

strings/base_implements.h

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,29 @@ namespace winrt::impl
12181218
};
12191219
#endif
12201220

1221+
template<typename T>
1222+
class has_initializer
1223+
{
1224+
template <typename U, typename = decltype(std::declval<U>().InitializeComponent())> static constexpr bool get_value(int) { return true; }
1225+
template <typename> static constexpr bool get_value(...) { return false; }
1226+
1227+
public:
1228+
static constexpr bool value = get_value<T>(0);
1229+
};
1230+
1231+
template<typename T, typename... Args>
1232+
T* create_and_initialize(Args&&... args)
1233+
{
1234+
com_ptr<T> instance{ new heap_implements<T>(std::forward<Args>(args)...), take_ownership_from_abi };
1235+
1236+
if constexpr (has_initializer<T>::value)
1237+
{
1238+
instance->InitializeComponent();
1239+
}
1240+
1241+
return instance.detach();
1242+
}
1243+
12211244
inline com_ptr<IStaticLifetimeCollection> get_static_lifetime_map()
12221245
{
12231246
auto const lifetime_factory = get_activation_factory<impl::IStaticLifetime>(L"Windows.ApplicationModel.Core.CoreApplication");
@@ -1233,7 +1256,7 @@ namespace winrt::impl
12331256

12341257
if constexpr (!has_static_lifetime_v<D>)
12351258
{
1236-
return { to_abi<result_type>(new heap_implements<D>), take_ownership_from_abi };
1259+
return { to_abi<result_type>(create_and_initialize<D>()), take_ownership_from_abi };
12371260
}
12381261
else
12391262
{
@@ -1247,7 +1270,7 @@ namespace winrt::impl
12471270
return { result, take_ownership_from_abi };
12481271
}
12491272

1250-
result_type object{ to_abi<result_type>(new heap_implements<D>), take_ownership_from_abi };
1273+
result_type object{ to_abi<result_type>(create_and_initialize<D>()), take_ownership_from_abi };
12511274

12521275
static slim_mutex lock;
12531276
slim_lock_guard const guard{ lock };
@@ -1293,17 +1316,17 @@ WINRT_EXPORT namespace winrt
12931316
}
12941317
else if constexpr (impl::has_composable<D>::value)
12951318
{
1296-
impl::com_ref<I> result{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
1319+
impl::com_ref<I> result{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
12971320
return result.template as<typename D::composable>();
12981321
}
12991322
else if constexpr (impl::has_class_type<D>::value)
13001323
{
13011324
static_assert(std::is_same_v<I, default_interface<typename D::class_type>>);
1302-
return typename D::class_type{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
1325+
return typename D::class_type{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
13031326
}
13041327
else
13051328
{
1306-
return impl::com_ref<I>{ to_abi<I>(new impl::heap_implements<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
1329+
return impl::com_ref<I>{ to_abi<I>(impl::create_and_initialize<D>(std::forward<Args>(args)...)), take_ownership_from_abi };
13071330
}
13081331
}
13091332

@@ -1325,7 +1348,7 @@ WINRT_EXPORT namespace winrt
13251348
}
13261349
else
13271350
{
1328-
return { new impl::heap_implements<D>(std::forward<Args>(args)...), take_ownership_from_abi };
1351+
return { impl::create_and_initialize<D>(std::forward<Args>(args)...), take_ownership_from_abi };
13291352
}
13301353
}
13311354

test/test/initialize.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#include "pch.h"
2+
3+
using namespace winrt;
4+
using namespace Windows::Foundation;
5+
6+
namespace
7+
{
8+
class some_exception : public std::exception
9+
{
10+
public:
11+
some_exception() noexcept
12+
: exception("some_exception", 1)
13+
{
14+
}
15+
};
16+
17+
template<typename D>
18+
struct InitializeT : implements<D, IStringable>
19+
{
20+
bool& m_initialize_called;
21+
22+
InitializeT(bool& initialize_called) : m_initialize_called(initialize_called)
23+
{
24+
}
25+
26+
~InitializeT()
27+
{
28+
}
29+
30+
void InitializeComponent()
31+
{
32+
m_initialize_called = true;
33+
throw some_exception();
34+
}
35+
36+
hstring ToString()
37+
{
38+
return {};
39+
}
40+
};
41+
42+
struct Initialize : InitializeT<Initialize>
43+
{
44+
Initialize(bool& initialize_called) : InitializeT(initialize_called)
45+
{
46+
}
47+
};
48+
49+
struct ThrowingDerived : InitializeT<ThrowingDerived>
50+
{
51+
ThrowingDerived(bool& initialize_called) : InitializeT(initialize_called)
52+
{
53+
throw some_exception();
54+
}
55+
};
56+
57+
struct OverriddenInitialize : InitializeT<OverriddenInitialize>
58+
{
59+
OverriddenInitialize(bool& initialize_called) : InitializeT(initialize_called)
60+
{
61+
}
62+
63+
void InitializeComponent()
64+
{
65+
m_initialize_called = true;
66+
}
67+
};
68+
}
69+
70+
TEST_CASE("initialize")
71+
{
72+
// Ensure that failure to initialize is failure to instantiate, with no side effects
73+
{
74+
bool initialize_called{};
75+
bool exception_caught{};
76+
try
77+
{
78+
make<Initialize>(initialize_called);
79+
}
80+
catch (some_exception const&)
81+
{
82+
exception_caught = true;
83+
}
84+
REQUIRE(initialize_called);
85+
REQUIRE(exception_caught);
86+
}
87+
88+
// Ensure that base is never initialized if exception thrown from derived/base constructor
89+
{
90+
bool initialize_called{};
91+
bool exception_caught{};
92+
try
93+
{
94+
make<ThrowingDerived>(initialize_called);
95+
}
96+
catch (some_exception const&)
97+
{
98+
exception_caught = true;
99+
}
100+
REQUIRE(!initialize_called);
101+
REQUIRE(exception_caught);
102+
}
103+
104+
// Support for overriding initialization for post-processing (e.g., accessing Xaml properties)
105+
{
106+
bool initialize_called{};
107+
bool exception_caught{};
108+
try
109+
{
110+
make<OverriddenInitialize>(initialize_called);
111+
}
112+
catch (some_exception const&)
113+
{
114+
exception_caught = true;
115+
}
116+
REQUIRE(initialize_called);
117+
REQUIRE(!exception_caught);
118+
}
119+
}

test/test/test.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@
369369
</ClCompile>
370370
<ClCompile Include="hstring_empty.cpp" />
371371
<ClCompile Include="iid_ppv_args.cpp" />
372+
<ClCompile Include="initialize.cpp" />
372373
<ClCompile Include="inspectable_interop.cpp">
373374
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
374375
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>

vsix/ItemTemplates/BlankPage/BlankPage.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml;
99

1010
namespace winrt::$rootnamespace$::implementation
1111
{
12-
$safeitemname$::$safeitemname$()
13-
{
14-
InitializeComponent();
15-
}
16-
1712
int32_t $safeitemname$::MyProperty()
1813
{
1914
throw hresult_not_implemented();

vsix/ItemTemplates/BlankPage/BlankPage.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ namespace winrt::$rootnamespace$::implementation
66
{
77
struct $safeitemname$ : $safeitemname$T<$safeitemname$>
88
{
9-
$safeitemname$();
9+
$safeitemname$()
10+
{
11+
// Xaml objects should not call InitializeComponent during construction.
12+
// See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
13+
}
1014

1115
int32_t MyProperty();
1216
void MyProperty(int32_t value);

vsix/ItemTemplates/BlankUserControl/BlankUserControl.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ using namespace Windows::UI::Xaml;
99

1010
namespace winrt::$rootnamespace$::implementation
1111
{
12-
$safeitemname$::$safeitemname$()
13-
{
14-
InitializeComponent();
15-
}
16-
1712
int32_t $safeitemname$::MyProperty()
1813
{
1914
throw hresult_not_implemented();

vsix/ItemTemplates/BlankUserControl/BlankUserControl.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ namespace winrt::$rootnamespace$::implementation
1010
{
1111
struct $safeitemname$ : $safeitemname$T<$safeitemname$>
1212
{
13-
$safeitemname$();
13+
$safeitemname$()
14+
{
15+
// Xaml objects should not call InitializeComponent during construction.
16+
// See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
17+
}
1418

1519
int32_t MyProperty();
1620
void MyProperty(int32_t value);

vsix/ProjectTemplates/VC/Windows Universal/BlankApp/App.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ using namespace $safeprojectname$;
1414
using namespace $safeprojectname$::implementation;
1515

1616
/// <summary>
17-
/// Initializes the singleton application object. This is the first line of authored code
17+
/// Creates the singleton application object. This is the first line of authored code
1818
/// executed, and as such is the logical equivalent of main() or WinMain().
1919
/// </summary>
2020
App::App()
2121
{
22-
InitializeComponent();
2322
Suspending({ this, &App::OnSuspending });
2423

2524
#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION

0 commit comments

Comments
 (0)