From 27db08232cd7d6dbef7b7174f329eb3ee90c272d Mon Sep 17 00:00:00 2001 From: "Haonan Tang (from Dev Box)" Date: Tue, 7 Apr 2026 11:33:25 +0800 Subject: [PATCH 1/5] support hybrid deployment for unpackaged app --- .../WindowsAppRuntimeAutoInitializer.cs | 8 + .../MddBootstrap.cpp | 154 +++++++++++++----- 2 files changed, 123 insertions(+), 39 deletions(-) diff --git a/dev/Common/WindowsAppRuntimeAutoInitializer.cs b/dev/Common/WindowsAppRuntimeAutoInitializer.cs index 3505b78cf2..f2c2a3e8cd 100644 --- a/dev/Common/WindowsAppRuntimeAutoInitializer.cs +++ b/dev/Common/WindowsAppRuntimeAutoInitializer.cs @@ -14,6 +14,14 @@ class AutoInitialize [global::System.Runtime.CompilerServices.ModuleInitializer] internal static void InitializeWindowsAppSDK() { + // HybridDeploy: set env vars BEFORE Bootstrap so that: + // 1. Bootstrap can detect hybrid mode and skip AddPackageDependency + // 2. On Win10, catalog loading during DllMain can expand loadFrom env vars +#if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP + System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", "1"); + System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", System.AppContext.BaseDirectory); +#endif + // Call the AutoInitialize functions, as needed, starting with those initializing the WindowsAppRuntime #if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_BOOTSTRAP Microsoft.Windows.ApplicationModel.DynamicDependency.BootstrapCS.AutoInitialize.AccessWindowsAppSDK(); diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index 1bc1a48409..c0086846b7 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -13,6 +13,49 @@ #include "MddWin11.h" #include +#include + +static bool IsHybridDeploy() noexcept +{ + wchar_t value[2]{}; + return GetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", value, ARRAYSIZE(value)) > 0; +} + +static void SetFrameworkPathEnvironmentVariable(PCWSTR frameworkPath) +{ + SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH", frameworkPath); +} + +// For C++ apps (no C# auto-initializer), set BASE_DIRECTORY to exe dir if not already set +static void SetBaseDirectoryEnvironmentVariableIfNotSet() +{ + wchar_t existing[2]{}; + if (GetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", existing, ARRAYSIZE(existing)) > 0) + { + return; // Already set by C# auto-initializer + } + wchar_t exePath[MAX_PATH]{}; + if (GetModuleFileNameW(nullptr, exePath, ARRAYSIZE(exePath)) > 0) + { + PathCchRemoveFileSpec(exePath, ARRAYSIZE(exePath)); + PathCchAddBackslashEx(exePath, ARRAYSIZE(exePath), nullptr, nullptr); + SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", exePath); + } +} + +// Resolve framework package install path from package full name +static std::wstring GetFrameworkPackagePath(PCWSTR packageFullName) +{ + UINT32 pathLength{ 0 }; + const auto rc{ GetPackagePathByFullName(packageFullName, &pathLength, nullptr) }; + if (rc != ERROR_INSUFFICIENT_BUFFER) + { + THROW_WIN32(rc); + } + auto path{ std::make_unique(pathLength) }; + THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, path.get())); + return std::wstring(path.get()); +} HRESULT _MddBootstrapInitialize( UINT32 majorMinorVersion, @@ -417,43 +460,68 @@ void FirstTimeInitialization( const UINT32 minVersionToUseWin11Support{ 0x00010007 }; if ((majorMinorVersion >= minVersionToUseWin11Support) && MddCore::Win11::IsSupported()) { - // Add the framework package to the package graph const std::wstring frameworkPackageFamilyName{ GetFrameworkPackageFamilyName(majorMinorVersion, packageVersionTag.c_str()) }; const MddPackageDependencyProcessorArchitectures architectureFilter{}; const auto lifetimeKind{ MddPackageDependencyLifetimeKind::Process }; const MddCreatePackageDependencyOptions createOptions{}; wil::unique_process_heap_string packageDependencyId; THROW_IF_FAILED(MddCore::Win11::TryCreatePackageDependency(nullptr, frameworkPackageFamilyName.c_str(), minVersion, architectureFilter, lifetimeKind, nullptr, createOptions, &packageDependencyId)); - // - const MddAddPackageDependencyOptions addOptions{}; - MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext{}; - wil::unique_process_heap_string packageFullName; - THROW_IF_FAILED(MddCore::Win11::AddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, &packageFullName)); - - // Update the activity context - auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; - activityContext.SetInitializationPackageFullName(packageFullName.get()); - // Pass along test information (if necessary) - if (!g_test_frameworkPackageNamePrefix.empty()) + const bool hybridDeploy{ IsHybridDeploy() }; + if (hybridDeploy) { - FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_ddlmPackageNamePrefix.empty()); - FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_ddlmPackagePublisherId.empty()); - FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_mainPackageNamePrefix.empty()); - - ::WindowsAppRuntime::VersionInfo::TestInitialize(frameworkPackageFamilyName.c_str()); + // HybridDeploy: resolve framework path but do NOT add to package graph. + // This lets the SxS manifest loadFrom control which DLLs load from where. + wil::unique_process_heap_string packageFullName; + THROW_IF_FAILED(MddCore::Win11::GetResolvedPackageFullNameForPackageDependency(packageDependencyId.get(), &packageFullName)); + + const auto frameworkPath{ GetFrameworkPackagePath(packageFullName.get()) }; + SetFrameworkPathEnvironmentVariable(frameworkPath.c_str()); + AddDllDirectory(frameworkPath.c_str()); + + // Update the activity context + auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; + activityContext.SetInitializationPackageFullName(packageFullName.get()); + + // Track our initialized state (keep packageDependencyId alive for lifetime protection) + g_usingWin11Support = UsingWin11Support::Yes; + g_packageDependencyId = std::move(packageDependencyId); + g_initializationMajorMinorVersion = majorMinorVersion; + g_initializationVersionTag = std::move(packageVersionTag); + const auto frameworkPackageIdentity{ ::AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.get()) }; + g_initializationFrameworkPackageVersion.Version = frameworkPackageIdentity.Version().Version; } + else + { + // Normal path: add the framework package to the package graph + const MddAddPackageDependencyOptions addOptions{}; + MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext{}; + wil::unique_process_heap_string packageFullName; + THROW_IF_FAILED(MddCore::Win11::AddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, &packageFullName)); + + // Update the activity context + auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; + activityContext.SetInitializationPackageFullName(packageFullName.get()); + + // Pass along test information (if necessary) + if (!g_test_frameworkPackageNamePrefix.empty()) + { + FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_ddlmPackageNamePrefix.empty()); + FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_ddlmPackagePublisherId.empty()); + FAIL_FAST_HR_IF(E_UNEXPECTED, g_test_mainPackageNamePrefix.empty()); - // Track our initialized state - g_usingWin11Support = UsingWin11Support::Yes; - // - g_packageDependencyId = std::move(packageDependencyId); - g_packageDependencyContext = packageDependencyContext; - // - g_initializationMajorMinorVersion = majorMinorVersion; - g_initializationVersionTag = std::move(packageVersionTag); - const auto frameworkPackageIdentity{ ::AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.get()) }; - g_initializationFrameworkPackageVersion.Version = frameworkPackageIdentity.Version().Version; + ::WindowsAppRuntime::VersionInfo::TestInitialize(frameworkPackageFamilyName.c_str()); + } + + // Track our initialized state + g_usingWin11Support = UsingWin11Support::Yes; + g_packageDependencyId = std::move(packageDependencyId); + g_packageDependencyContext = packageDependencyContext; + g_initializationMajorMinorVersion = majorMinorVersion; + g_initializationVersionTag = std::move(packageVersionTag); + const auto frameworkPackageIdentity{ ::AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.get()) }; + g_initializationFrameworkPackageVersion.Version = frameworkPackageIdentity.Version().Version; + } } else { @@ -469,6 +537,15 @@ void FirstTimeInitialization( // Temporarily add the framework's package directory to PATH so LoadLibrary can find it and any colocated imports wil::unique_dll_directory_cookie dllDirectoryCookie{ AddFrameworkToPath(frameworkPackageInfo->path) }; + const bool hybridDeploy{ IsHybridDeploy() }; + if (hybridDeploy) + { + // HybridDeploy: set env vars BEFORE loading the DLL so that + // catalog.cpp can expand %FRAMEWORK_PATH% and %BASE_DIRECTORY% in loadFrom + SetFrameworkPathEnvironmentVariable(frameworkPackageInfo->path); + SetBaseDirectoryEnvironmentVariableIfNotSet(); + } + auto windowsAppRuntimeDllFilename{ std::wstring(frameworkPackageInfo->path) + L"\\Microsoft.WindowsAppRuntime.dll" }; wil::unique_hmodule windowsAppRuntimeDll(LoadLibraryEx(windowsAppRuntimeDllFilename.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)); if (!windowsAppRuntimeDll) @@ -477,20 +554,19 @@ void FirstTimeInitialization( THROW_WIN32_MSG(lastError, "Error in LoadLibrary: %d (0x%X) loading %ls", lastError, lastError, windowsAppRuntimeDllFilename.c_str()); } - // Add the framework package to the package graph - const MddPackageDependencyProcessorArchitectures architectureFilter{}; - const auto lifetimeKind{ MddPackageDependencyLifetimeKind::Process }; - const MddCreatePackageDependencyOptions createOptions{}; wil::unique_process_heap_string packageDependencyId; - THROW_IF_FAILED(MddTryCreatePackageDependency(nullptr, frameworkPackageInfo->packageFamilyName, minVersion, architectureFilter, lifetimeKind, nullptr, createOptions, &packageDependencyId)); - // - const MddAddPackageDependencyOptions addOptions{}; MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext{}; - THROW_IF_FAILED(MddAddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, nullptr)); - - // Remove our temporary path addition - RemoveFrameworkFromPath(frameworkPackageInfo->path); - dllDirectoryCookie.reset(); + if (!hybridDeploy) + { + // Normal path: add the framework package to the package graph + const MddPackageDependencyProcessorArchitectures architectureFilter{}; + const auto lifetimeKind{ MddPackageDependencyLifetimeKind::Process }; + const MddCreatePackageDependencyOptions createOptions{}; + THROW_IF_FAILED(MddTryCreatePackageDependency(nullptr, frameworkPackageInfo->packageFamilyName, minVersion, architectureFilter, lifetimeKind, nullptr, createOptions, &packageDependencyId)); + // + const MddAddPackageDependencyOptions addOptions{}; + THROW_IF_FAILED(MddAddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, nullptr)); + } // Pass along test information (if necessary) if (!g_test_ddlmPackageNamePrefix.empty()) From 75cc92902cde313a435fda8fec98be183f5d3987 Mon Sep 17 00:00:00 2001 From: "Haonan Tang (from Dev Box)" Date: Tue, 7 Apr 2026 11:37:33 +0800 Subject: [PATCH 2/5] bug fix --- .../MddBootstrap.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index c0086846b7..49b4abf2f0 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -13,7 +13,6 @@ #include "MddWin11.h" #include -#include static bool IsHybridDeploy() noexcept { @@ -37,9 +36,13 @@ static void SetBaseDirectoryEnvironmentVariableIfNotSet() wchar_t exePath[MAX_PATH]{}; if (GetModuleFileNameW(nullptr, exePath, ARRAYSIZE(exePath)) > 0) { - PathCchRemoveFileSpec(exePath, ARRAYSIZE(exePath)); - PathCchAddBackslashEx(exePath, ARRAYSIZE(exePath), nullptr, nullptr); - SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", exePath); + // Extract directory from exe path and ensure trailing backslash + auto exeDir{ std::filesystem::path(exePath).parent_path().wstring() }; + if (!exeDir.empty() && exeDir.back() != L'\\') + { + exeDir += L'\\'; + } + SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", exeDir.c_str()); } } @@ -566,7 +569,13 @@ void FirstTimeInitialization( // const MddAddPackageDependencyOptions addOptions{}; THROW_IF_FAILED(MddAddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, nullptr)); + + // Remove our temporary path addition (package graph handles DLL search now) + RemoveFrameworkFromPath(frameworkPackageInfo->path); + dllDirectoryCookie.reset(); } + // else: HybridDeploy keeps the framework path in DLL search order + // since there's no package graph to handle it // Pass along test information (if necessary) if (!g_test_ddlmPackageNamePrefix.empty()) From ab63f46f4e7e220440f28fe595517cdab7689801 Mon Sep 17 00:00:00 2001 From: "Haonan Tang (from Dev Box)" Date: Thu, 9 Apr 2026 12:21:54 +0800 Subject: [PATCH 3/5] fix bug --- dev/Common/WindowsAppRuntimeAutoInitializer.cs | 6 +++--- dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/dev/Common/WindowsAppRuntimeAutoInitializer.cs b/dev/Common/WindowsAppRuntimeAutoInitializer.cs index f2c2a3e8cd..f0886be7d8 100644 --- a/dev/Common/WindowsAppRuntimeAutoInitializer.cs +++ b/dev/Common/WindowsAppRuntimeAutoInitializer.cs @@ -15,11 +15,11 @@ class AutoInitialize internal static void InitializeWindowsAppSDK() { // HybridDeploy: set env vars BEFORE Bootstrap so that: - // 1. Bootstrap can detect hybrid mode and skip AddPackageDependency + // 1. Bootstrap can detect hybrid mode and keep the package graph empty // 2. On Win10, catalog loading during DllMain can expand loadFrom env vars #if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP - System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", "1"); - System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", System.AppContext.BaseDirectory); + global::System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", "1"); + global::System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", global::System.AppContext.BaseDirectory); #endif // Call the AutoInitialize functions, as needed, starting with those initializing the WindowsAppRuntime diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index 49b4abf2f0..e828c0e828 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -473,12 +473,19 @@ void FirstTimeInitialization( const bool hybridDeploy{ IsHybridDeploy() }; if (hybridDeploy) { - // HybridDeploy: resolve framework path but do NOT add to package graph. - // This lets the SxS manifest loadFrom control which DLLs load from where. + // HybridDeploy: temporarily add to package graph to resolve the framework path, + // then immediately remove so that the package graph stays empty. + // An empty package graph lets the SxS manifest loadFrom control DLL loading. + const MddAddPackageDependencyOptions addOptions{}; + MDD_PACKAGEDEPENDENCY_CONTEXT tempContext{}; wil::unique_process_heap_string packageFullName; - THROW_IF_FAILED(MddCore::Win11::GetResolvedPackageFullNameForPackageDependency(packageDependencyId.get(), &packageFullName)); + THROW_IF_FAILED(MddCore::Win11::AddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &tempContext, &packageFullName)); const auto frameworkPath{ GetFrameworkPackagePath(packageFullName.get()) }; + + // Remove from package graph immediately — keep it empty for manifest loadFrom + MddCore::Win11::RemovePackageDependency(tempContext); + SetFrameworkPathEnvironmentVariable(frameworkPath.c_str()); AddDllDirectory(frameworkPath.c_str()); From 9a02b9556aef8b2da2cf1c8224b5a02abfc9b5ce Mon Sep 17 00:00:00 2001 From: "Haonan Tang (from Dev Box)" Date: Sun, 19 Apr 2026 21:58:40 +0800 Subject: [PATCH 4/5] fix cpp --- .../WindowsAppRuntimeAutoInitializer.cpp | 21 +++++++++++++++++++ .../MddBootstrap.cpp | 1 + 2 files changed, 22 insertions(+) diff --git a/dev/Common/WindowsAppRuntimeAutoInitializer.cpp b/dev/Common/WindowsAppRuntimeAutoInitializer.cpp index 543e7348e4..81f2e4f4e1 100644 --- a/dev/Common/WindowsAppRuntimeAutoInitializer.cpp +++ b/dev/Common/WindowsAppRuntimeAutoInitializer.cpp @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. See LICENSE in the project root for license information. +#include + // Forward-declare the various AutoInitialize functions namespace Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap { @@ -38,6 +40,25 @@ namespace Microsoft::Windows::ApplicationModel::WindowsAppRuntime::Common static void Initialize() { + // HybridDeploy: set env vars BEFORE Bootstrap so that: + // 1. Bootstrap can detect hybrid mode and keep the package graph empty + // 2. On Win10, catalog loading during DllMain can expand loadFrom env vars +#if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP + ::SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", L"1"); + + WCHAR exePath[MAX_PATH]{}; + if (::GetModuleFileNameW(nullptr, exePath, ARRAYSIZE(exePath)) > 0) + { + // Strip filename, keep trailing backslash for concatenation. + WCHAR* lastBackslash{ wcsrchr(exePath, L'\\') }; + if (lastBackslash != nullptr) + { + *(lastBackslash + 1) = L'\0'; + ::SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", exePath); + } + } +#endif + // Call the AutoInitialize functions, as needed, starting with those initializing the WindowsAppRuntime #if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_BOOTSTRAP Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap::AutoInitialize::Initialize(); diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index e828c0e828..60b4e79574 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -488,6 +488,7 @@ void FirstTimeInitialization( SetFrameworkPathEnvironmentVariable(frameworkPath.c_str()); AddDllDirectory(frameworkPath.c_str()); + SetBaseDirectoryEnvironmentVariableIfNotSet(); // Update the activity context auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; From 696f0c284ef80876850f760fda7657d6b0c7c2f3 Mon Sep 17 00:00:00 2001 From: "Haonan Tang (from Dev Box)" Date: Tue, 21 Apr 2026 11:24:27 +0800 Subject: [PATCH 5/5] resolve comments --- .../MddBootstrap.cpp | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index 60b4e79574..0b6f001451 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -25,25 +25,27 @@ static void SetFrameworkPathEnvironmentVariable(PCWSTR frameworkPath) SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH", frameworkPath); } -// For C++ apps (no C# auto-initializer), set BASE_DIRECTORY to exe dir if not already set -static void SetBaseDirectoryEnvironmentVariableIfNotSet() +// Validate BASE_DIRECTORY is set non-empty. Hybrid deploy's SxS manifest uses +// %MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY% in loadFrom to locate pinned +// component DLLs. The env var must be set before MddBootstrapInitialize so the +// OS loader can expand it when DLLs are resolved. +// +// The WinAppSDK auto-initializer sets this automatically for C++/C# apps. Manual +// callers (Rust/Python/Node bindings, or apps with WindowsAppSdkBootstrapInitialize=false) +// must set it themselves. Fails explicitly to surface misuse early instead of +// silently continuing to cryptic DLL-load failures later. +static void VerifyBaseDirectoryEnvironmentVariableIsSet() { wchar_t existing[2]{}; - if (GetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", existing, ARRAYSIZE(existing)) > 0) - { - return; // Already set by C# auto-initializer - } - wchar_t exePath[MAX_PATH]{}; - if (GetModuleFileNameW(nullptr, exePath, ARRAYSIZE(exePath)) > 0) - { - // Extract directory from exe path and ensure trailing backslash - auto exeDir{ std::filesystem::path(exePath).parent_path().wstring() }; - if (!exeDir.empty() && exeDir.back() != L'\\') - { - exeDir += L'\\'; - } - SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", exeDir.c_str()); - } + const DWORD chars{ GetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", + existing, ARRAYSIZE(existing)) }; + // chars == 0 covers both "not set" (ERROR_ENVVAR_NOT_FOUND) and "set to empty string". + // Both are invalid for hybrid deploy. + THROW_HR_IF_MSG(E_INVALIDARG, chars == 0, + "Hybrid deploy requires MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY to be set " + "to a non-empty directory path (with trailing backslash) before " + "MddBootstrapInitialize. The WinAppSDK auto-initializer sets this for " + "C++/C# apps; manual callers must set it themselves."); } // Resolve framework package install path from package full name @@ -378,8 +380,11 @@ void VerifyInitializationIsCompatible( // g_lifetimeManager is optional. Don't check it // g_endTheLifetimeManagerEvent is optional. Don't check it // g_windowsAppRuntimeDll is only relevant if not delegating to OS APIs - FAIL_FAST_HR_IF(E_UNEXPECTED, g_packageDependencyId == nullptr); - FAIL_FAST_HR_IF(E_UNEXPECTED, g_packageDependencyContext == nullptr); + // In hybrid deploy the package dependency is created and immediately removed, + // so g_packageDependencyId / g_packageDependencyContext are intentionally null. + const bool hybridDeploy{ IsHybridDeploy() }; + FAIL_FAST_HR_IF(E_UNEXPECTED, !hybridDeploy && g_packageDependencyId == nullptr); + FAIL_FAST_HR_IF(E_UNEXPECTED, !hybridDeploy && g_packageDependencyContext == nullptr); FAIL_FAST_HR_IF(E_UNEXPECTED, (g_usingWin11Support == UsingWin11Support::No) && (g_windowsAppRuntimeDll == nullptr)); // Verify the parameter(s) @@ -473,6 +478,10 @@ void FirstTimeInitialization( const bool hybridDeploy{ IsHybridDeploy() }; if (hybridDeploy) { + // Validate caller contract: BASE_DIRECTORY must be set before Bootstrap runs + // (auto-initializer handles this; manual callers must set it themselves). + VerifyBaseDirectoryEnvironmentVariableIsSet(); + // HybridDeploy: temporarily add to package graph to resolve the framework path, // then immediately remove so that the package graph stays empty. // An empty package graph lets the SxS manifest loadFrom control DLL loading. @@ -488,15 +497,15 @@ void FirstTimeInitialization( SetFrameworkPathEnvironmentVariable(frameworkPath.c_str()); AddDllDirectory(frameworkPath.c_str()); - SetBaseDirectoryEnvironmentVariableIfNotSet(); // Update the activity context auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; activityContext.SetInitializationPackageFullName(packageFullName.get()); - // Track our initialized state (keep packageDependencyId alive for lifetime protection) + // Track our initialized state. In hybrid mode we intentionally do NOT store + // packageDependencyId or packageDependencyContext: the graph membership has + // already been removed and the OS-side descriptor is process-lifetime (auto-cleaning). g_usingWin11Support = UsingWin11Support::Yes; - g_packageDependencyId = std::move(packageDependencyId); g_initializationMajorMinorVersion = majorMinorVersion; g_initializationVersionTag = std::move(packageVersionTag); const auto frameworkPackageIdentity{ ::AppModel::Identity::PackageIdentity::FromPackageFullName(packageFullName.get()) }; @@ -551,10 +560,13 @@ void FirstTimeInitialization( const bool hybridDeploy{ IsHybridDeploy() }; if (hybridDeploy) { - // HybridDeploy: set env vars BEFORE loading the DLL so that - // catalog.cpp can expand %FRAMEWORK_PATH% and %BASE_DIRECTORY% in loadFrom + // Validate caller contract: BASE_DIRECTORY must be set before Bootstrap runs + // (auto-initializer handles this; manual callers must set it themselves). + VerifyBaseDirectoryEnvironmentVariableIsSet(); + + // HybridDeploy: set FRAMEWORK_PATH before loading the DLL so that + // catalog.cpp can expand %FRAMEWORK_PATH% and %BASE_DIRECTORY% in loadFrom. SetFrameworkPathEnvironmentVariable(frameworkPackageInfo->path); - SetBaseDirectoryEnvironmentVariableIfNotSet(); } auto windowsAppRuntimeDllFilename{ std::wstring(frameworkPackageInfo->path) + L"\\Microsoft.WindowsAppRuntime.dll" }; @@ -582,8 +594,15 @@ void FirstTimeInitialization( RemoveFrameworkFromPath(frameworkPackageInfo->path); dllDirectoryCookie.reset(); } - // else: HybridDeploy keeps the framework path in DLL search order - // since there's no package graph to handle it + else + { + // HybridDeploy: no package graph to handle DLL search, so keep both the + // PATH entry (not removed above) and the DllDirectory for process lifetime. + // Release the RAII cookie so it isn't revoked when this function returns. + // SxS manifest handles most DLL resolution; DllDirectory covers callers that + // use LOAD_LIBRARY_SEARCH_USER_DIRS (which bypasses PATH). + dllDirectoryCookie.release(); + } // Pass along test information (if necessary) if (!g_test_ddlmPackageNamePrefix.empty())