From 0c870d68c69a32819712f0303db6d1be9fab8db2 Mon Sep 17 00:00:00 2001 From: leileizhang Date: Tue, 24 Jun 2025 13:41:53 +0800 Subject: [PATCH] [CmdPal] Fix slow fuzzy search in apps extension by properly handling null-terminated strings from SHLoadIndirectString (#40198) ## Summary of the Pull Request This change addresses a significant performance regression caused by improper handling of null-terminated strings returned from the SHLoadIndirectString API. Previously, the output buffer was converted to string using Span.ToString() without trimming at the null terminator (\0). As a result, the entire buffer (1024 characters) was converted, including trailing garbage data after the valid string. This caused the fuzzy matching logic to process unnecessarily long strings, leading to excessive CPU usage and input lag (~2 seconds delay per keystroke). The fix properly locates the first null terminator in the buffer and slices the span before converting to string, eliminating trailing garbage characters. This reduces the workload in the scoring function and resolves the input lag in the apps extension search. ## PR Checklist - [x] **Closes:** #40197 - [ ] **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 - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **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 ## Validation Steps Performed --- .../Programs/UWPApplication.cs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs index be973f3431..5ea2e42657 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/UWPApplication.cs @@ -173,6 +173,25 @@ public class UWPApplication : IProgram return false; } + private static string TryLoadIndirectString(string source, Span buffer, string errorContext) + { + try + { + PInvoke.SHLoadIndirectString(source, buffer).ThrowOnFailure(); + + var len = buffer.IndexOf('\0'); + var loaded = len >= 0 + ? buffer[..len].ToString() + : buffer.ToString(); + return string.IsNullOrEmpty(loaded) ? string.Empty : loaded; + } + catch (Exception ex) + { + Logger.LogError($"Unable to load resource {source} : {errorContext} : {ex.Message}"); + return string.Empty; + } + } + internal unsafe string ResourceFromPri(string packageFullName, string resourceReference) { const string prefix = "ms-resource:"; @@ -221,28 +240,15 @@ public class UWPApplication : IProgram Span outBuffer = stackalloc char[1024]; var source = $"@{{{packageFullName}? {parsed}}}"; - try - { - PInvoke.SHLoadIndirectString(source, outBuffer).ThrowOnFailure(); + var loaded = TryLoadIndirectString(source, outBuffer, resourceReference); - var loaded = outBuffer.ToString(); - return string.IsNullOrEmpty(loaded) ? string.Empty : loaded; - } - catch (Exception) + if (!string.IsNullOrEmpty(loaded)) { - try - { - var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}"; - PInvoke.SHLoadIndirectString(sourceFallback, outBuffer).ThrowOnFailure(); - var loaded = outBuffer.ToString(); - return string.IsNullOrEmpty(loaded) ? string.Empty : loaded; - } - catch (Exception) - { - // ProgramLogger.Exception($"Unable to load resource {resourceReference} from {packageFullName}", new InvalidOperationException(), GetType(), packageFullName); - return string.Empty; - } + return loaded; } + + var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}"; + return TryLoadIndirectString(sourceFallback, outBuffer, $"{resourceReference} (fallback)"); } else {