[Keyboard Manager] Build the WinUI 3 editor self-contained to fix launch crash (0xC0000409) (#49524)

## Summary of the Pull Request

`PowerToys.KeyboardManagerEditorUI.exe` fail-fasts with `0xC0000409`
(`STATUS_STACK_BUFFER_OVERRUN`) during `MainWindow` construction, so the
new Keyboard Manager editor never opens.
`KeyboardManagerEditorUI.csproj` was the **only WinUI 3 executable in
the repo missing
`<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>`**, so it
was built framework-package-dependent and mixed two Windows App SDK
provenances in one process.

## PR Checklist

- [x] Closes: #49399
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
<!-- no user-facing strings added -->
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places <!-- no new
binaries -->
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments

### Root cause

The reporter's WinDbg capture shows a first-chance `Core::ApiException`
carrying `0x800704DF` (`ERROR_ALREADY_INITIALIZED`):

```
MainWindow.SetTitleBar
-> Microsoft.UI.Xaml.Window.set_ExtendsContentIntoTitleBar
-> Microsoft.UI.Input!InputNonClientPointerSourceWinRTStatics::GetForWindowIdHelper
-> Microsoft.UI.Windowing.Core!RegisterWindowFeature
-> Core::NamedApiObject::Init
-> Core::ApiException
```

`ExtendsContentIntoTitleBar` is the **site**, not the cause — it is
simply the first user statement that crosses XAML -> Windowing -> Input.


`microsoft.windowsappsdk.foundation/*/buildTransitive/Microsoft.WindowsAppSDK.BootstrapCommon.targets`
turns the bootstrapper on precisely when this project's shape is hit:

```xml
<PropertyGroup Condition="'$(WindowsAppSdkBootstrapInitialize)'=='' and '$(WindowsAppSDKSelfContained)'!='true' and '$(WindowsPackageType)'=='None' and ('$(OutputType)'=='Exe' or '$(OutputType)'=='Winexe')">
    <WindowsAppSdkBootstrapInitialize>true</WindowsAppSdkBootstrapInitialize>
</PropertyGroup>
```

That compiles in `MddBootstrapAutoInitializer.cs`, which joins the
machine-wide `Microsoft.WindowsAppRuntime` MSIX framework package to the
process package graph before `Main`. Meanwhile the exe's own directory —
`WinUI3Apps` — is first in the Win32 DLL search order and already
contains a complete app-local Windows App SDK payload, deployed there by
the other 14 self-contained apps. One process, two Windows App SDK
provenances, and the one-time feature-type registration in
`Microsoft.UI.Input` collides.

The omission was easy to miss: the project imports
`src\Common.SelfContained.props`, whose name suggests it covers this —
but it only sets the **.NET** `<SelfContained>` property, which is
unrelated.

This also explains why the reporter could not shake it off: the
framework package is machine state, so uninstall/reinstall and wiping
`%LOCALAPPDATA%\Microsoft\PowerToys` change nothing. The classic C++
editor is unaffected because it uses WinUI 2 XAML Islands and ships no
Windows App SDK at all. `PowerToys.Settings.exe` ran healthily in the
same elevated session on the same day while doing strictly more
title-bar work (it sets `ExtendsContentIntoTitleBar` twice and drives
`InputNonClientPointerSource.GetForWindowId` on every `SizeChanged`) —
because it *is* self-contained.

### Two additional defects fixed

Both were found while investigating why the crash left no diagnostics at
all:

1. **`App.xaml.cs` initialized the logger via fire-and-forget
`Task.Run`** — the only one of ~30 `Logger.InitializeLogger` call sites
in the repo to do so. That races window creation, and `Logger` has no
buffering or replay (`Trace.WriteLine` straight through, listener
attached in `InitializeLogger`), so anything logged before the listener
is attached is lost permanently. This is why the user's bug report
bundle contains a `WinUI3Editor` log for the day it worked and **no log
file at all** for the day it crashed. Made synchronous, ordered to match
`FileLocksmithXAML/App.xaml.cs`, plus a log line before the window is
constructed.

2. **`MainWindow` never called
`WindowHelpers.ForceTopBorder1PixelInsetOnWindows10`**, unlike the other
PowerToys WinUI 3 module windows (AdvancedPaste, EnvironmentVariables,
FileLocksmith, Hosts, ImageResizer, Peek, RegistryPreview, Settings). It
is a no-op on Windows 11 and fixes the black top border from
microsoft/microsoft-ui-xaml#6901 on Windows 10 — the OS this issue was
reported against. Happy to drop this hunk if reviewers prefer a minimal
diff.

Deliberately **not** done: wrapping `new MainWindow()` in `try/catch`.
The failure is a WIL `RaiseFailFastException`, which managed code cannot
intercept; and swallowing managed exceptions there would leave a
windowless zombie process still holding the runner's `m_hEditorProcess`
handle, making the runner take its "editor already open" branch and
breaking every subsequent launch. That is why #49477 cannot work.

### Repo-wide audit

All 15 WinUI 3 executables (`UseWinUI=true` and `OutputType=WinExe`)
were checked. **KeyboardManagerEditorUI was the only one missing the
property**; the other 14 already set it. Also verified as correct and
unchanged: the 7 WinUI class libraries (property is app-level, N/A),
`runner.vcxproj` and `PowerRenameUI.vcxproj` (native exes, both already
`true`), and `PowerToys.MeasureToolCore.vcxproj` / `FindMyMouse.vcxproj`
(deliberately `false` — in-proc module DLLs whose host already
establishes the self-contained context).

There is no repo-level default or build guard for this property; it is
hand-copied into 17 project files, which is how the hole opened. A
`Directory.Build.targets` guard that errors when an unpackaged Windows
App SDK executable omits it would prevent recurrence, but it would catch
nothing today, so I left it out of this PR to keep the diff scoped.
Happy to open it separately.

## Validation Steps Performed

Built `KeyboardManagerEditorUI.csproj` (x64/Debug) and diffed the build
output before and after the change:

| | before | after | `PowerToys.Hosts.exe` (reference) |
|---|---|---|---|
| `obj\x64\Debug\Manifests\` (created only by `CreateWinRTRegistration`)
| absent | **present** | present |
| `activatableClass` registrations embedded in the exe | **0** |
**1912** | 1912 |
| assembly references `Microsoft.WindowsAppRuntime.Bootstrap.Net` /
`MddBootstrap` | **yes** | **no** | no |

The editor now resolves every `Microsoft.UI.*` activation app-locally
through registration-free WinRT instead of the machine framework
package, which removes the mixing hazard.

**Not yet validated on Windows 10.** I do not have a Windows 10 19045
machine, so the crash repro itself is unverified end-to-end. The
deployment-mode change is verified from build output as above;
confirmation from the issue reporter would be valuable. A useful
discriminator if anyone has the reporter's ProcDump dump: `lm v m
Microsoft.UI.*` — if `Microsoft.UI.Input.dll` is listed twice from two
different paths, the mechanism is confirmed directly.

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
moooyo
2026-07-28 16:00:36 +08:00
committed by GitHub
parent 837fe46ed3
commit 130a77907b
3 changed files with 14 additions and 6 deletions

View File

@@ -15,6 +15,11 @@
<ApplicationIcon>Assets\KeyboardManagerEditor\Keyboard.ico</ApplicationIcon>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<!-- Required alongside WindowsPackageType=None. Without it the Windows App SDK targets compile in the
MddBootstrap auto-initializer, and this app binds to the machine-wide Microsoft.WindowsAppRuntime
framework package instead of the app-local payload PowerToys ships in WinUI3Apps. Note that
Common.SelfContained.props above sets the unrelated .NET <SelfContained> property. See #49399. -->
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->

View File

@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Settings;
using ManagedCommon;
@@ -40,12 +39,12 @@ namespace KeyboardManagerEditorUI
/// </summary>
public App()
{
this.InitializeComponent();
// Initialize the logger synchronously, before anything else can log. Doing this on a
// background task races window creation, so a failure during startup leaves no trace
// at all - which is exactly what happened in #49399.
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
Task.Run(() =>
{
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
});
this.InitializeComponent();
UnhandledException += App_UnhandledException;
@@ -58,6 +57,8 @@ namespace KeyboardManagerEditorUI
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
Logger.LogInfo("keyboard-manager WinUI3 editor is creating its main window");
MainWindow = new MainWindow();
MainWindow.DispatcherQueue.TryEnqueue(() =>

View File

@@ -10,6 +10,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using KeyboardManagerEditorUI.Helpers;
using ManagedCommon;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
@@ -41,6 +42,7 @@ namespace KeyboardManagerEditorUI
this.SetIcon(@"Assets\KeyboardManagerEditor\Keyboard.ico");
this.SetTitleBar(titleBar);
Title = "Keyboard Manager";
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(this.GetWindowHandle());
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)