Windows Activation Contexts: Side-by-Side Manifests and Scoped Activation
Use Windows activation contexts to scope manifest-based side-by-side binding, pair cookies in stack order, and release references without crossing threads.
An activation context is Windows’ runtime view of manifest-declared side-by-side assembly bindings. A manifest can describe dependencies on versioned DLLs, COM servers, window classes, type libraries, files, and other activation-context sections. When an appropriate Win32 API resolves one of those names, the active context lets Windows select the version named by the manifest rather than relying on one process-wide unversioned choice.
Most applications do not need to manage activation contexts directly: the system normally establishes the application’s context from its manifest. Explicit calls are useful for a dispatch layer that must make a particular call under a component-specific manifest. The API is a scoped stack operation associated with the calling thread, not a global switch for the process. Treat each activation cookie as a stack token that must be popped in reverse order.
A manifest describes bindings; it is not a runtime switch by itself
An assembly manifest identifies an assembly and can declare dependencies. A common-controls dependency, for example, names the versioned Microsoft.Windows.Common-Controls side-by-side assembly. The manifest is input to activation-context construction; writing a dependency into XML does not by itself prove that a later call ran with that context active.
Use CreateActCtxW with an ACTCTXW structure describing a specific manifest source. Set cbSize to sizeof(ACTCTXW), zero-initialize the other fields, and set lpSource to the manifest file you intend to test. On success, the function returns a reference-counted activation-context handle. On failure, it returns INVALID_HANDLE_VALUE; capture GetLastError() immediately, before cleanup calls can replace it.
ACTCTXW spec = {0};
spec.cbSize = sizeof(spec);
spec.lpSource = L"C:\\actctx-lab\\fixture.manifest";
HANDLE context = CreateActCtxW(&spec);
if (context == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
/* Report the manifest-construction failure. */
return error;
}
The application manifest and a dynamically created activation context are separate inputs to the process’s activation stack. Do not assume that constructing a context activates it or replaces the default application context. Conversely, do not manually activate a context around ordinary code if the executable’s manifest and the system’s automatic context management already provide the needed binding. Microsoft describes direct stack management as an uncommon application responsibility, most relevant to indirection or dispatch layers.
Activate for one synchronous scope and save the cookie
ActivateActCtx pushes the supplied context onto the activation stack of the current thread and returns a cookie that identifies that activation. The top item is the active context for appropriate side-by-side API calls on that thread. The cookie is not a handle to close; it is the pairing value for DeactivateActCtx.
ULONG_PTR cookie = 0;
if (!ActivateActCtx(context, &cookie)) {
DWORD error = GetLastError();
ReleaseActCtx(context);
return error;
}
/* Perform only the manifest-sensitive operation here, on this thread. */
if (!DeactivateActCtx(0, cookie)) {
DWORD error = GetLastError();
/* Preserve the error, release our reference, and retire this thread. */
ReleaseActCtx(context);
return (int)error;
}
ReleaseActCtx(context);
The example is intentionally lexical: it activates one context and deactivates it before leaving the scope. For nested contexts, deactivate the most recently activated one first, then unwind outward. Microsoft documents the activation stack as LIFO; with dwFlags set to zero, the cookie must be on top. If it is lower on the stack, Windows searches for it and raises a side-by-side early-deactivation exception; if it is absent, Windows can raise an invalid-deactivation exception. The force flag is not a repair for unbalanced code: when the cookie is on top, DeactivateActCtx returns FALSE with ERROR_INVALID_PARAMETER; when the cookie is lower, Windows pops through that frame; and when it is absent, Windows can raise an invalid-deactivation exception.
Wrap the scope in a small C++ RAII guard if exceptions or many early returns are possible, but make that guard noncopyable and keep its activation and deactivation on the same thread. Activation stacks are per-thread, but Windows documents propagation in specific cases: CreateThread puts the current activation context on the new thread’s stack, APCs and completion-port callbacks receive the source context, and documented callbacks on other threads are cleaned up when the callback returns. This does not promise inheritance for every arbitrary work queue or handoff. Manual ActivateActCtx changes only the current thread’s stack; when code explicitly activates another context in a callback or work item, pair it within that execution scope and unwind before it returns.
Keep activation references distinct from ordinary handles
CreateActCtxW gives the caller one reference. Activation and deactivation manage references for the stack, while ReleaseActCtx releases the caller-owned reference when it is no longer needed. A handle returned by GetCurrentActCtx also carries a release obligation. Activation-context handles are not kernel objects: do not call CloseHandle, LocalFree, or heap functions on them.
The cleanup order is therefore significant:
- Create the context and retain the returned handle.
- Activate it on the thread that will make the sensitive call; retain the cookie.
- Complete the operation, including any synchronous work that depends on the binding.
- Deactivate using that cookie, after inner contexts have already been deactivated.
- Release the caller’s context reference.
If a function returns while a context remains pushed, later unrelated code on that thread may resolve names under the wrong binding. A leaked reference can keep the activation context allocated, but it does not make an incorrect stack lifetime safe. If DeactivateActCtx returns FALSE, capture and preserve GetLastError() before cleanup, release the caller-owned reference, and fail closed: report the thread’s activation state as unusable and retire that thread before it dispatches more work. This return-value path is distinct from a stack-cookie mismatch. With dwFlags set to zero, a cookie below the top of the stack or a cookie that is absent can raise the documented side-by-side deactivation exceptions instead of returning FALSE; the lexical sample assumes its own valid, top-of-stack cookie and does not handle those exception cases. Code that accepts less-controlled activation scopes must define an appropriate structured-exception boundary and thread-retirement policy rather than assuming GetLastError() will report a mismatch. Do not continue on the assumption that the stack was restored. The sample below exits immediately after an API-reported deactivation failure for that reason.
Test with a disposable local manifest
The fixture below is a minimal external manifest that declares a Common Controls dependency. Save it only in a disposable test directory on Windows as fixture.manifest; do not replace the embedded or external manifest of a production application to test this example.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*"
name="Batch5.ActivationContextFixture" type="win32" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*" />
</dependentAssembly>
</dependency>
</assembly>
Build a small console test that calls CreateActCtxW, activates the returned context, checks GetCurrentActCtx on that same thread, releases the additional handle returned by that query, deactivates with the saved cookie, and finally calls ReleaseActCtx on the original reference. Keep error capture adjacent to each failed API call. The test demonstrates construction and balanced stack cleanup; to validate actual assembly or window-class redirection, add a test operation whose documented binding is described by the fixture manifest.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
int
main(void)
{
ACTCTXW spec = {0};
HANDLE context;
HANDLE current = NULL;
ULONG_PTR cookie = 0;
int result = 0;
spec.cbSize = sizeof(spec);
spec.lpSource = L"C:\\actctx-lab\\fixture.manifest";
context = CreateActCtxW(&spec);
if (context == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
fprintf(stderr, "CreateActCtxW failed: %lu\n",
(unsigned long)error);
return 1;
}
if (!ActivateActCtx(context, &cookie)) {
DWORD error = GetLastError();
fprintf(stderr, "ActivateActCtx failed: %lu\n",
(unsigned long)error);
ReleaseActCtx(context);
return 1;
}
if (!GetCurrentActCtx(¤t)) {
DWORD error = GetLastError();
fprintf(stderr, "GetCurrentActCtx failed: %lu\n",
(unsigned long)error);
result = 1;
} else if (current == NULL) {
fprintf(stderr, "no activation context is active on this thread\n");
result = 1;
}
if (current != NULL)
ReleaseActCtx(current);
/* This test removes its own valid, top-of-stack activation cookie. */
if (!DeactivateActCtx(0, cookie)) {
DWORD error = GetLastError();
fprintf(stderr, "DeactivateActCtx failed: %lu\n",
(unsigned long)error);
result = 1;
}
ReleaseActCtx(context);
if (result == 0)
puts("PASS: activation observed and stack entry removed");
return result;
}
Run this test as an ordinary process under the account that owns the test directory. It does not need to edit the registry, install an assembly, change the executable manifest, or elevate privileges. Delete only the temporary fixture and test outputs after the process has exited. A native Windows run is required to prove the OS resolves the dependency as intended; compiling the sample on a non-Windows host checks only declarations and syntax.
Diagnose the boundary instead of broadening it
If CreateActCtxW fails, first preserve the immediate error code and verify cbSize, the manifest path, XML structure, assembly identity fields, and whether the referenced side-by-side assembly is available. If creation succeeds but a call resolves the wrong object, ask which thread made the call and what context was on top at that moment. GetCurrentActCtx returns the active context for the calling thread and adds a reference that the caller must release; it can help diagnose the stack without changing it.
If nested code owns another activation context, let that code restore its own cookie before deactivating yours. Avoid a forced early pop: it can remove frames owned by other components, leaving their later cleanup calls mismatched. Keep ownership explicit at API boundaries instead of storing one ambient context in process-global state.
Related:
- CreateProcessW Command Lines: Executable Resolution and Argument Boundaries
- COM Apartments on Windows: STA, MTA, Marshaling, and Message Pumps
Sources:
- Microsoft Learn: Activation Contexts
- Microsoft Learn: Using the Activation Context API
- Microsoft Learn:
CreateActCtxW - Microsoft Learn:
ActivateActCtx - Microsoft Learn:
DeactivateActCtx - Microsoft Learn:
ReleaseActCtxand activation-context references - Microsoft Learn: Reference counting activation contexts
- Microsoft Learn:
GetCurrentActCtx - Microsoft Learn: Application manifests
- Microsoft Learn: Threads, asynchronous procedures, and window messages