diff --git a/dev/Common/WindowsAppRuntimeAutoInitializer.cpp b/dev/Common/WindowsAppRuntimeAutoInitializer.cpp index 543e7348e4..710407f1b6 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,24 @@ namespace Microsoft::Windows::ApplicationModel::WindowsAppRuntime::Common static void Initialize() { + // HybridDeploy: URFW reads the app's SxS manifest and expands + // %MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY% inside . + // Set it before Bootstrap runs so the framework DLL's DllMain can load + // the catalog with correct pinned-DLL paths. +#if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP + 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/Common/WindowsAppRuntimeAutoInitializer.cs b/dev/Common/WindowsAppRuntimeAutoInitializer.cs index 3505b78cf2..62f547fd78 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: URFW reads the app's SxS manifest and expands + // %MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY% inside . + // Set it before Bootstrap runs so the framework DLL's DllMain can load + // the catalog with correct pinned-DLL paths. +#if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP + 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 #if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_BOOTSTRAP Microsoft.Windows.ApplicationModel.DynamicDependency.BootstrapCS.AutoInitialize.AccessWindowsAppSDK(); diff --git a/dev/UndockedRegFreeWinRT/urfw.cpp b/dev/UndockedRegFreeWinRT/urfw.cpp index e44847f464..bb6a12db18 100644 --- a/dev/UndockedRegFreeWinRT/urfw.cpp +++ b/dev/UndockedRegFreeWinRT/urfw.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include "urfw.h" #include "catalog.h" @@ -405,27 +403,17 @@ HRESULT ExtRoLoadCatalog() return S_OK; } -static bool UrfwUseOSImplementation() noexcept -{ - // Delegate to the OS' implementation on >= Windows 11 24H1 - return WindowsVersion::IsWindows11_24H1OrGreater(); -} - bool UrfwNeedsDetours() noexcept { - // Detour to our own implementation if Windows doesn't provide a sufficient implementation - return !UrfwUseOSImplementation(); + // pureUrfw: always detour. Hybrid deploy requires URFW to intercept + // RoGetActivationFactory before combase so that pinned-component classes + // resolve via the SxS manifest's loadFrom (pointing to the app's bin dir) + // rather than via the package graph (which points to the framework package). + return true; } HRESULT UrfwInitialize() noexcept { - // Delegate to the OS' implementation if we can - if (UrfwUseOSImplementation()) - { - return S_OK; - } - - // OS Reg-Free WinRT isn't available so let's do it ourselves... DetourAttach(&(PVOID&)TrueRoActivateInstance, RoActivateInstanceDetour); DetourAttach(&(PVOID&)TrueRoGetActivationFactory, RoGetActivationFactoryDetour); DetourAttach(&(PVOID&)TrueRoGetMetaDataFile, RoGetMetaDataFileDetour); diff --git a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp index 1bc1a48409..e2b8e8f6be 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp @@ -14,6 +14,83 @@ #include +// Publish the framework package's install directory via env var. +// WinUI's LoadLibraryAbs consults this when a sibling DLL is not next to +// Microsoft.UI.Xaml.dll (the hybrid case, where Microsoft.UI.Xaml.dll is +// pinned in the app's bin dir while siblings remain in the framework). +// In non-hybrid apps the env var is still set but WinUI's primary path +// (muxPath) succeeds first and this fallback never fires. +static void SetFrameworkPathEnvironmentVariable(PCWSTR frameworkPath) +{ + SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH", frameworkPath); +} + +// Ensure Microsoft.WindowsAppRuntime.dll is loaded into the process, preferring +// the pinned (NEW) copy in BASE_DIRECTORY (app bin) over the framework copy. +// This is the URFW-detour bootstrapping point: the detour is installed by this +// DLL's DllMain, so whichever physical file gets loaded first determines which +// version of UrfwInitialize runs. If the framework copy were loaded first +// (e.g. because the OS package graph raises framework-dir search priority +// above app-dir), an older UrfwInitialize might early-return on Win11 24H2+ +// and skip detour installation, causing pinned class activations to fall +// through to combase Step 4 and use the framework version instead. +// +// Loading by absolute path with LOAD_WITH_ALTERED_SEARCH_PATH bypasses the +// OS DLL search order entirely, making this race-free regardless of how the +// package graph is later configured. +// +// Loads APP_DIR pin if present; otherwise falls back to framework path. +// Framework fallback is what runs the DLL's DllMain → URFW always-on detour +// installs. Pass nullptr only if you'll re-invoke with framework path later +// (Win11 path does this around AddPackageDependency). +static wil::unique_hmodule EnsureFoundationDllLoaded(PCWSTR frameworkPath) noexcept +{ + // 1. Prefer the pinned copy in app dir. + WCHAR baseDir[MAX_PATH]{}; + const DWORD chars{ ::GetEnvironmentVariableW( + L"MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", baseDir, ARRAYSIZE(baseDir)) }; + if (chars > 0 && chars < ARRAYSIZE(baseDir)) + { + std::wstring pinned{ baseDir }; + pinned += L"Microsoft.WindowsAppRuntime.dll"; + if (::GetFileAttributesW(pinned.c_str()) != INVALID_FILE_ATTRIBUTES) + { + wil::unique_hmodule hmod{ ::LoadLibraryExW(pinned.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH) }; + if (hmod) + { + return hmod; + } + } + } + + // 2. Fall back to the framework copy. + if (frameworkPath != nullptr) + { + std::wstring framework{ frameworkPath }; + framework += L"\\Microsoft.WindowsAppRuntime.dll"; + return wil::unique_hmodule{ ::LoadLibraryExW(framework.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH) }; + } + + // 3. No fallback available; caller is responsible for ensuring the DLL + // gets loaded later (e.g. Win11 path relies on package graph + later PInvoke). + return wil::unique_hmodule{}; +} + +// Resolve a framework package's install path from its full name (Win11 path +// receives only a full name from MddCore::Win11::AddPackageDependency). +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, PCWSTR versionTag, @@ -417,6 +494,11 @@ void FirstTimeInitialization( const UINT32 minVersionToUseWin11Support{ 0x00010007 }; if ((majorMinorVersion >= minVersionToUseWin11Support) && MddCore::Win11::IsSupported()) { + // Try APP_DIR pin first (before AddPackageDependency raises framework + // search priority). Framework fallback below covers the null case. + // Capture into g_windowsAppRuntimeDll or unique_hmodule frees it. + g_windowsAppRuntimeDll = EnsureFoundationDllLoaded(nullptr); + // Add the framework package to the package graph const std::wstring frameworkPackageFamilyName{ GetFrameworkPackageFamilyName(majorMinorVersion, packageVersionTag.c_str()) }; const MddPackageDependencyProcessorArchitectures architectureFilter{}; @@ -430,6 +512,16 @@ void FirstTimeInitialization( wil::unique_process_heap_string packageFullName; THROW_IF_FAILED(MddCore::Win11::AddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, &packageFullName)); + // Publish framework path for WinUI's sibling-DLL fallback (see helper comment). + SetFrameworkPathEnvironmentVariable(GetFrameworkPackagePath(packageFullName.get()).c_str()); + + // Framework fallback when APP_DIR had no pin — runs DllMain → URFW + // detour. Without this, non-Foundation hybrid apps lose routing. + if (!g_windowsAppRuntimeDll) + { + g_windowsAppRuntimeDll = EnsureFoundationDllLoaded(GetFrameworkPackagePath(packageFullName.get()).c_str()); + } + // Update the activity context auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() }; activityContext.SetInitializationPackageFullName(packageFullName.get()); @@ -469,12 +561,14 @@ 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) }; - auto windowsAppRuntimeDllFilename{ std::wstring(frameworkPackageInfo->path) + L"\\Microsoft.WindowsAppRuntime.dll" }; - wil::unique_hmodule windowsAppRuntimeDll(LoadLibraryEx(windowsAppRuntimeDllFilename.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)); + // HybridDeploy: prefer the pinned (NEW) Microsoft.WindowsAppRuntime.dll + // from app dir if present; otherwise fall back to the framework copy. + // See helper comment for rationale. + wil::unique_hmodule windowsAppRuntimeDll{ EnsureFoundationDllLoaded(frameworkPackageInfo->path) }; if (!windowsAppRuntimeDll) { const auto lastError{ GetLastError() }; - THROW_WIN32_MSG(lastError, "Error in LoadLibrary: %d (0x%X) loading %ls", lastError, lastError, windowsAppRuntimeDllFilename.c_str()); + THROW_WIN32_MSG(lastError, "Error in LoadLibrary: %d (0x%X) loading Microsoft.WindowsAppRuntime.dll (framework=%ls)", lastError, lastError, frameworkPackageInfo->path); } // Add the framework package to the package graph @@ -488,6 +582,9 @@ void FirstTimeInitialization( MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext{}; THROW_IF_FAILED(MddAddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &packageDependencyContext, nullptr)); + // Publish framework path for WinUI's sibling-DLL fallback (see helper comment). + SetFrameworkPathEnvironmentVariable(frameworkPackageInfo->path); + // Remove our temporary path addition RemoveFrameworkFromPath(frameworkPackageInfo->path); dllDirectoryCookie.reset(); diff --git a/dev/WindowsAppRuntime_BootstrapDLL/dllmain.cpp b/dev/WindowsAppRuntime_BootstrapDLL/dllmain.cpp index fea531256b..df8c78feb4 100644 --- a/dev/WindowsAppRuntime_BootstrapDLL/dllmain.cpp +++ b/dev/WindowsAppRuntime_BootstrapDLL/dllmain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #include "pch.h"