This feature makes Framework packages accessible to all kinds of apps, packaged and unpackaged. The package loader - which you carry with your app - lets you pin, bind, resolve, and consume framework package content including WinRT APIs and 'flat C' exports.
DynamicDependencies enable access to packaged content via APIs at runtime. This supplements the
appmodel's static dependency support (via <PackageDependency>
in appxmanifest.xml
) with a
dynamic runtime equivalent. It also allows non-packaged processes (which have no appxmanifest.xml
)
to use packaged content.
- 1. MSIX Dynamic Dependencies
- 2. Background
- 3. Description
- 3.1. Dynamic Package Graph
- 3.2. Known Issues for Packaged Processes
- 3.3. Install-time 'Pinning' aka Prevent Removal
- 3.4. Runtime 'Pinning' aka Prevent Update While In-Use
- 4. Examples
- 5. Remarks
- 5.1. API Overview
- 5.2. Package Dependency Resolution is Per-User
- 5.3. LocalSystem is not Supported
- 5.4. Packaging - Microsoft.WindowsAppRuntime.dll, Microsoft.WindowsAppRuntime.Bootstrap.dll and DDLM
- 5.5. Dynamic Dependencies vis a vis Static Dependencies
- 5.6. Known Limitations in v1
- 6. API Details
- 7. Static Package Dependency Resolution Algorithm
MSIX supports the ability for packaged applications to access components and other files distributed as Framework packages. However this functionality provides a rather 'static' world view, where dependencies are known at development time and resolved at install-time. There is no support at runtime to define a Framework dependency based on machine state, user interface or other post-development factors of the developer's choosing. Access to Framework package content is also limited to packaged applications; non-packaged applications cannot use content provided via Framework packages.
Microsoft-internal task 23447728
This is the spec for proposal MSIX Dynamic Dependencies - allow any process to use MSIX Framework packages #89.
This feature provides APIs to enable access to packaged content at runtime, regardless if the caller is packaged or not. This supplements the MSIX appmodel's current static dependency support (via an appxmanifest.xml) with a dynamic runtime equivalent. It also allows non-packaged processes (which have no appxmanifest.xml) to use packaged content.
Dynamic selection and access of packaged content is challenged by several issues:
A process' Package Graph has historically been fixed at process creation. There has been no affordance to alter a process' package graph at runtime.
Packaged processes are initialized with a package graph based on package dependencies declared in
their appxmanifest.xml
. See Static Package Dependency Resolution Algorithm
for more details. Non-packaged processes have no declared dependencies as they
have no appxmanifest.xml
, thus non-packaged processes are created with an
empty Package Graph. Once a process is created its package graph was constant for the rest of its lifetime.
We'll provide new APIs to alter the current process' Package Graph at runtime.
Windows provides access to a process' package graph primarily via GetCurrentPackageInfo and GetCurrentPackageInfo2. Equivalent information is available via Windows.ApplicationModel.Package.Current and Package.Current.Dependencies. These correspond to PackageGraph[0] and PackageGraph[1+]. These APIs provide access to the static package graph.
This information is used by several Windows components to find resources across packages available to a process (aka across a process' package graph) including:
- DLLs - the Loader searches the package graph for DLLs (per Dynamic-Link Library Search Order)
- CLR - the CLR searches the package graph for assemblies (similar to how the Loader does so for DLLs)
- WinRT - WinRT searches the package graph for WinRT activation. Type resolution is constrained to types provided by packages in a process' package graph (and those provided by Windows via the
Windows.*
namespace). Thius include metadata resolution (i.e. *.winmd). - MRT - MRT searches the package graph for resources (resources.pri, images, etc)
- ms-appx URI scheme - ms-appx URIs are resolved to packages across the package graph
If we can alter the package graph as seen by GetCurrentPackageInfo
and like APIs we can provide
the desired dynamic behavior.
We'll manage our own package graph to supplement the static package graph (if any) with dynamic
dependencies. We'll use Detours to hook
GetCurrentPackageInfo
and related APIs to redirect to our own Dynamic Dependencies savvy variants.
LoadPackageLibrary() provides a restricted form of LoadLibrary, but restricted to packaged processes. We'll enlighten LoadPackageLibrary to support the dynamic package graph and work as expected, regardless if the process is packaged or not.
Package Graph information is available for packages registered to the current user via OpenPackageInfoByFullName. We'll keep track of the expanded package graph (static + dynamic) via a global variable filled with PACKAGE_INFO_REFRENCE objects. Our Detour'd API variants walk this information to produce their answers seamlessly spanning static and dynamic package information for the process.
// An instance with no PACKAGE_INFO_REFERENCE (nullptr) is a placeholder for the static package graph
class PackageGraphNode
{
...
private:
PACKAGE_INFO_REFERENCE m_packageInfoReference{};
MDD_PACKAGE_DEPENDENCY_CONTEXT m_context{};
DLL_DIRECTORY_COOKIE m_addDllDirectoryCookie{};
};
static std::mutex g_lock;
std::vector<PackageGraphNode> g_packageGraph;
static LONG (WINAPI * TruGetCurrentPackageInfo)(...) = CurrentPackageInfo;
LONG DynamicGetCurrentPackageInfo(...);
...
BOOL DllMain(...)
{
if (DetourIsHelperProcess())
{
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH)
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&TrueGetCurrentPackageInfo, DynamicGetCurrentPackageInfo);
...
DetourTransactionCommit();
}
else if (dwReason == DLL_PROCESS_DETACH)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&TrueGetCurrentPackageInfo, DynamicGetCurrentPackageInfo);
...
DetourTransactionCommit();
}
return TRUE;
}
LONG DynamicGetCurrentPackageInfo(...PACKAGE_INFO* packageInfo...UINT32* count)
{
*count = 0;
std::unique_lock<std::mutex> lock(g_lock);
for (node : g_packageGraph)
{
auto packageInfoReference = node.get();
if (node)
{
packageInfo += GetPackageInfo(packageInfoRefernce...);
}
else
{
packageInfo += TrueGetCurrentPackageInfo(...);
}
++count
}
return ERROR_SUCCESS;
}
...
HRESULT MddAddPackageDependency(...)
{
...
std::unique_lock<std::mutex> lock(g_lock);
}
MddAddPackageDependency
has 2 responsibilities:
- Resolve a package dependency to a specific package
- Update the package graph
Updating the package graph also requires the DLL Search Order be updated. See below for more details.
HRESULT MddAddPackageDependency(...)
{
std::unique_lock<std::mutex> lock(g_lock);
auto packageFullName = ResolvePackageDependency(packageFamilyName, minVersion, architectureFilter, ...);
if (!packageFullName)
{
return ERROR;
}
return AddToPackageGraph(packageFullName, ...);
}
Resolve the package dependency to a (specific) package, per the following conditions:
- Packages in Package Family
- PackageType = Framework
- Packages registered to the user
- Highest version
- Version >= packageDependency.MinVersion
- Architecture meets ArchitectureFilter criteria (see below)
- PackageStatus = usable (not Bad, not NotAvailable, etc)
If 2+ meet the criteria due, then the best-fit is...
- Highest version
- Architecture exactly matches caller's architecture
- Architecture=Neutral
Thus v5 is better than v4, but choosing amongst v5-x86 vs v5-x64 vs v5-neutral depends on the caller's architecture. For example, assuming multiple package candiates only varying by version and/or architecture:
Given v1 and v2
- Best-Fit = v2
Given v1-x86 and v1-x64
- Best-Fit for x64 callers = v1-x64
- Best-Fit for x86 callers = v1-x86
- Best-Fit for arm callers = --no match--
Given v1-x86 and v1-neutral
- Best-Fit for x64 callers = v1-neutral
- Best-Fit for x86 callers = v1-x86
- Best-Fit for arm callers = v1-neutral
NOTE: MddAddPackageDependency
can only resolve to packages registered for the user. Thus as
packages cannot be registered to LocalSystem a process running as LocalSystem cannot use Dynamic
Dependencies. Impersonation is irrelevant here. Only the process' user identity is relevant to
determine the packages registered to the user.
string ResolvePackageDependency(packageFamily, minVersion, architectureFilter, ...)
{
string[] packageFullNames = FindPackagesByPackageFamily(packageFamilyName, packageTypeFilter=Framework, ...);
count = packageFullNames.size();
if (count == 0)
{
return ERROR;
}
string bestFit = null;
for (packageFullName : packageFullNames)
{
// Do we already have a higher version under consideration?
if (bestFit && bestFit.Version > packageFullName.Version)
{
continue;
}
// Package version must meet the minVersion filter
if (packageFullName.Version < minVersion)
{
continue;
}
// Package architecture must meet the architecture filter
if (architectureFilter == PackageDependencyArchitectureFilters.None)
{
if (!IsPackageABetterFitPerArchitecture(bestFit, packageFullName))
{
continue;
}
}
else
{
if (packageFullName.Architecture not in architectureFilter)
{
continue;
}
}
// Package status must be OK to use a package
auto packageManager = ActivateInstance(Windows.Management.Deployment.PackageManager);
auto currentUser = "";
auto package = packageManager.FindPackageForUser(currentUser, packageFullName);
if (!package.VerifyIsOK())
{
continue;
}
// The new candidate is better than the current champion
bestFit = packageFullName;
}
return bestFit;
}
bool IsPackageABetterFitPerArchitecture(string bestFit, string packageFullName)
{
// Is the package a contender?
const auto currentArchitecture = GetCurrentArchitecture();
if (packageFullName.Architecture != Neutral && packageFullName.Architecture != currentArchitecture)
{
return false;
}
// Do we have a best-fit candidate yet?
if (!bestFit)
{
// We do now :-)
return true;
}
// Is the new package a better fit than the current champion?
// Architecture-specific is always better than architecture-neutral
if (bestFit.Architecture == Neutral && packageFullName.Architecture != Neutral)
{
return false;
}
// We have a new winner!
return true;
}
Architecture GetCurrentArchitecture()
{
#if defined(_M_ARM)
return Arm;
#elif defined(_M_ARM64)
return Arm64;
#elif defined(_M_IX86)
return X86;
#elif defined(_M_X64)
return X64;
#else
# error "Unknown processor architecture"
#endif
}
A package graph is a package dependency graph flattened into an ordered list.
Searches using this list start with the lowest rank and proceed in ascending order through the list. For example, when the Loader searches the package graph for DLLs (per Dynamic-Link Library Search Order).
Package processes start with a package graph containing the package and its dependencies, as resolved resolved at install time. This can be accessed via Windows.ApplicationModel.Package followed by Windows.ApplicationModel.Package.Dependencies. This is referred to as the 'static package graph' as it's fixed at process creation and cannot be altered.
Non-packaged processes start with an empty package graph.
A process' package graph can be altered dynamically at runtime via the Dynamic Dependency API.
Packages can be added dynamically to the package graph,
managed as as a sorted list based on rank
. The list is sorted in ascending order
i.e. -∞...0...+∞.
Packages in the static package graph have a rank of MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT (0).
If a package graph node already exists with the same rank as a new addition the new node is, by
default, appended to the nodes of matching rank. If
MddAddPackageDependencyOptionsPrependIfRankCollision
is specified the new node is prepended to
those of matching rank.
HRESULT AddToPackageGraph(string packageFullName, ...)
{
auto packageInfoReference = OpenPackageInfoByFullName(packageFullName);
auto packageGraphNode = PackageGraphNode(packageInfoReference);
// Find the insertion point where to add the new package graph node to the package graph
int index = 0;
for (; index < g_packageGraph.size(); ++index)
{
auto& node = g_packageGraph[index];
if (node.rank() < rank)
{
// Too soon. Keep looking
continue;
}
else if (rank < node.rank())
{
// Gotcha!
break;
}
if (node.rank() == rank)
{
// Match! Insert before or after items of the same rank?
if (options.PrependIfRankCollision)
{
// Gotcha!
break;
}
else
{
// Append to items of this rank
for (int nextIndex=index+1; nextIndex < g_packageGraph.size(); ++nextIndex)
{
auto& nextNode = g_packageGraph[nextIndex];
if (nextNode.rank() > rank)
{
// Gotcha!
break;
}
}
}
}
}
// Add the new node to the package graph
if (index <> g_packageGraph.size())
{
g_packageGraph.insert(index, packageGraphNode);
}
else
{
g_packageGraph.append(packageGraphNode);
}
// The DLL Search Order must be updated when we update the package graph
AddToDllSearchOrder(node);
}
NOTE: This pseudo-code can be expressed more consisely as:
HRESULT AddToPackageGraph(string packageFullName, ...)
{
auto packageInfoReference = OpenPackageInfoByFullName(packageFullName);
auto node = PackageGraphNode(packageInfoReference);
auto by_rank = [](auto&& left, auto&& right) { return left.rank < right.rank; });
auto where = options.PrependIfRankCollision ?
std::lower_bound(g_packageGraph.begin(), g_packageGraph.end(), node, by_rank) :
std::upper_bound(g_packageGraph.begin(), g_packageGraph.end(), node, by_rank);
g_packageGraph.insert(where, node);
return S_OK;
}
MddRemovePackageDependency
undoes the work done by MddRemovePackageDependency:
- Update the package graph
Updating the package graph also requires the DLL Search Order be updated. See below for more details.
HRESULT MddRemovePackageDependency(MDD_PACKAGE_DEPENDENCY_CONTEXT context)
{
std::unique_lock<std::mutex> lock(g_lock);
for (int index=0; index < g_packageGraph.size(); ++index)
{
auto& node = g_packageGraph[index];
if (node.context() == context)
{
// The DLL Search Order must be updated when we update the package graph
RemoveFromDllSearchOrder(node);
g_packageGraph.erase(index);
return S_OK;
}
}
return HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE);
}
Windows informs the Loader when the package graph is defined, but this doesn't help after process creation (and non-packaged processes never have a static package graph).
The Dynamic Dependencies API informs the loader when the package graph changes.
For non-packaged processes we'll update the DLL Search Order via the PATH environment variable and AddDllDirectory. Neither technique alone suffices as one or the other can be ignored depending on LoadLibraryEx parameters. We'll set both to cover the bases. Worst case a directory is listed twice in the search order, incurring a small cost when failing to find a file in the directory (two misses instead of one).
void AddToDllSearchOrder(PackageGraphNode package)
{
// Update the PATH environment variable
UpdatePath();
// Update the AddDllDirectory list
auto cookie = AddDllDirectory(package.path());
package.addDllDirectoryCookie(cookie);
}
void RemoveFromDllSearchOrder(PackageGraphNode package)
{
// Update the PATH environment variable
UpdatePath();
// Update the AddDllDirectory list
RemoveDllDirectory(package.addDllDirectoryCookie());
}
static string g_pathListLastAddedToPath;
void UpdatePath()
{
// Build the package graph path list (semi-colon delimited)
string pathList;
for (PackageGraphNode node : g_packageGraph)
{
const auto flags = PACKAGE_FILTER_HEAD | PACKAGE_FILTER_DIRECT | PACKAGE_FILTER_OPTIONAL_IN_RELATED_SET;
UINT32 count = 0;
auto packageInfoReference = node.get();
if (packageInfoReference)
{
PACKAGE_INFO[] packageInfos = GetPackageInfo(packageInfoReference, flags, ...);
}
else
{
PACKAGE_INFO[] packageInfos = TrueGetCurrentPackageInfo(packageInfoReference, flags, ...);
}
AppendPathsToList(pathList, packageInfos);
}
// Add the path list to the PATH enironment variable
// If not present in PATH then add the path list to the front of PATH
// If already present then replace it with the updated path list
// package graph environment variable's at the fron ot the PATH environment variable
string path = GetEnvironmentVariable("PATH");
if (g_pathListLastAddedToPath.length() == 0)
{
// First time we're changing PATH. Prepend the path list to PATH
path = pathList + ";" + path;
}
else
{
// Find the previous path list in PATH (if present)
...find g_pathg_pastListLastAddedToPath in path...
if (found)
...path.replace(g_pastListLastAddedToPath, pathList)...
else
path = pathlist + ";" + path;
}
SetEnvironmentVariable("PATH", path);
// Remember the path list we added to PATH for future updates
g_pathListLastAddedToPath = pathList;
}
void AppendPathsToList(string& pathList, PACKAGE_INFO[] packageInfos)
{
for (packageInfo : packageInfos)
{
if (pathList.size() > 0)
{
path += ";";
}
path += packageInfo.path;
}
}
Loader path modifications (via PATH environment variable and AddDllDirectory) are ignored for packaged processes. The loader instead uses:
- DLL Redirection (aka
pkgdir\microsoft.system.package.metadata\Application.Local
) if DevOverideEnable=1 - APIsets
- [DesktopBridge processes] SxS manifest DLL redirection
- Loaded Module List (i.e. DLLs already loaded into memory)
- Known DLLs
- Package Graph
- Executeable's directory OR top-loading module's directory if
LOAD_WITH_ALTERED_SEARCH_PATH
is specified - %SystemRoot%\system32
None of these are sufficiently flexible to leverage for Dynamic Dependencies.
TODO Perhaps the uap7:ImportRedirectionTable supporting machinery can help out here?
3.2.1. Known Issue: DLL Search Order ignores uap6:LoaderSearchPathOverride
The current design and implementation don't consult uap6:LoaderSearchPathOverride when updating the Loader's path list to find DLLs.
3.2.2. Known Issue: DLL Search Order ignores uap6:AllowExecution
The current design and implementation don't consult uap6:AllowExecution to determine if a package should be in the DLL Search Order.
For packaged processes only packages with execution rights are included in the DLL Search Order. This can be explicitly controlled via the uap6:AllowExecution element in appxmanifest.xml. The default value varies, depending on package:
AllowExecution default = True
- Main
- Framework
- Optional, if InRelatedSet
AllowExecution default = False
- Resource
- Optional, if not InRelatedSet
There are no public APIs to get this information so we'll build the DLL Search Order only based on package types (as above), regardless if a package manifests uap6:AllowExecution.
Deployment uses a form of 'garbage collection' to decide if a package is unused and can be removed from a system.
When a Main package is registered for a user its manifested <PackageDependency>
are resolved to
specific packages and saved to disk. A Main package and its resolved package dependencies are
referred to as a 'package graph'. When evaluating packages if Deployment sees there are no package
graphs referencing a Framework package, Deployment may decide to remove the package from the system.
This is a classic garbage collection model - an object (package) can be removed if there are no
outstanding object references (dependencies in package graphs) for it.
This 'garbage collection' evaluation is based on manifested dependencies. Packages used via Dynamic Dependencies express no such manifested dependency and are unknown to Deployment.
To prevent Deployment from prematurely removing a package a packaged application can add a
<PackageDependency>
on the Framework package.
Applications can install a Main
package with a manifested <PackageDependency>
referencing the
Framework package. This Main package requires a minimal manifest. For example:
<Package
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
IgnorableNamespaces="uap10">
<Identity Name="LolzKitten" Publisher="CN=Fabrikam" Version="1.2.3.4" />
<Properties>
<DisplayName>Lolz! Kitten!</DisplayName>
<PublisherDisplayName>Fabrikam Incorporated</PublisherDisplayName>
<Logo>logo.png</Logo>
</Properties>
<Resources>
<Resource Language="" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763" MaxVersionTested="10.0.17763" />
<PackageDependency Name="Contoso.Muffins" MinVersion="1.0.1967.0" />
</Dependencies>
</Package>
When this package is registered for a user the <PackageDependency>
element will be resolved to a
(specific) Framework package. That Framework package will be 'pinned' for the user as long as this
Main package is registered for the user. Deployment will not remove the Framework because it now
sees a dependency needing it.
A future version of Windows should be more savvy to Dynamic Dependencies and not require this 'helper' Main package to prevent unintended premature removal of a Framework package. Until then, this is the recommended technique for install-time pinning.
To that end, Windows App SDK includes the Dynamic Dependency Lifetime Manager (DDLM) to provide this install-time 'pinning' for Windows App SDK's Framework package.
Registering an updated package for a user poses a complication for applications using Dynamic Dependencies.
Framework packages support concurrent versioning semantics. This differs from other package types:
- A user can only have 0-1 Main package in a package family registered at a time
- A user can have 0+ Framework packages in a package family registered at a time
If Main-v1 is registered to a user and Main-v2 is registered, Deployment changes the user to have v2 registered instead of v1. Frameworks have no such 'There Can Be Only One' restriction. If Fwk-v1 is registered for a user when Fwk-v2 is registered...
- All Main packages dependent on Fwk-v1 but not in use have their package graphs changed to reference Fwk-v2 (instead of Fwk-v1)
- All Main packages dependent on Fwk-v1 that are in use are not modified. When the last running packaged process for a user terminates, the Framework package is no longer in use by that Main package and will be updated to Fwk-v2.
where 'in use' is defined as 'one or more packaged processes with static package graphs referencing the Framework package'.
When a Main package is updated it means the next time a process runs with that Main package's identity it will include Fwk-v2 in its package graph (not Fwk-v1).
This 'do not service package graphs for running packaged processes' behavior relies on manifested package dependencies to detect which processes use which Framework packages. A Framework package used via Dynamic Dependencies has no such static information and thus is effectively invisible and unknown. Deployment determines no one is used Fwk-v1 and will happily deregister it for the user, breaking any future calls to MddAddPackageDependency (which only resolves package dependencies to packages registered to the user).
If no other user has the Framework package registered and it's not provisioned Deployment determines there's no need for Fwk-v1 to stay on the machine and removes it from the system. DLLs loaded by running processes cannot be deleted, but DLLS not already loaded and other files (data files etc) can and will be deleted. This can have minor to disastrous impact on an appplication using Fwk-v1, e.g.
- an application is running using A.dll from Fwk-v1
- Deployment determines no one needs Fwk-v1
- Deployment deletes Fwk-v1's B.dll but cannot delete A.dll
- the application needs to
LoadLibrary("B.dll")
and fails because it's been deleted
This 'torn package' problem can be prevented by ensuring a process is running with the identity of a Main package with a manifested dependency on the Framework.
One solution is to include a COM OutOfProcess server
in the 'helper' Main package. At runtime, instantiate the COM server before calling
MddAddPackageDependency
and hang onto the COM server's interface pointer until after calling
MddRemovePackageDependency
(or process termination).
Alternatively, the 'helper' Main package can declare an AppService.
At runtime, establish a connection with the AppService before MddAddPackageDependency
and don't
close the connection until after MddRemovePackageDependency
.
Deployment detects this process running with the 'helper' Main package's identity and will not remove the referenced Framework package when updating the Framework.
NOTE: A single Windows App SDK Main package can provide the needed 'pin' support but blocks servicing scenarios e.g. App1 is running when App2 installs a newer Windows App SDK Framework package. These issues can be avoided via a per-app 'helper' Main package. See TBD-link-to-per-app-helper-proposal for more details.
Windows App SDK includes the Dynamic Dependency Lifetime Manager (DDLM) integrated with the bootstrapper API to provide this runtime 'pinning' for not-packaged applications.
Samples illustrating the DynamicDependency APIs
- Sample 1 - Fabrikam app using Contoso's Muffins package [Win32] [WinRT]
- Sample 2 - Fabrikam app using Contoso's Muffins package with smart class helpers [Win32]
- Sample 3 - LolzKitten app using Contoso's Muffins package via transient package dependency [Win32] [WinRT]
- Sample 4 - LolzKitten Installer / Uninstaller with File+Registry LifetimeArtifacts [Win32] [WinRT]
- Sample 5 - LolzKitten app using PackageDependency pinned by LolzKitten Installer [Win32] [WinRT]
- Sample 6 - LolzKitten Installer / Uninstaller defining a 32bit PackageDependency [Win32] [WinRT]
- Sample 7 - LolzKitten app ordering Packages in PackageGraph [Win32] [WinRT]
- Sample 8 - LolzKitten app ordering Packages in PackageGraph with prepend [Win32] [WinRT]
- Sample B.1 - HelloWorld console app using the Boostrap API [Win32] [C#] [C# (no throw)]
All processes have a package graph. A process may be created with entries in its package graph; this is referred to as the 'static package graph'.
Packaged processes (i.e. a process with package identity) are created with a static package graph per their AppxManifest.xml. A process' static package graph cannot be altered, but it can be supplemented at runtime via the Dynamic Dependency API.
Processes without package identity have an no static package graph. They can modify their package graph using the Dynamic Dependency API.
The API supports 4 main operations:
- Create
- Add
- Remove
- Delete
MddTryCreatePackageDependency
defines a package dependency.
MddAddPackageDependency
determines a package that satisfies a package dependency and updates
the caller's process. This includes adding the resolved package to the process' package graph,
updating the Loader to include the resolved package in the DLL Search Order, etc. The package
dependency is resolved to a specific package if not already resolved.
A resolved PackageDependency is represented by MDD_PACKAGE_DEPENDENCY_CONTEXT
.
Once a PackageDependency is resolved to a package all further MddAddPackageDependency
calls
yield the same result until the package dependency is unresolved. Resolved package dependencies are
tracked by User + PackageDependencyId. This ensures multiple overlapping calls to
MddAddPackageDependency
yield the same result. A package dependency is unresolved when the
last MDD_PACKAGE_DEPENDENCY_CONTEXT
is closed (via MddRemovePackageDependency
or process
termination).
MddRemovePackageDependency
removes the resolved PackageDependency
from the calling process' package graph.
MddDeletePackageDependency
undefines a package dependency
previously defined via MddTryCreatePackageDependency
.
Package graphs are managed per-user, thus resolving a package dependency is a per-user operation. Two users can resolve a package dependency to different answers, depending on the current machine state.
Package dependencies can only be resolved to packages registered for a user. As packages cannot be registered for LocalSystem the Dynamic Dependencies feature is not available to callers running as LocalSystem.
5.4. Packaging - Microsoft.WindowsAppRuntime.dll, Microsoft.WindowsAppRuntime.Bootstrap.dll and DDLM
The Dynamic Dependencies API is provided via Microsoft.WindowsAppRuntime.dll in Windows App SDK's Framework package.
Microsoft.WindowsAppRuntime.Bootstrap.dll is a redistributable for non-packaged applications.
Packaged applications can declare <PackageDependency Name='Microsoft.WindowsAppRuntime'...>
to access Framework packages.
Non-packaged applications need to use the 'boostrapper API' in Microsoft.WindowsAppRuntime.Bootstrap.dll to get access to
Windows App SDK's Framework package. See [3.4. Runtime 'Pinning' aka Prevent Update While In-Use] for more details.
The 'bootstrapper API' provides an initialization function that performs the following actions:
- Find the per-application 'helper' Main package and create a process with its identity
- Find and load Microsoft.WindowsAppRuntime.dll from the Windows App SDK Framework package
Here's an architectural diagram showing the primary actors and their flow:
- An non-packaged app using Dynamic Dependencies calls
MddBootstrapInitialize()
exported fromMicrosoft.WindowsAppRuntime.Bootstrap.dll
. MddBootstrapInitialize()
uses Windows.ApplicationModel.AppExtension to enumerate Windows App SDK's Dynamic Dependency Lifetime Mananer (DDLM) packages with matching version.major and architecture and select the best matching package.MddBootstrapInitialize()
callsCoCreateInstance(clsid)
to instantiate the app's Packaged COM OOP Server from DynamicDependencyLifetimeManager.exe in the selected Windows App SDK DDLM package.MddBootstrapInitialize()
callsLoadLibrary(GetPath(WindowsAppRuntimeFramework) + "\Microsoft.WindowsAppRuntime.dll"))
to load the dll containing the Dynamic Dependencies API.Microsoft.WindowsAppRuntime.dll
's DllMain() initializes Detours to support dynamic dependencies in the process' package graph.MddBootstrapInitialize()
uses the Dynamic Dependencies API to create and add the Windows App SDK Framework package to the process' package graph- The application is now free to call the Dynamic Depedencies API.
Thus calling MddBootstrapInitialize(minVersion=1.2.3.4)
is akin to calling
globalvar packageDependendency = MddTryCreatePackageDependency(minVersion=1.2.3.4, Lifetime=Process)
globalvar context = MddAddPackageDependency(packageDependency)
and calling MddBootstrapShutdown()
is akin to calling
MddRemovePackageDependency(context)
MddDeletePackageDependency(packageDependency)
The per-application 'helper' Main package defines a COM OutOfProcess
server
and <PackageDependency Name="Microsoft.WindowsAppRuntime"...>
.
The bootstrapper API calls CoCreateInstance() to create a process with this package's identity. The Dynamic Dependencies API can be used by non-packaged processes as long as that process runs. The bootstrapper API holds a reference to the returned object until a 'bootstrapper shutdown' API is called or process termination.
The bootstrapper API finds the Windows App SDK Framework package and calls LoadLibrary("Microsoft.WindowsAppRuntime.dll") to load and initialize the Dynamic Dependencies API.
Windows App SDK provides a Dynamic Dependency Lifetime Manager (DDLM)
as a Packaged COM OutOfProcess Server
to provide runtime 'pinning' of Windows App SDK's Framework package for not-packaged applications.
This COM server is provided via a Main package called the Dynamic Dependency Lifetime Manager Main package, aka the DDLM Main package or simply 'the DDLM package'. This package also provides install-time 'pinning' for Windows App SDK's Framework package.
The DDLM is a Main package unique to its associated Windows App SDK Framework package, down to its version+architecture. To accomplish this a DDLM package's identity includes this information in its package name:
DDLM Package Name = "Microsoft.WindowsAppRuntime-<version>-<architecture>"
For example, given a Windows App SDK Framework package with the package full name of
Microsoft.WindowsAppRuntime.1.1967.333_x64__1234567890abc
the associated DDLM package would have a package full name of
Microsoft.WindowsAppRuntime.DDLM-4.1.1967.333-x64_4.1.1967.333_x64__1234567890abc
Version and architecture are encoded in the DDLM's package name the same way they're formatted in a package full name.
NOTE: This DDLM->Framework association is defined via <PackageDependency>
in the DDLM's appxmanifest.xml
so this DDLM->Framework association is a minimum-version dependency (not an exact match), as that's how Windows
evaluates a <PackageDependency>
. The DDLM ensures the associated Framework package or a higher version will
be available.
A DDLM package's appxmanifest.xml
declares a <PackageDependency>
on its associated
Framework package. For example
<PackageDependency
Name="Microsoft.WindowsAppRuntime.Framework"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
MinVersion="4.1.1967.333"/>
This ensures Windows keeps at least 1 package meeting this criteria.
A DDLM package contains a DDLM as a Packaged COM OutOfProcess Server to provide runtime 'pinning'. This COM server has a CLSID unique to its containing DDLM. For example:
A DDLM package's appxmanifest.xml
declares an AppExtension to advertise its availability for the
bootstrapper API to efficiently enumerate and the CLSID of its Packaged COM OutOfProcess Server. For example
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="microsoft.winappruntime.ddlm.4.1-x6-e1"
Id="ddlm-4.1.1967.333-x64-e1"
PublicFolder="public\ddlm"
DisplayName="Windows App SDK DynamicDependency LifetimeManager Extension (4.1.* x64 experimental1)">
<uap3:Properties>
<CLSID>32E7CF70-038C-429a-BD49-88850F1B4A11</CLSID>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
The declared AppExtension has a name of
com.microsoft.winappruntime.ddlm.<version.major>.<version.minor>.<architcture>
NOTE: AppExtension name is limited to <=39 characters on Windows builds <10.0.18307.0
(RS5=10.0.17763.0, 19H1=10.0.18362). The preferred name microsoft.winappruntime.ddlm....
has a length of 40+ characters so we use this shortened form for as long as we need to support
older releases.
The bootstrapper API uses Windows.ApplicationModel.AppExtension to enumerate all Windows App SDK Framework packages with the specified Major version number and selects the one best matching the criteria passed to the bootstrapper API.
Once selected, the bootstrapper API MddBootstrapInitialize()
creates an instance of the DDLM (COM object)
per the CLSID specified and holds onto it until MddBootstrapShutdown()
is called (or the process terminates).
The bootstrap API then determines the specific instance of Windows App SDK's Framework package in the
DDLM process' package graph and dynamically adds that to the calling process' package graph. Thus even though
an application calling the bootstrapper API may find v1.2.3.4 of the DDLM package it will resolve an
equal or greater version of the Framework package (whatever version Windows has resolved as the DDLM's package graph per its <PackageDependency>
).
A DDLM package defines a DDLM as a Packaged COM OutOfProcess Server. The DDLM package's AppxManifest.xml
declares the DDLM:
<Package>
...
<Applications>
<Application Id="DynamicDependencyLifetimeManager" Executable="DynamicDependencyLifetimeManager.exe" EntryPoint="Windows.PartialTrustApplication">
<uap:VisualElements AppListEntry="none" DisplayName="DynamicDependency Lifetime Manager" ...>
<uap:SplashScreen ... uap5:Optional="true" />
</uap:VisualElements>
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="DynamicDependencyLifetimeManager.exe" DisplayName="Windows App SDK DynamicDependency LifetimeManager">
<com:Class Id="...clsid..." DisplayName="Windows App SDK DynamicDependency LifetimeManager"/>
</com:ExeServer>
</com:ComServer>
</com:Extension>
<com:Extension Category="windows.comInterface">
<com:ComInterface>
<com:ProxyStub Id="06F1BAD0-DD14-11d0-AB8F-0000C0148FDB" DisplayName="DynamicDependencyLifetimeManager ProxyStub" Path="DynamicDependencyLifetimeManager.ProxyStub.dll"/>
<com:Interface Id="06F1BAD0-DD14-11d0-AB8F-0000C0148FDB" ProxyStubClsid="06F1BAD0-DD14-11d0-AB8F-0000C0148FDB"/> <!-- IID_IDynamicDependencyLifetimeManager -->
</com:ComInterface>
</com:Extension>
</Extensions>
</Application>
</Applications>
...
</Package>
The windows.comServer
extension defines a Packaged COM OOP Server provided by DynamicDependencyLifetimeManager.exe.
This ensures Windows knows the Framework package(s) are in use and cannot be serviced while this process is running.
The windows.comServer
extension is an application-scope extension i.e. it requires an <Application>
.
The application is a non-intrusive definition, i.e. AppListEntry="none"
is specified to prevent it from appearing
in applists, as it serves no purpose other than providing a means to define the windows.comServer
extension.
Windows App SDK's DDLM package is signed the same as other Windows App SDK packages.
Dynamic dependencies' package dependency is equivalent to an MSIX package's <PackageDependency>
in
AppxManifest.xml
. This defines criteria to be resolved to a package for use at runtime in a
process' package graph.
Packages dynamically added to a package graph can be removed. Packages in the package graph at process creation (aka static package graph) cannot be removed; the static package graph is a part of a process' DNA, from birth to death.
The initial implementation does not fully implement this spec.
5.6.1. uap6:LoaderSearchPathOverride not supported
See 3.2.1. Known Issue: DLL Search Order ignores uap6:LoaderSearchPathOverride.
5.6.2. uap6:AllowExecution not supported
See 3.2.2. Known Issue: DLL Search Order ignores uap6:AllowExecution.
All Win32 APIs are prefixed with Mdd/MDD for MSIX Dynamic Dependencies.
This header contains the Dynamic Dependency API
enum class MddCreatePackageDependencyOptions : uint32_t
{
None = 0,
/// Disable dependency resolution when pinning a package dependency.
DoNotVerifyDependencyResolution = 0x00000001,
/// Define the package dependency for the system, accessible to all users
/// (default is the package dependency is defined for a specific user).
/// This option requires the caller has adminitrative privileges.
ScopeIsSystem = 0x00000002,
};
DEFINE_ENUM_FLAG_OPERATORS(MddCreatePackageDependencyOptions)
enum class MddPackageDependencyLifetimeKind
{
/// The current process is the lifetime artifact. The package dependency
/// is implicitly deleted when the process terminates.
Process = 0,
/// The lifetime artifact is an absolute filename or path.
/// The package dependency is implicitly deleted when this is deleted.
FilePath = 1,
/// The lifetime artifact is a registry key in the format
/// 'root\\subkey' where root is one of the following: HKLM, HKCU, HKCR, HKU.
/// The package dependency is implicitly deleted when this is deleted.
RegistryKey = 2,
};
enum class MddAddPackageDependencyOptions : uint32_t
{
None = 0,
PrependIfRankCollision = 0x00000001,
};
DEFINE_ENUM_FLAG_OPERATORS(MddAddPackageDependencyOptions)
#define MDD_PACKAGE_DEPENDENCY_RANK_DEFAULT 0
enum class MddPackageDependencyProcessorArchitectures : uint32_t
{
None = 0,
Neutral = 0x00000001,
X86 = 0x00000002,
X64 = 0x00000004,
Arm = 0x00000008,
Arm64 = 0x00000010,
X86OnArm64 = 0x00000020,
};
DEFINE_ENUM_FLAG_OPERATORS(MddPackageDependencyProcessorArchitectures)
DECLARE_HANDLE(MDD_PACKAGEDEPENDENCY_CONTEXT);
/// Define a package dependency. The criteria for a PackageDependency
/// (package family name, minimum version, etc)
/// may match multiple packages, but ensures Deployment won't remove
/// a package if it's the only one satisfying the PackageDependency.
///
/// @note A package matching a PackageDependency pin can still be removed
/// as long as there's another package that satisfies the PackageDependency.
/// For example, if Fwk-v1 is installed and a PackageDependency specifies
/// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove
/// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1
/// is removed Deployment won't remove Fwk-v2 because it's the only package
/// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other
/// package matching the PackageDependency) are 'loosely pinned'. Deployment
/// guarantees it won't remove a package if it would make a PackageDependency
/// unsatisfied.
///
/// A PackageDependency specifies criteria (package family, minimum version, etc)
/// and not a specific package. Deployment reserves the right to use a different
/// package (e.g. higher version) to satisfy the PackageDependency if/when
/// one becomes available.
///
/// @param user the user scope of the package dependency. If NULL the caller's
/// user context is used. MUST be NULL if MddCreatePackageDependencyOptions::ScopeIsSystem
/// is specified
/// @param lifetimeArtifact MUST be NULL if lifetimeKind=MddPackageDependencyLifetimeKind::Process
/// @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate
///
/// @note MddTryCreatePackageDependency() fails if the PackageDependency cannot be resolved to a specific
/// package. This package resolution check is skipped if
/// MddCreatePackageDependencyOptions::DoNotVerifyDependencyResolution is specified. This is useful
/// for installers running as user contexts other than the target user (e.g. installers
/// running as LocalSystem).
STDAPI MddTryCreatePackageDependency(
PSID user,
_In_ PCWSTR packageFamilyName,
PACKAGE_VERSION minVersion,
MddPackageDependencyProcessorArchitectures packageDependencyProcessorArchitectures,
MddPackageDependencyLifetimeKind lifetimeKind,
PCWSTR lifetimeArtifact,
MddCreatePackageDependencyOptions options,
_Outptr_result_maybenull_ PWSTR* packageDependencyId) noexcept;
/// Undefine a package dependency. Removing a pin on a PackageDependency is typically done at uninstall-time.
/// This implicitly occurs if the package dependency's 'lifetime artifact' (specified via MddTryCreatePackageDependency)
/// is deleted. Packages that are not referenced by other packages and have no pins are elegible to be removed.
///
/// @warn MddDeletePackageDependency() requires the caller have administrative privileges
/// if the package dependency was pinned with MddCreatePackageDependencyOptions::ScopeIsSystem.
STDAPI_(void) MddDeletePackageDependency(
_In_ PCWSTR packageDependencyId) noexcept;
/// Resolve a previously-pinned PackageDependency to a specific package and
/// add it to the invoking process' package graph. Once the dependency has
/// been added other code-loading methods (LoadLibrary, CoCreateInstance, etc)
/// can find the binaries in the resolved package.
///
/// Package resolution is specific to a user and can return different values
/// for different users on a system.
///
/// Each successful MddAddPackageDependency() adds the resolve packaged to the
/// calling process' package graph, even if already present. There is no
/// duplicate 'detection' or 'filtering' applied by the API (multiple
/// references from a package is not harmful). Once resolution is complete
/// the package dependency stays resolved for that user until the last reference across
/// all processes for that user is removed via MddRemovePackageDependency (or
/// process termination).
///
/// MddAddPackageDependency() adds the resolved package to the caller's package graph,
/// per the rank specified. A process' package graph is a list of packages sorted by
/// rank in ascending order (-infinity...0...+infinity). If package(s) are present in the
/// package graph with the same rank as the call to MddAddPackageDependency the resolved
/// package is (by default) added after others of the same rank. To add a package
/// before others o the same rank, specify MddAddPackageDependencyOptions::PrependIfRankCollision.
///
/// Every MddAddPackageDependency can be balanced by a MddRemovePackageDependency
/// to remove the entry from the package graph. If the process terminates all package
/// references are removed, but any pins stay behind.
///
/// MddAddPackageDependency adds the resolved package to the process' package
/// graph, per the rank and options parameters. The process' package
/// graph is used to search for DLLs (per Dynamic-Link Library Search Order),
/// WinRT objects and other resources; the caller can now load DLLs, activate
/// WinRT objects and use other resources from the framework package until
/// MddRemovePackageDependency is called. The packageDependencyId parameter
/// must match a package dependency defined for the calling user or the
/// system (i.e. pinned with MddCreatePackageDependencyOptions::ScopeIsSystem) else
/// an error is returned.
///
/// @param packageDependencyContext valid until passed to MddRemovePackageDependency()
/// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate
STDAPI MddAddPackageDependency(
_In_ PCWSTR packageDependencyId,
INT32 rank,
MddAddPackageDependencyOptions options,
_Out_ MDD_PACKAGEDEPENDENCY_CONTEXT* packageDependencyContext,
_Outptr_opt_result_maybenull_ PWSTR* packageFullName) noexcept;
/// Remove a resolved PackageDependency from the current process' package graph
/// (i.e. undo MddAddPackageDependency). Used at runtime (i.e. the moral equivalent
/// of Windows' RemoveDllDirectory()).
///
/// @note This does not unload loaded resources (DLLs etc). After removing
/// a package dependency any files loaded from the package can continue
/// to be used; future file resolution will fail to see the removed
/// package dependency.
STDAPI_(void) MddRemovePackageDependency(
_In_ MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext) noexcept;
/// Return the package full name that would be used if the
/// PackageDependency were to be resolved. Does not add the
/// package to the process graph.
///
/// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate.
/// If the package dependency cannot be resolved the function
/// succeeds but packageFullName is nullptr.
STDAPI MddGetResolvedPackageFullNameForPackageDependency(
_In_ PCWSTR packageDependencyId,
_Outptr_result_maybenull_ PWSTR* packageFullName) noexcept;
/// Return the package dependency for the context.
///
/// @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate.
/// If the package dependency context cannot be resolved
/// the function succeeds but packageDependencyId is nullptr.
STDAPI MddGetIdForPackageDependencyContext(
_In_ MDD_PACKAGEDEPENDENCY_CONTEXT packageDependencyContext,
_Outptr_result_maybenull_ PWSTR* packageDependencyId) noexcept;
/// Return the package graph's current revision id.
STDAPI_(UINT32) MddGetPackageGraphRevisionId() noexcept;
/// Return the package graph's current revision id.
///
/// @note This API is deprecated and will be removed in a future release.
/// Use MddGetPackageGraphRevisionId().
STDAPI_(UINT32) MddGetGenerationId() noexcept;
This header contains the Bootstrap API
/// Options for Bootstrap initialization
typedef enum MddBootstrapInitializeOptions
{
/// Default behavior
MddBootstrapInitializeOptions_None = 0,
/// If not successful call DebugBreak()
MddBootstrapInitializeOptions_OnError_DebugBreak = 0x0001,
/// If not successful call DebugBreak() if a debugger is attached to the process
MddBootstrapInitializeOptions_OnError_DebugBreak_IfDebuggerAttached = 0x0002,
/// If not successful perform a fail-fast
MddBootstrapInitializeOptions_OnError_FailFast = 0x0004,
/// If a compatible Windows App Runtime framework package is not found show UI
MddBootstrapInitializeOptions_OnNoMatch_ShowUI = 0x0008,
/// Do nothing (do not error) if the process has package identity
MddBootstrapInitializeOptions_OnPackageIdentity_NOOP = 0x0010,
} MddBootstrapInitializeOptions;
#if defined(__cplusplus)
DEFINE_ENUM_FLAG_OPERATORS(MddBootstrapInitializeOptions)
#endif // defined(__cplusplus)
/// Initialize the calling process to use Windows App Runtime framework package.
///
/// Find a Windows App Runtime framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// If called multiple times the parameters must be compatible with the framework package
/// resolved by the first initialization call (i.e. the framework package currently in use).
/// If the request is not compatible with the framework package currently in use
/// the API fails and an error is returned.
///
/// @param majorMinorVersion the major and minor version to use, e..g 0x00010002 for Major.Minor=1.2
/// @param versionTag the version pre-release identifier, or NULL if none.
/// @param minVersion the minimum version to use
STDAPI MddBootstrapInitialize(
UINT32 majorMinorVersion,
PCWSTR versionTag,
PACKAGE_VERSION minVersion) noexcept;
/// Initialize the calling process to use Windows App Runtime framework package.
///
/// Find a Windows App Runtime framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// If called multiple times the parameters must be compatible with the framework package
/// resolved by the first initialization call (i.e. the framework package currently in use).
/// If the request is not compatible with the framework package currently in use
/// the API fails and an error is returned.
///
/// @param majorMinorVersion the major and minor version to use, e..g 0x00010002 for Major.Minor=1.2
/// @param versionTag the version pre-release identifier, or NULL if none.
/// @param minVersion the minimum version to use
STDAPI MddBootstrapInitialize2(
UINT32 majorMinorVersion,
PCWSTR versionTag,
PACKAGE_VERSION minVersion,
MddBootstrapInitializeOptions options) noexcept;
/// Undo the changes made by MddBoostrapInitialize().
///
/// @warning Packages made available via MddBootstrapInitialize2() and
/// the Dynamic Dependencies API should not be used after this call.
STDAPI_(void) MddBootstrapShutdown() noexcept;
// C++ friendly APIs
#if defined(__cplusplus)
#if defined(WINDOWSAPPSDK_RELEASE_MAJORMINOR) && defined(WINDOWSAPPSDK_RELEASE_VERSION_TAG_W) && defined(WINDOWSAPPSDK_RUNTIME_VERSION_UINT64)
namespace Microsoft::Windows::ApplicationModel
{
class PackageVersion : public PACKAGE_VERSION
{
public:
PackageVersion();
// Create an instance with the value `major.minor.build.revision`.
PackageVersion(uint16_t major, uint16_t minor = 0, uint16_t build = 0, uint16_t revision = 0);
// Create an instance from a version as a uint64.
PackageVersion(uint64_t version);
// Return the version as a uint64.
uint64_t ToVersion() const;
#if defined(_XSTRING_) && defined(_STRSAFE_H_INCLUDED_) && defined(WI_VERIFY)
// Return the string as a formatted value "major.minor.build.revision".
std::wstring ToString() const;
static std::wstring ToString(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision);
#endif
};
namespace DynamicDependency::Bootstrap
{
// Automate Boostrap shutdown when leaving scope
using unique_mddbootstrapshutdown_call = wil::unique_call<decltype(&::MddBootstrapShutdown), ::MddBootstrapShutdown>;
/// Options for Bootstrap initialization APIs.
/// @see InitializeFailFast(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Initialize(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see InitializeNoThrow(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
enum class InitializeOptions
{
/// Default behavior
None = MddBootstrapInitializeOptions_None,
/// If not successful call DebugBreak()
OnError_DebugBreak = MddBootstrapInitializeOptions_OnError_DebugBreak,
/// If not successful call DebugBreak() if a debugger is attached to the process
OnError_DebugBreak_IfDebuggerAttached = MddBootstrapInitializeOptions_OnError_DebugBreak_IfDebuggerAttached,
/// If not successful perform a fail-fast
OnError_FailFast = MddBootstrapInitializeOptions_OnError_FailFast,
/// If a compatible Windows App Runtime framework package is not found show UI
OnNoMatch_ShowUI = MddBootstrapInitializeOptions_OnNoMatch_ShowUI,
/// Do nothing (do not error) if the process has package identity
OnPackageIdentity_NOOP = MddBootstrapInitializeOptions_OnPackageIdentity_NOOP,
};
DEFINE_ENUM_FLAG_OPERATORS(InitializeOptions)
/// Call MddBootstrapInitialize2() and aborts the process (via std::abort()) if it fails;
/// returns an RAII object that reverts the initialization on success.
///
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use
/// @param options optional behavior
/// @see Initialize(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see InitializeNoThrow(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Shutdown()
/// ~~~~~
/// #include <windows.h>
///
/// #include <WindowsAppSDK-VersionInfo.h>
/// #include <MddBootstrap.h>
///
/// #include <iostream>
///
/// using MddBootstrap = Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap;
///
/// int main()
/// {
/// auto mddBootstrapShutdown = MddBootstrap::InitializeFailFast();
/// std::cout << "hello world";
/// return 0;
/// }
/// ~~~~~
[[nodiscard]] inline unique_mddbootstrapshutdown InitializeFailFast(
uint32_t majorMinorVersion = WINDOWSAPPSDK_RELEASE_MAJORMINOR,
PCWSTR versionTag = WINDOWSAPPSDK_RELEASE_VERSION_TAG_W,
PackageVersion minVersion = WINDOWSAPPSDK_RUNTIME_VERSION_UINT64,
InitializeOptions options = ::Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap::InitializeOptions::None);
#if defined(_CPPUNWIND) && defined(WINRT_BASE_H)
/// Call MddBootstrapInitialize2() and throws an exception if it fails;
/// returns an RAII object that reverts the initialization on success.
///
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use
/// @param options optional behavior
/// @see Initialize_failfast(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Initialize_nothrow(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Shutdown()
/// @exception winrt::hresult_error thrown if intialization fails; see code() for more details.
/// ~~~~~
/// #include <windows.h>
///
/// #include <winrt\base.h>
///
/// #include <WindowsAppSDK-VersionInfo.h>
/// #include <MddBootstrap.h>
///
/// #include <iostream>
///
/// using MddBootstrap = MddBootstrap;
///
/// int main()
/// {
/// try
/// {
/// auto mddBootstrapCleanup = MddBootstrap::Initialize();
/// std::cout << "hello world";
/// }
/// catch (const winrt::hresult_error& ex)
/// {
/// const auto hr{ ex.code() };
/// std::cout << "Error 0x" << std::hex << hr << " in Bootstrap initialization";
/// return hr;
/// }
/// return 0;
/// }
/// ~~~~~
[[nodiscard]] inline unique_mddbootstrapshutdown Initialize(
uint32_t majorMinorVersion = WINDOWSAPPSDK_RELEASE_MAJORMINOR,
PCWSTR versionTag = WINDOWSAPPSDK_RELEASE_VERSION_TAG_W,
PackageVersion minVersion = WINDOWSAPPSDK_RUNTIME_VERSION_UINT64,
InitializeOptions options = ::Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap::InitializeOptions::None);
#endif // defined(_CPPUNWIND) && defined(WINRT_BASE_H)
/// Call MddBootstrapInitialize2() and returns a failure HRESULT if it fails.
///
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use
/// @param options optional behavior
/// @see InitializeFailFast(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Initialize(uint32_t, PCWSTR, PackageVersion, InitializeOptions)
/// @see Shutdown()
/// ~~~~~
/// #include <windows.h>
///
/// #include <WindowsAppSDK-VersionInfo.h>
/// #include <MddBootstrap.h>
///
/// #include <iostream>
///
/// using MddBootstrap = Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap;
///
/// int main()
/// {
/// const auto hr{ MddBootstrap::InitializeNoThrow() };
/// if (FAILED(hr))
/// {
/// std::cout << "Error 0x" << std::hex << hr << " in Bootstrap initialization";
/// return hr;
/// }
/// auto mddBootstrapShutdown{ MddBootstrap::unique_mddbootstrapshutdown(reinterpret_cast<MddBootstrap::details::mddbootstrapshutdown_t*>(1)) };
/// std::cout << "hello world";
/// return 0;
/// }
/// ~~~~~
inline HRESULT InitializeNoThrow(
uint32_t majorMinorVersion = WINDOWSAPPSDK_RELEASE_MAJORMINOR,
PCWSTR versionTag = WINDOWSAPPSDK_RELEASE_VERSION_TAG_W,
PackageVersion minVersion = WINDOWSAPPSDK_RUNTIME_VERSION_UINT64,
InitializeOptions options = ::Microsoft::Windows::ApplicationModel::DynamicDependency::Bootstrap::InitializeOptions::None);
}
}
#endif // defined(WINDOWSAPPSDK_RELEASE_MAJORMINOR) && defined(WINDOWSAPPSDK_RELEASE_VERSION_TAG_W) && defined()WINDOWSAPPSDK_RUNTIME_VERSION_UINT64)
#endif // defined(__cplusplus)
This header contains the Lifetime Management API
/// Remove unnecessary Dynamic Dependency Lifetime Management (DDLM) packages.
///
/// A DDLM package can be removed if it's not in-use and a newer version is available.
STDAPI MddLifetimeManagementGC() noexcept;
This C# assembly contains the Bootstrap API for C#
namespace Microsoft.Windows.ApplicationModel.DynamicDependency
{
// The version of an MSIX package. This is logically `Major.Minor.Build.Revision` and can be expressed as...
// * individual `ushort` values (uint16)
// * an unsigned `ulong` value (uint64)
// * a dot-string notation ("major.minor.build.revision")
public struct PackageVersion
{
public ushort Major;
public ushort Minor;
public ushort Build;
public ushort Revision;
// Create an instance with the value `major.0.0.0`.
public PackageVersion(ushort major);
// Create an instance with the value `major.minor.0.0`.
public PackageVersion(ushort major, ushort minor);
// Create an instance with the value `major.minor.build.0`.
public PackageVersion(ushort major, ushort minor, ushort build);
// Create an instance with the value `major.minor.build.revision`.
public PackageVersion(ushort major, ushort minor, ushort build, ushort revision);
// Create an instance from a version as a uint64.
public PackageVersion(ulong version);
// Return the version as a uint64.
public ulong ToVersion();
// Return the string as a formatted value "major.minor.build.revision".
public override string ToString();
};
// The Windows App SDK bootstrap initialization API.
public class Bootstrap
{
/// Options for Bootstrap initialization APIs.
public enum InitializeOptions : int
{
/// Default behavior
None = 0,
/// If not successful call DebugBreak()
OnError_DebugBreak = 0x0001,
/// If not successful call DebugBreak() if a debugger is attached to the process
OnError_DebugBreak_IfDebuggerAttached = 0x0002,
/// If not successful perform a fail-fast
OnError_FailFast = 0x0004,
/// If a compatible Windows App Runtime framework package is not found show UI
OnNoMatch_ShowUI = 0x0008,
/// Do nothing (do not error) if the process has package identity
OnPackageIdentity_NOOP = 0x0010,
};
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `Initialize(majorMinorVersion, null, new PackageVersion(), InitializeOptions.None)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @see Initialize(uint, string)
/// @see Initialize(uint, string, PackageVersion)
/// @see Initialize(uint, string, PackageVersion, InitializeOptions)
/// @see Shutdown()
public static void Initialize(uint majorMinorVersion);
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `Initialize(majorMinorVersion, versionTag, new PackageVersion(), InitializeOptions.None)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @see Initialize(uint)
/// @see Initialize(uint, string, PackageVersion)
/// @see Initialize(uint, string, PackageVersion, InitializeOptions)
/// @see Shutdown()
public static void Initialize(uint majorMinorVersion, string versionTag);
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `Initialize(majorMinorVersion, versionTag, minVersion, InitializeOptions.None)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use.
/// @see Initialize(uint)
/// @see Initialize(uint, string)
/// @see Initialize(uint, string, PackageVersion, InitializeOptions)
/// @see Shutdown()
public static void Initialize(uint majorMinorVersion, string versionTag, PackageVersion minVersion);
/// Initialize the calling process to use Windows App SDK's framework package.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use.
/// @param options optional behavior.
/// @see Initialize(uint)
/// @see Initialize(uint, string)
/// @see Initialize(uint, string, PackageVersion)
/// @see Shutdown()
public static void Initialize(uint majorMinorVersion, string versionTag, PackageVersion minVersion, InitializeOptions options);
/// Initialize the calling process to use Windows App SDK's framework package.
/// Failure returns false with the failure HRESULT in the hresult parameter.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `TryInitialize(majorMinorVersion, null, new PackageVersion(), InitializeOptions.None, hresult)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @retval true if successful, otherwise false is returned.
/// @see TryInitialize(uint, string, out int)
/// @see TryInitialize(uint, string, PackageVersion, InitializeOptions, out int)
/// @see Shutdown()
public static bool TryInitialize(uint majorMinorVersion, out int hresult);
/// Initialize the calling process to use Windows App SDK's framework package.
/// Failure returns false with the failure HRESULT in the hresult parameter.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `TryInitialize(majorMinorVersion, versionTag, new PackageVersion(), InitializeOptions.None, hresult)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @retval true if successful, otherwise false is returned.
/// @see TryInitialize(uint, out int)
/// @see TryInitialize(uint, string, PackageVersion, out int)
/// @see TryInitialize(uint, string, PackageVersion, InitializeOptions, out int)
/// @see Shutdown()
public static bool TryInitialize(uint majorMinorVersion, string versionTag, out int hresult);
/// Initialize the calling process to use Windows App SDK's framework package.
/// Failure returns false with the failure HRESULT in the hresult parameter.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// This is equivalent to `TryInitialize(majorMinorVersion, versionTag, minVersion, InitializeOptions.None, hresult)`.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use.
/// @param options optional behavior.
/// @param hresult the error code if an error occurred.
/// @retval true if successful, otherwise false is returned.
/// @see TryInitialize(uint, out int)
/// @see TryInitialize(uint, string, out int)
/// @see TryInitialize(uint, string, PackageVersion, out int)
/// @see Shutdown()
public static bool TryInitialize(uint majorMinorVersion, string versionTag, PackageVersion minVersion, out int hresult);
/// Initialize the calling process to use Windows App SDK's framework package.
/// Failure returns false with the failure HRESULT in the hresult parameter.
///
/// Find a Windows App SDK framework package meeting the criteria and make it available
/// for use by the current process. If multiple packages meet the criteria the best
/// candidate is selected.
///
/// @param majorMinorVersion major and minor version of Windows App SDK's framework package, encoded as `0xMMMMNNNN` where M=Major, N=Minor (e.g. 1.2 == 0x00010002).
/// @param versionTag version tag (if any), e.g. "preview1".
/// @param minVersion the minimum version to use.
/// @param options optional behavior.
/// @param hresult the error code if an error occurred.
/// @retval true if successful, otherwise false is returned.
/// @see TryInitialize(uint, out int)
/// @see TryInitialize(uint, string, out int)
/// @see TryInitialize(uint, string, PackageVersion, out int)
/// @see Shutdown()
public static bool TryInitialize(uint majorMinorVersion, string versionTag, PackageVersion minVersion, InitializeOptions options, out int hresult);
/// Undo the changes made by Initialize().
///
/// @warning Packages made available via `Initialize()` and
/// the Dynamic Dependencies API should not be used after this call.
/// @see Initialize(uint)
/// @see Initialize(uint, string)
/// @see Initialize(uint, string, PackageVersion)
/// @see Initialize(uint, string, PackageVersion, InitializeOptions options)
/// @see TryInitialize(uint, out int)
/// @see TryInitialize(uint, string, out int)
/// @see TryInitialize(uint, string, PackageVersion, out int)
/// @see TryInitialize(uint, string, PackageVersion, InitializeOptions options, out int)
public static void Shutdown();
}
}
namespace Microsoft.Windows.ApplicationModel.DynamicDependency
{
[contractversion(2)]
apicontract DynamicDependencyContract{};
/// CPU architectures to optionally filter available packages against a package dependency.
/// These generally correspond to processor architecture types supported by MSIX.
/// @see Windows.System.ProcessorArchitecture
[flags]
[contract(DynamicDependencyContract, 1)]
enum PackageDependencyProcessorArchitectures
{
None = 0,
Neutral = 0x00000001,
X86 = 0x00000002,
X64 = 0x00000004,
Arm = 0x00000008,
Arm64 = 0x00000010,
X86OnArm64 = 0x00000020,
};
[contract(DynamicDependencyContract, 1)]
enum PackageDependencyLifetimeArtifactKind
{
/// The current process is the lifetime artifact. The package dependency
/// is implicitly deleted when the process terminates.
Process,
/// The lifetime artifact is an absolute filename or path.
/// The package dependency is implicitly deleted when this is deleted.
FilePath,
/// The lifetime artifact is a registry key in the format
/// 'root\\subkey' where root is one of the following: HKLM, HKCU, HKCR, HKU.
/// The package dependency is implicitly deleted when this is deleted.
RegistryKey,
};
/// Options when 'pinning' a package dependency
[contract(DynamicDependencyContract, 1)]
runtimeclass CreatePackageDependencyOptions
{
CreatePackageDependencyOptions();
/// Optional filtering by cpu architecture(s)
PackageDependencyProcessorArchitectures Architectures;
/// Do not verify at least 1 matching package exists when pinning a package dependency
/// @note Default value is `true`
Boolean VerifyDependencyResolution;
/// The kind of lifetime artifact for this package dependency.
PackageDependencyLifetimeArtifactKind LifetimeArtifactKind;
/// The lifetime artifact when pinning a package dependency. The value depends on the LifetimeArtifactKind value.
String LifetimeArtifact;
};
[contract(DynamicDependencyContract, 1)]
static runtimeclass PackageDependencyRank
{
/// The default value is zero (0).
static Int32 Default { get; };
};
/// Options when adding a package dependency
[contract(DynamicDependencyContract, 1)]
runtimeclass AddPackageDependencyOptions
{
AddPackageDependencyOptions();
/// The rank when adding the package dependency to a a package graph.
/// @note A package graph is sorted in ascending order from -infinity...0...+infinity
/// @note The default value is PackageDependencyRank.Default
Int32 Rank;
/// If a package dependency is added to a package graph with a package of the same rank (aka a collision on rank)
/// and this option is true the resolved package dependency is prepended to the set of packages of the same rank.
/// By default resolved package dependencies are appended to the set of packages with the same rank.
Boolean PrependIfRankCollision;
};
[contract(DynamicDependencyContract, 1)]
runtimeclass PackageDependency
{
/// Create an intstance of the package dependency identified by id defined for the current user.
///
/// @return null if the package dependency cannot be found for the user.
///
/// @see Create(String, PackageVersion)
/// @see Create(String, PackageVersion, CreatePackageDependencyOptions)
/// @see GetFromIdForSystem()
static PackageDependency GetFromId(String id);
/// Create an intstance of the package dependency identified by id defined for the system.
///
/// @return null if the package dependency cannot be found for the system.
///
/// @see CreateForSystem()
/// @see GetFromId()
static PackageDependency GetFromIdForSystem(String id);
/// Return the package dependency id.
String Id { get; };
/// Define a package dependency for the current user. The criteria for a PackageDependency
/// (package family name, minimum version, etc) may match multiple
/// packages, but ensures Deployment won't remove a package if it's
/// the only one satisfying the PackageDependency.
///
/// @note A package matching a PackageDependency pin can still be removed
/// as long as there's another package that satisfies the PackageDependency.
/// For example, if Fwk-v1 is installed and a PackageDependency specifies
/// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove
/// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1
/// is removed Deployment won't remove Fwk-v2 because it's the only package
/// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other
/// package matching the PackageDependency) are 'loosely pinned'. Deployment
/// guarantees it won't remove a package if it would make a PackageDependency
/// unsatisfied.
///
/// A PackageDependency specifies criteria (package family, minimum version, etc)
/// and not a specific package. Deployment reserves the right to use a different
/// package (e.g. higher version) to satisfy the PackageDependency if/when
/// one becomes available.
///
/// @param packageFamilyName the package family to pin
/// @param minVersion the minimum version to pin
///
/// @note This fails if the package dependency cannot be resolved to a specific package.
///
/// @see Create(String, PackageVersion, CreatePackageDependencyOptions)
/// @see CreateForSystem()
static PackageDependency Create(
String packageFamilyName,
Windows.ApplicationModel.PackageVersion minVersion);
/// Define a package dependency for the current user. The criteria for a PackageDependency
/// (package family name, minimum version, etc) may match multiple
/// packages, but ensures Deployment won't remove a package if it's
/// the only one satisfying the PackageDependency.
///
/// @note A package matching a PackageDependency pin can still be removed
/// as long as there's another package that satisfies the PackageDependency.
/// For example, if Fwk-v1 is installed and a PackageDependency specifies
/// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove
/// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1
/// is removed Deployment won't remove Fwk-v2 because it's the only package
/// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other
/// package matching the PackageDependency) are 'loosely pinned'. Deployment
/// guarantees it won't remove a package if it would make a PackageDependency
/// unsatisfied.
///
/// A PackageDependency specifies criteria (package family, minimum version, etc)
/// and not a specific package. Deployment reserves the right to use a different
/// package (e.g. higher version) to satisfy the PackageDependency if/when
/// one becomes available.
///
/// @param packageFamilyName the package family to pin
/// @param minVersion the minimum version to pin
/// @param options additional options affecting the package dependency
///
/// @note This fails if the package dependency cannot be resolved to a specific package (null is returned).
/// This package resolution check is skipped if MddCreatePackageDependencyOptions.VerifyDependencyResolution=false
/// is specified. This is useful if a package satisfying the dependency
/// will be installed after the package dependency is defined.
///
/// @see Create(String, PackageVersion)
/// @see CreateForSystem()
static PackageDependency Create(
String packageFamilyName,
Windows.ApplicationModel.PackageVersion minVersion,
CreatePackageDependencyOptions options);
/// Define a package dependency for the system (i.e. all users). The criteria for a PackageDependency
/// (package family name, minimum version, etc) may match multiple
/// packages, but ensures Deployment won't remove a package if it's
/// the only one satisfying the PackageDependency.
///
/// @note A package matching a PackageDependency pin can still be removed
/// as long as there's another package that satisfies the PackageDependency.
/// For example, if Fwk-v1 is installed and a PackageDependency specifies
/// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove
/// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1
/// is removed Deployment won't remove Fwk-v2 because it's the only package
/// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other
/// package matching the PackageDependency) are 'loosely pinned'. Deployment
/// guarantees it won't remove a package if it would make a PackageDependency
/// unsatisfied.
///
/// A PackageDependency specifies criteria (package family, minimum version, etc)
/// and not a specific package. Deployment reserves the right to use a different
/// package (e.g. higher version) to satisfy the PackageDependency if/when
/// one becomes available.
///
/// @param user the user scope of the package dependency. If null the caller's user context is used
/// @param packageFamilyName the package family to pin
/// @param minVersion the minimum version to pin
/// @param options additional options affecting the package dependency
///
/// @note This fails if the package dependency cannot be resolved to a specific package.
/// This package resolution check is skipped if MddCreatePackageDependencyOptions.VerifyDependencyResolution=false
/// is specified. This is useful for installers pinning a package dependency for all users on a system.
///
/// @see Create(String, PackageVersion)
/// @see Create(String, PackageVersion, CreatePackageDependencyOptions)
static PackageDependency CreateForSystem(
String packageFamilyName,
Windows.ApplicationModel.PackageVersion minVersion,
CreatePackageDependencyOptions options);
/// Delete a defined package dependency.
/// @note The package depenency id useless after Delete. The property is valid but attempting to use it fails e.g. PackageDependency.GetFromId(id) returns null.
void Delete();
/// Resolve a previously pinned PackageDependency to a specific package and
/// add it to the calling process' package graph. Once the dependency has
/// been added other code-loading methods (LoadLibrary, CoCreateInstance, etc)
/// can find the binaries in the resolved package.
///
/// Package resolution is specific to a user. The same package dependency can
/// resolve to different packages for different users on a system.
///
/// This adds the resolved package to the process' package graph.
/// A process' package graph is used to search for DLLs (per Dynamic-Link Library Search Order),
/// WinRT objects and other resources; the caller can now load DLLs, activate
/// WinRT objects and use other resources from the framework package until
/// PackageDependencyContext.Remove() is called (or the process ends).
/// The package dependency Id must match a package dependency defined
/// for the calling user or the system (via CreateForSystem) or an exception is raised.
///
/// Each successful call adds the resolved packaged to the
/// calling process' package graph, even if already present. There is no
/// duplicate 'detection' or 'filtering' applied by the API (multiple
/// references to a package is not harmful). Once resolution is complete
/// the package stays resolved for that user until the last reference across
/// all processes for that user is removed via PackageDependencyContext.Remove()
/// (or process termination).
///
/// Calls to Add() can be balanced by a PackageDependencyContext.Remove()
/// to remove the entry from the package graph.
///
/// Successful calls change the package graph's current revision id.
///
/// @see PackageGraphRevisionId
PackageDependencyContext Add();
/// Resolve a previously pinned PackageDependency to a specific package and
/// add it to the calling process' package graph. Once the dependency has
/// been added other code-loading methods (LoadLibrary, CoCreateInstance, etc)
/// can find the binaries in the resolved package.
///
/// Package resolution is specific to a user. The same package dependency can
/// resolve to different packages for different users on a system.
///
/// This adds the resolved package to the process' package graph.
/// A process' package graph is used to search for DLLs (per Dynamic-Link Library Search Order),
/// WinRT objects and other resources; the caller can now load DLLs, activate
/// WinRT objects and use other resources from the framework package until
/// PackageDependencyContext.Remove() is called (or the process ends).
/// The package dependency Id must match a package dependency defined
/// for the calling user or the system (via CreateForSystem) or an exception is raised.
///
/// Each successful call adds the resolved packaged to the
/// calling process' package graph, even if already present. There is no
/// duplicate 'detection' or 'filtering' applied by the API (multiple
/// references to a package is not harmful). Once resolution is complete
/// the package stays resolved for that user until the last reference across
/// all processes for that user is removed via PackageDependencyContext.Remove()
/// (or process termination).
///
/// This adds the resolved package to the caller's package graph, per rank.
/// A process' package graph is a list of packages sorted by rank in ascending
/// order (-infinity...0...+infinity). If package(s) are present in the
/// package graph with the same rank the resolved package is
/// (by default) added after others of the same rank. To add a package
/// before others of the same rank, specify PackageDependency.PrependIfRankCollision.
///
/// Calls to Add() can be balanced by a PackageDependencyContext.Remove() (or object destruction)
/// to remove the entry from the package graph.
///
/// Successful calls change the package graph's current revision id.
///
/// @see PackageGraphRevisionId
PackageDependencyContext Add(AddPackageDependencyOptions options);
/// Return the package graph's current revision id.
[contract(DynamicDependencyContract, 2)]
static UInt32 PackageGraphRevisionId{ get; };
/// Return the package graph's current revision id.
///
/// @note This API is deprecated and will be removed in a future release.
/// Use the PackageGraphRevisionId property.
static UInt32 GenerationId{ get; };
};
/// A unique identifier for a resolved package dependency
[contract(DynamicDependencyContract, 1)]
struct PackageDependencyContextId
{
UInt64 Id;
};
/// This object provides access to information about a package dependency context.
/// The resolved package dependency is removed from the caller's package graph via
/// .Remove() or when the object is destroyed.
///
/// Calling .Remove() or destroying this object is the moral equivalent of Windows' AddDllDirectory().
///
/// @note This does not unload loaded resources (DLLs etc). After removing
/// a package dependency any files loaded from the package can continue
/// to be used; future package dependency resolution (via new calls to
/// PackageDependency.Add) will fail to see the removed package dependency.
[contract(DynamicDependencyContract, 1)]
runtimeclass PackageDependencyContext
{
/// Create an intstance of the package dependency context identified by context
PackageDependencyContext(PackageDependencyContextId contextId);
/// Returns the package dependency context id
PackageDependencyContextId ContextId { get; };
/// Return the package dependency id.
String PackageDependencyId{ get; };
/// Returns the package full name of the resolved package for this context
String PackageFullName { get; };
/// Remove from the package graph a package dependency previously added via PackageDependency.Add().
///
/// Successful calls change the package graph's current revision id.
///
/// @see PackageDependency.RevisionId
void Remove();
};
}
Windows resolves package dependencies at install-time to compute a package graph. This is flattened into an strictly ordered list for use at runtime. This list is formally called a 'package graph'. It's also been referred to as a 'package dependency graph', 'dependency graph', 'resolve package dependencies' or simply the 'static package graph'.
Packages are strictly ordered in a package graph. The ordering rules are designed to meet these base principles:
- Deterministc - Package graph ordering is stable and reproducible. Given the same set of packages the same package graph is produced. There are no timing or order-of-install factors impacting this.
- Knowable - Package graph ordering is comprehensible and intuitive. You don't have to be an expert to understand what package graph you'll get.
Package graphs are computed per the following rules, as of Windows 10.0.19041.0 (aka Windows 10 May 2020 Update).
Packages are included in a Main package's package graph for any of the following reasons:
- Main package
- Optional packages associated with the Main package, per
<uap3:MainPackageDependency>
- Framework packages, per
<PackageDependency>
- HostRuntime providers, per
<uap10:HostRuntimeDependency>
- Resource packages, in the same package family as any other package in the package graph and selected per Windows' resource package applicability rules
Packages in a package graph are grouped into 'bands' per the following criteria:
- Main
- Optional
- Framework and HostRuntime
- Resource
Each 'band' may have additional intra-band ordering rules:
- Main - No additional rules
- Optional - Sorted alphabetically by package name
- Frameworks/HostRuntimes - Ordered byr physical order of
<PackageDependency>
in AppxManifest.xml - Resources - Sorted alphabetically by PackageFamilyName+ResourceId
A package appears once in a package graph. When building the package graph Windows uses "Add if not already present" (aka de-dupe'ing) semantics e.g.
packagegraph = []
...
IF package NOT IN packagegraph
packagegraph.Append(package)
Q: Why are Optional packages after the Main package, w/o Framework or Resource packages between?
A: Optional packages are conceptually a Main package chopped until into a
smaller Main and 1+ Optional package.
Q: Why are Optional packages sorted alphabetically by PackageFamilyName + ResourceId
?
A: Some, all or no Optional packages may be installed for a user.
The developer has no ability to know in advance, reason over or directly
influence which Optional packages may be installed at a given time for a given user.
Alphabetical sort by PackageFamilyName + ResourceId
is deterministic and knowable.
Q: Why are Frameworks ordered per physical order in AppxManifest.xml?
A: Some subsystems sequentially search across the package graph to find
package content. Most notably, the Loader (DLLs), .NET Framework's CLR Binder
(assemblies) and WinRT (type resolution and inproc DLL's import
resolution) use the package graph with FirstWinner search order semantics.
Developers MUST be able to influence and control the order of Framework
packages to avoid inappropriate collision resolution.
Q: Why are HostRuntimes ordered per physical order in AppxManifest.xml
?
A: HostRuntimes perform a role similar to Framework packages thus facing
similar requirements and constraints.
Q: Why are HostRuntimes in the same 'band' as Framework packages?
A: HostRuntime packages function equivalent to framework packages.
Q: Why are Resource packages sorted alphabetically by PackageFamilyName + ResourceId
?
A: Resource packages are installed based on applicability rules and
machine+user state (scale, languages selected, etc). The developer has no
ability to know in advance, reason over or directly influence which Resource
packages may be installed at a given time for a given user. Alphabetical sort by
PackageFamilyName + ResourceId
is deterministic and knowable.
Q: Why are Framework packages before Resource packages?
A: Framework order can be influenced by the developer; Resource order
cannot. Placing Frameworks before Resource packages ensures the developer
has control if/when needed.
Q: How do we traverse dependencies?
A: Breadth-First Search (BFS) in a package before moving on to the next
(then spread across the 'bands' as appropriate). Do NOT do
a Depth-First Search (DFS) as this will violate several rules and base principles.
Q: How about an example?
A: Given package=[dependencies]
Main = [ H1, F1 ]
Opt = [ H2, F2 ]
H1 = [ F3 ]
H2 = [ F4 ]
where
Main = Main package
Opt = Optional package
H* = Main package providing a HostRuntime
F* = Framework package
the package graph will be
[Main, Opt, H1, F1, H2, F2, F3, F4]