Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions dev/Common/WindowsAppRuntimeAutoInitializer.cpp
Original file line number Diff line number Diff line change
@@ -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 <Windows.h>

// Forward-declare the various AutoInitialize functions
namespace Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap
{
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions dev/Common/WindowsAppRuntimeAutoInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 keep the package graph empty
// 2. On Win10, catalog loading during DllMain can expand loadFrom env vars
#if MICROSOFT_WINDOWSAPPSDK_AUTOINITIALIZE_HYBRIDDEPLOYSETUP
global::System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", "1");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env vars should be avoided if at all possible. MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH and MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY are required for loadFrom. But the boolean MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY could instead be an API that's explicitly set by the autoinitialization source. Or better yet, can we just infer it from the existing and differing values of MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH and MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env vars should be avoided if at all possible

+1000

We should be seriously examining dev\UndockedRegFreeWinRT\UndockedRegFreeWinRT-AutoInitializer.cs to see how we can remove

// Set base directory env var for PublishSingleFile support (referenced by SxS redirection)
Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", AppContext.BaseDirectory);

not lean in even more heavily on it.

Q: If we need altered Assembly.Load*() behavior why aren't we adding a Custom Load Context to .NET callers? We're already adding *autoiniitalizer*.cs to .NET projects through build magic. What's a little more code?

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();
Expand Down
194 changes: 153 additions & 41 deletions dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,54 @@

#include <filesystem>

static bool IsHybridDeploy() noexcept
{
wchar_t value[2]{};
return GetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY", value, ARRAYSIZE(value)) > 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SET MICROSOFT_WINDOWSAPPRUNTIME_HYBRID_DEPLOY=1
RunWhatever.exe

How is this not a massive DOS attack waiting to happen?

}

static void SetFrameworkPathEnvironmentVariable(PCWSTR frameworkPath)
{
SetEnvironmentVariableW(L"MICROSOFT_WINDOWSAPPRUNTIME_FRAMEWORK_PATH", frameworkPath);
}

// 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]{};
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
static std::wstring GetFrameworkPackagePath(PCWSTR packageFullName)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to just use GetInstallPath<std::wstring>(pkgfullname) from #6200

That's only 2 steps away from checkin

  • Address Letao's codereview feedback
  • Resolve the RS5 test failure

{
UINT32 pathLength{ 0 };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: UINT32 pathLength{};

const auto rc{ GetPackagePathByFullName(packageFullName, &pathLength, nullptr) };
if (rc != ERROR_INSUFFICIENT_BUFFER)
{
THROW_WIN32(rc);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: THROW_HR_IF(HRESULT_FROM_WIN32(rc), rc != ERROR_INSUFFICIENT_BUFFER);

auto path{ std::make_unique<WCHAR[]>(pathLength) };
THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, path.get()));
return std::wstring(path.get());
}

HRESULT _MddBootstrapInitialize(
UINT32 majorMinorVersion,
PCWSTR versionTag,
Expand Down Expand Up @@ -332,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)
Expand Down Expand Up @@ -417,43 +468,80 @@ 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());
// 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.
const MddAddPackageDependencyOptions addOptions{};
MDD_PACKAGEDEPENDENCY_CONTEXT tempContext{};
wil::unique_process_heap_string packageFullName;
THROW_IF_FAILED(MddCore::Win11::AddPackageDependency(packageDependencyId.get(), MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT, addOptions, &tempContext, &packageFullName));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MddCore::Win11::* are calls to the OS DynDep APIs

I don't understand the intent. It looks like it's playing hide'n'seek with OS DynDep and winappsdk OS-alternative polyfill hacks (SetFrameworkPathEnvironmentVariable, AddDllDirectory, etc).

This is all terribly confusing and disturbing, unless I'm missing something.


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());

// Update the activity context
auto& activityContext{ WindowsAppRuntime::MddBootstrap::Activity::Context::Get() };
activityContext.SetInitializationPackageFullName(packageFullName.get());

// 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_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
{
Expand All @@ -469,6 +557,18 @@ 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)
{
// 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);
}

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)
Expand All @@ -477,20 +577,32 @@ 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));

// Remove our temporary path addition (package graph handles DLL search now)
RemoveFrameworkFromPath(frameworkPackageInfo->path);
dllDirectoryCookie.reset();
}
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())
Expand Down