From 31ff286279f6d5f58054d0158db2ef216c2e0a11 Mon Sep 17 00:00:00 2001 From: YexuanXiao Date: Mon, 28 Sep 2026 10:00:25 +0800 Subject: [PATCH 1/3] Allow InitializeComponent to have parameters --- nuget/readme.md | 2 + strings/base_implements.h | 18 ++++++- test/test/initialize.cpp | 98 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/nuget/readme.md b/nuget/readme.md index 16eb5d2bb..417cca5c3 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -115,6 +115,8 @@ void MyComponent::InitializeComponent() } ``` +InitializeComponent can also have parameters, and its parameters must be equal to the parameters of the corresponding constructor. When a constructor is called, the InitializeComponent that has the same parameters as it, or that has zero formal parameters, will also be called, preferring the one with the same parameters as the constructor. + ***[Windows|Microsoft]::UI::Xaml::Markup::ComponentConnectorT*** A consequence of calling InitializeComponent outside construction is that Xaml runtime callbacks to IComponentConnector::Connect and IComponentConnector2::GetBindingConnector are now dispatched to the most derived implementations. Previously, these calls were dispatched directly to the class under construction, as the vtable had yet to be initialized. For objects with markup that derive from composable base classes with markup, this is a breaking change. Derived classes must now implement IComponentConnector::Connect and IComponentConnector2::GetBindingConnector by explicitly calling into the base class. The ComponentConnectorT template provides a correct implementation for these interfaces: diff --git a/strings/base_implements.h b/strings/base_implements.h index 0eb8db0bd..430276565 100644 --- a/strings/base_implements.h +++ b/strings/base_implements.h @@ -1320,12 +1320,26 @@ WINRT_EXPORT namespace winrt::impl static constexpr bool value = get_value(0); }; + template + class has_initializer_with_args + { + template ().InitializeComponent(std::declval()...))> 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) + + if constexpr ((sizeof...(Args) != 0) && has_initializer_with_args::value) + { + instance->InitializeComponent(std::forward(args)...); + } + else if constexpr (has_initializer::value) { instance->InitializeComponent(); } diff --git a/test/test/initialize.cpp b/test/test/initialize.cpp index e5532fe2d..a449e6125 100644 --- a/test/test/initialize.cpp +++ b/test/test/initialize.cpp @@ -65,6 +65,56 @@ namespace m_initialize_called = true; } }; + + template + struct InitializeTWithArgs : implements + { + InitializeTWithArgs([[maybe_unused]] bool& initialize_called) + { + } + + ~InitializeTWithArgs() + { + } + + void InitializeComponent(bool& initialize_called) + { + initialize_called = true; + throw some_exception(); + } + + hstring ToString() + { + return {}; + } + }; + + struct InitializeWithArgs : InitializeTWithArgs + { + InitializeWithArgs(bool& initialize_called) : InitializeTWithArgs(initialize_called) + { + } + }; + + struct ThrowingDerivedWithArgs : InitializeTWithArgs + { + ThrowingDerivedWithArgs(bool& initialize_called) : InitializeTWithArgs(initialize_called) + { + throw some_exception(); + } + }; + + struct OverriddenInitializeWithArgs : InitializeTWithArgs + { + OverriddenInitializeWithArgs(bool& initialize_called) : InitializeTWithArgs(initialize_called) + { + } + + void InitializeComponent(bool& initialize_called) + { + initialize_called = true; + } + }; } TEST_CASE("initialize") @@ -116,4 +166,52 @@ TEST_CASE("initialize") REQUIRE(initialize_called); REQUIRE(!exception_caught); } + + // 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); + } } From b85ef190febf2330e1e56e5d9f7f2ab7c77ba2ea Mon Sep 17 00:00:00 2001 From: Yexuan Xiao Date: Mon, 28 Sep 2026 11:49:52 +0800 Subject: [PATCH 2/3] Update readme.md --- nuget/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/readme.md b/nuget/readme.md index 417cca5c3..4885e5bdb 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -115,7 +115,7 @@ void MyComponent::InitializeComponent() } ``` -InitializeComponent can also have parameters, and its parameters must be equal to the parameters of the corresponding constructor. When a constructor is called, the InitializeComponent that has the same parameters as it, or that has zero formal parameters, will also be called, preferring the one with the same parameters as the constructor. +InitializeComponent can also have parameters, and its parameters must be equal to the parameters of the corresponding constructor. When a constructor is called, the InitializeComponent that has the same parameters as it, or that has zero parameters, will also be called, preferring the one with the same parameters as the constructor. ***[Windows|Microsoft]::UI::Xaml::Markup::ComponentConnectorT*** From 624ef1a8adb07d4e3b161d6a59fff339741fd5b7 Mon Sep 17 00:00:00 2001 From: Yexuan Xiao Date: Wed, 30 Sep 2026 13:31:19 +0800 Subject: [PATCH 3/3] Update readme.md with InitializeComponent details Clarified behavior of InitializeComponent with parameters and its impact on XAML runtime callbacks. --- nuget/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/readme.md b/nuget/readme.md index 4885e5bdb..ba9e1a598 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -115,7 +115,7 @@ void MyComponent::InitializeComponent() } ``` -InitializeComponent can also have parameters, and its parameters must be equal to the parameters of the corresponding constructor. When a constructor is called, the InitializeComponent that has the same parameters as it, or that has zero parameters, will also be called, preferring the one with the same parameters as the constructor. +InitializeComponent can also have parameters, the arguments passed to the constructor will also be attempted for use in calling InitializeComponent. If no such overload exists, it will fall back to calling InitializeComponent without arguments. ***[Windows|Microsoft]::UI::Xaml::Markup::ComponentConnectorT***