I created this blog in order to post this article about WinSxS. It is based on my own reading of the docs and some experimentation. It does not represent the views of Microsoft or my employer.
WinSxS is not as new as it seems (apparently it dates back to Windows ME), nor is it as undocumented as it seems. However, the documentation, which can be found in MSDN under Isolated Applications and Side-by-side Assemblies, is big, piecemeal and confusing. To get a decent understanding of the technology, you have to read all of it and then try and fit the pieces back together like a jigsaw. Having done that, I’m going to try and explain it in what I hope is a more digestible way. The article is aimed at programmers rather than sysadmins, but could be useful for either.
I have tried to be as accurate as possible, and I have done some testing to clarify some of the details. However, I’m sure there are many inaccuracies. I’d be grateful if people could use the comments to set me right on these.
WinSxS and .NET
The first thing to get straight is the relationship between WinSxS and .NET assembly binding. Both have “assemblies”, “assembly identities” and “manifests”, and they resemble each other in many other ways: WinSxS has the system assembly cache (winsxs folder), .NET has the GAC; WinSxS has Activation Contexts, .NET has AppDomains; WinSxS has “publisher configuration files”, .NET has “publisher policy files”, etc, etc.
These resemblances are just that: resemblances. Although the two mechanisms are deliberately similar, and WinSxS attempts to provide similar deployment options for unmanaged code as are available for managed code, they are completely distinct. It’s probably best to put .NET out of your mind for the rest of this.
What it’s all for
There are various points in Win32 and COM where it is possible to supply an identifier of some description, which loosely identifies (rather than explicitly locates) an object available somewhere within the system. Some of the particular cases of interest are:
- Using a DLL name (without path) in a call to LoadLibrary (explicit linking)
- Referencing a DLL dependency from the PE header of another DLL or EXE (implicit linking)
- Using a CLSID or ProgId to create an instance of a COM class
- Using a window class name to specify a wndclass
In all of these cases, the identifier is mapped to a specific location for the object via some kind of search. In the first two cases, the mapping is determined by the “Dynamic-Link Library Search Order” (documented in MSDN on the page of that name). CLSIDs and ProgIds are mapped to DLLs via the registry, and window class names are mapped to whatever class has been registered under that name at runtime with ::RegisterClass().
There is a problem with all of the mappings listed above, but it is not that they fail to allow versioning. There are at least three versioning techniques available:
- Firstly, with all of the mechanisms listed above, it is possible to use a different identifier for each version of the object. In all cases except CLSIDs, this can be done by simply including the version number in the identifier. This is a perfectly valid technique that is still used even with WinSxS (“msvcr80.dll, for example). The downside is that every piece of code that requests a piece of information that is versioned in this way must know which version to request at the point the request is made. This often means that changing the version of the requested object involves a recompile for the requestor. If the object can change in a backwards-compatible way (eg. for bugfixes), it can be desirable to relax this requirement.
- The second approach is to use a single identifier for multiple compatible versions of the object, and ensure that only one of these versions is available at runtime. For example, you could have multiple compatible versions of MyDLL50.dll but only install one on a machine. One limitation of this technique is that if a piece of requesting code really does want to get a specific version of the object, it cannot do so. The requestor can only use the single available version of the object.
- The third approach only works for COM ProgIds. Here it is possible to register a set of versioned ProgIds, together with an unversioned ProgId which is mapped in the registry to one of the versioned ProgIds. In this way, calling code can choose whether to ask for a specific version of the dependency or ask for the default version. (This is similar in principle to the UNIX approach for libraries, where progressively less versioned names are mapped to versioned names via symlinks.)
A crucial problem with all of these techniques is that as soon as you relax the requirement that all requestors must specify the exact versions of dependencies when requesting them (as in the first technique), the various applications on a machine that depend on a given object are no longer isolated from each other. The mapping between unversioned identifier and versioned object, whether it is achieved by placing a particular DLL version on the path, registering a particular version of a COM DLL, or whatever, is system-wide. Changing it will affect all applications that depend on the object. There is no way of saying “This app/component should use version X of the object, but this app/component should use version Y”.
This isolation problem (rather than any other interpretation of “DLL Hell”) is what WinSxS is primarily designed to solve. It adds features around internationalisation and security, and the underlying mechanism will inevitably be used for other purposes (apparently it’s used for “User Account Control” in Vista, for a start), but the core purpose of the mechanism is to allow multiple applications to be installed on a single machine in such a way that their shared dependencies do not become points of interaction.
Assemblies and Manifests
A WinSxS “assembly” is a collection of resources, such as DLLs, COM classes and window classes, together with a manifest. The manifest gives the assembly an “assembly identity”, which is similar to a .NET strong name. The assembly identity includes a type (currently always “win32”), a name and some version information. It can also optionally include language and processor architecture and a public key token. The public key token is used for assembly signing, as in .NET.
The MSDN documentation distinguishes between “isolated applications” and “side-by-side assemblies”. It also further divides “side-by-side assemblies” into “private assemblies” and “shared assemblies”. These terms are useful when discussing the layout of deployments, but when explaining the runtime behaviour of the system I think they just get in the way. I’ll therefore return to them later once I’ve explained how WinSxS works at run time. For now I will just use the word “assembly” to mean “something with a manifest”, which includes all of these terms.
When explaining WinSxS it is not sufficient simply to explain how assemblies are located. The assembly search is only (the easy) half of the story, and without the other half it’s useless.
The problem is that the code that calls out for an object at run time does not specify which assembly it lives in. For example, you don’t link a VC8 application against
<assemblyIdentity type=”win32″ name=”Microsoft.VC80.CRT” version=”8.0.50727.762″ processorArchitecture=”x86″ publicKeyToken=”1fc8b3b9a1e18e3b”></assemblyIdentity>
You link it against “msvcr80.dll”!
Clearly in order to load the correct VC8 runtime DLL via WinSxS, the system has to map a DLL name, “msvcr80.dll”, to a DLL location. Knowing how an assembly name is mapped to an assembly location won’t help you understand this. The missing concept is the “activation context”.
An activation context is an object which the system can use to map an unversioned name (eg. “msvcrt80.dll”) to a structure that provides enough information to actually instantiate the object (eg. “the target DLL is C:WINNTWinSxSx86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.762_x-ww_6b128700msvcrt80.dll”).
Creating Activation Contexts
Activation contexts can be created with the Win32 API function ::CreateActCtx(). This accepts an ACTCTX structure, which specifies, amongst other things, the manifest file to use to create the activation context. This can be either a plain manifest file or a PE file (EXE or DLL) together with a resource ID under which to look for the manifest.
Creating an activation context in this way causes the supplied manifest file to be read, and each of the resources (DLLs, COM objects, etc) to be added to the activation context. The manifests for the dependent assemblies listed in the manifest are read recursively and their contents are also added to the activation context. The way dependencies are located is documented in MSDN under Assembly Searching Sequence (see ACTCTX for info on how the “application’s directory” is specified). This is where the mapping of assembly names to assembly locations is important. This logic is used for populating activation contexts – it is not (or is only indirectly) used for resolving unversioned object names.
Note that it is at the point of activation context creation that uniqueness is checked. Each unversioned object name must be unique within an activation context, as otherwise it would be impossible to unambiguously resolve requests for the object. For example, the VC8 runtime assembly can be referenced twice in the same activation context without any trouble, but if two different assemblies referenced in a single activation context were to contain DLLs called “msvcr80.dll”, then ::CreateActCtx would fail (in practice, such problems tend to lead to DLL load failures and messages in the event log along the lines of “generate activation context failed”).
As far as I can see, this is the only way to manually create or populate an activation context. I can’t see any way of creating one without specifying a manifest (say by manually adding the unversioned->versioned mappings). Nor can I see any way of amending an activation context once it has been created (say by adding a new assembly dependency, or by merging two activation contexts).
Activating Activation Contexts
Once an activation context has been created, it is not automatically used for anything. First, it has to be activated. The active activation context is the one that is used to resolve any calls to LoadLibrary, SearchPath or any other API function or OS facility that is affected by WinSxS.
Activation contexts are held on a stack. There is one such stack for each thread in a process. The “active” activation context is always the one at the top of the stack. If an activation context is “activated”, it is pushed on to the top of the stack, and is therefore the active context until such a time as it is either deactivated (popped from the stack) or another context is activated (which will temporarily “eclipse” the previously active one).
Activation contexts are activated by calling ::ActivateActCtx and deactivated by calling ::DeactivateActCtx. Activation contexts must be deactivated in reverse order of activation. See the MSDN docs for ::DeactivateActCtx for what happens if you try and deactivate too early. Code anywhere in a process can get hold of the active activation context for the current thread by calling ::GetCurrentActCtx. A thread cannot get or activate an activation context for another different thread.
In some places, the OS will manage the activation context on your behalf. For example, if you make a cross-apartment COM call, the active activation context will be marshalled and activated on the target thread for the duration of the call. Similarly, if you make an asynchronous procedure call (APC), the system will ensure the procedure is called in the activation context of the caller.
In other places, you must manage the activation context yourself. For example, if you build your own thread pool and queue jobs on it, you ought (in principle at least) to ensure that the correct activation context is activated for each job.
Resolving Unversioned Names
When an unversioned identifier is used, for example when calling LoadLibrary() with just a filename (no path), calling SearchPath() or calling CoCreateInstance with a CLSID, the currently active activation context is searched for the identifier. If it is found, the object in the corresponding assembly is used. If it is not found, the search then proceeds as usual (CLSID looked up in registry, DLL searched in path, etc). I can’t see any way of forcing these searches not follow the ordinary search as a fallback, so if you attempt to use an activation context that covers all possible dependencies, it seems you could easily miss one without noticing.
Note that only the active (ie. top of the stack) context is ever used for resolving unversioned names. The rest of the contexts in the stack are dormant and do not contribute in any way until they become active again.
As far as I can tell, this process of giving a DLL name, CLSID, or whatever to a standard API is the only way of loading the resources in an assembly. You cannot load a DLL by providing the assembly identifier. The assembly identifier is only used when locating an assembly for the purposes of populating an activation context. The only programmatic API I can find that takes an assembly identifier as a parameter is CreateActCtx (and that’s only if you set the ACTCTX_FLAG_SOURCE_IS_ASSEMBLYREF flag, which is mentioned in Searching for Assemblies, but not in the actual documentation for CreateActCtx or ACTCTX).
Creation and Activation of Activation Contexts by the System
Naturally, you don’t have to manually create and activate your own activation contexts all the time. As far as it can, the system attempts to manage them on your behalf.
Having said this, there are actually only two places in which the OS itself will create and activate a context on your behalf: during CreateProcess() and while loading a DLL.
When CreateProcess() is called, the OS searches for a manifest associated with the executable file. This can a separate file in the same folder as the EXE, in which case it must have the same name as the EXE (including the extension), followed by “.manifest” – eg. “test.exe.manifest”. Alternatively, it can be an XML document embedded in the EXE’s resources. According to MSDN (Specifying a Default Activation Context), the resource will take precedence over the file. However, this is the other way around on 2003 and Vista (see the note on item 4 in Troubleshooting C/C++ Isolated Applications and Side-by-side Assemblies).
The manifest file found here is used to create and activate an activation context, which is known as the default activation context. If no manifest is found a default activation context called the “system default activation context” is used, but I can’t find any documentation as to what is in it. (According to Junfeng Zhang’s blog, “The system default Activation Context is not interesting. It only exists for backward compatibility reason[s].”)
One very important point about embedding manifests as resources: the resource ID used is meaningful! The possible values are 1, 2 and 3. Their respective meanings are documented in the bizarrely named MSDN article Using Side-by-Side Assemblies as a Resource. For now, the important thing is that if you want your manifest to be used to create the default activation context, it should be embedded as resource id 1. This is the default resource ID used by VC8 for embedding manifests in EXEs. If the manifest is embedded under a different resource ID, CreateProcess() will ignore it (this is column “Manifest specifies the Process Default?” in Using Side-by-Side Assemblies as a Resource).
When loading a DLL for any reason (::LoadLibrary or implicit linking), regardless of whether that DLL has been located by WinSxS or by the ordinary DLL search (or explicitly specified to ::LoadLibrary()), the OS will always look into the resources of the DLL that is being loaded. If it sees a manifest in there under resource ID 1 or 2, it will use the manifest to create an activation context, and will activate that context while it loads the DLL. This means that resolution of any implicit dependencies will take place in the new activation context. However, it is important to note that the OS will deactivate this context once the DLL has been loaded. Any calls made later into the newly loaded DLL will not magically have the right activation context (though see the next section for a way of pretending they do). Also, note that this context is activated after the DLL has been found (otherwise how would it know where to get the manifest from?). This context therefore is not used while searching for the DLL itself. It is only used while resolving the DLL’s dependencies.
I suspect that if you use resource ID 3 for the resource it will be ignored by this mechanism, though I haven’t tested this. This suspicion is just because I can’t find any other possible meaning for the “Use for Static Imports?” column in Using Side-by-Side Assemblies as a Resource. I’m pretty sure this column refers to the mechanism I have just described, although if it does it’s a horrible title for the column. Please correct me if you find out I’m wrong.
Another thing to be aware of with this mechanism is that if you have a manifest deployed as a separate file along with the DLL that is being loaded, it will be ignored! It is possible to have separate manifests for assemblies containing DLLs, but if you do this the manifest will only be used if the assembly is explicitly referenced from another manifest. It will not be magically recognised whenever the DLL is loaded.
Creation and Activation of Activation Contexts by Inline Helpers in the Platform SDK Headers
There is a major hole in the activation context mechanism as described so far. Even if you build all your EXEs and DLLs with manifests in their resources at the required IDs, containing details of the relevant dependencies, you still can’t guarantee that whenever a DLL is loaded (or COM object instantiate, etc) the correct manifest information will be used.
Consider an EXE, implicitly linked against a single DLL dependency. Both have manifests, embedded as resource IDs 1 and 2 respectively. The sequence of actions is as follows:
- When the EXE is run, its manifest will be loaded and used to create the default activation context
- The default activation context will be active while the system searches for the DLL, so the EXE’s manifest information will be used to locate the DLL.
- Once the loader finds the DLL, it will look in its resources, find the manifest, and create and activate an activation context. This activation context will be used to locate any implicitly linked dependencies the DLL may have.
- Once the DLL has been successfully loaded (and DLLMain has been successfully called), the loader deactivates the context, thus restoring the default activation context.
- The EXE’s main() function is called, resulting in a call to the DLL.
- Now we are executing code in the DLL, but the default activation context is still active. Any calls to LoadLibrary, CoCreateInstance, etc, will use the manifest info from the EXE, not the DLL!
In a perfect world, in which there were some kind of all-knowing mechanism that knew what we were executing and managed dependency loading appropriately (cough, .NET, cough), the activation context would magically change when the call enters the DLL. Unfortunately, this is not possible here.
The platform SDK headers provide a solution for this in the form of a preprocessor define, called ISOLATION_AWARE_ENABLED, (which is not defined by default). If this is defined, WinSxS aware functions such as ::LoadLibrary(), ::CoCreateInstance and ::SearchFile() become wrapped in inline helper functions. These helpers look in the resources of the current module for a manifest with resource ID 1, 2 or 3 (column “Uses Side-by-Side version of assemblies if compiled with -DISOLATION_AWARE_ENABLED?” in Using Side-by-Side Assemblies as a Resource). If one is found, a corresponding activation context is created and activated before making the call.
So, in order to solve the problem above, ISOLATION_AWARE_ENABLED would need to be defined for the DLL. This doesn’t mean that the activation context is magically altered whenever code execution enters the DLL. It just means that whenever the DLL calls out to a Win32 API call that uses WinSxS, the call is intercepted and the correct activation context is enabled.
The MSDN article on Specifying a Default Activation Context implies that in this example, enabling ISOLATION_AWARE_ENABLED for the EXE would somehow be a “better way” to get its top-level manifest read than just sticking it in resource ID 1 or an external file and letting the system pick it up. I’m not sure why it is considered better.
The main downside of ISOLATION_AWARE_ENABLED is that it makes it impossible to load a DLL without having your own manifest read and activated. If you are doing your own activation context management, there may be cases where you want to disable this. However, I can’t really see any reason not to just define ISOLATION_AWARE_ENABLED for all modules by default. The activation context that is created is cached, so I expect the overhead of activating the context will be small compared with the cost of the API call itself.
ISOLATION_AWARE_ENABLED is a feature of the platform SDK headers, not the VC compiler, so it should work with any compiler version as long as you have the latest SDK. To check if the SDK headers you are using implement this feature, make sure there is a file called WinBase.inl in the same folder as WinBase.h, and that it contains the string “ISOLATION_AWARE_ENABLED”.
I’ll briefly return to those terms, “isolated application”, “side-by-side-assembly”, “private assembly” and “shared assembly”:
An “isolated application” is an EXE with a manifest, in its resources or a separate file. For some reason, these are not referred to as “assemblies” in MSDN, and there appears to be no umbrella term. The rules for what can go in Application Manifests differ from those for Assembly Manifests in a few ways. See the linked MSDN articles for details.
A “side-by-side” assembly is a collection of resources with a manifest. A side-by-side assembly can be referenced as a dependency by assembly id from other assembly (or application) manifests. The reference for how assemblies are located is Assembly Searching Sequence.
A “private assembly” is a side-by-side assembly that is deployed with a particular application. A “shared assembly” is a side-by-side assembly that is deployed to the system assembly cache (the winsxs) folder. Shared assemblies must be signed and must be installed using Windows Installer. The Assembly Searching Sequence will choose a shared assembly in preference to a private one.
Note that a private assembly is not the same thing as a DLL plonked into the same folder as the application. A private assembly has to have a manifest, and it will be located by WinSxS.
Application and publisher configuration files are used to redirect dependencies on particular assemblies to different versions. They are documented in MSDN under Configuration. These files affect the resolution of assembly dependencies during the process of populating an activation context.
Bits and Pieces
I’ll end with a collection of traps and observations…
You may have seen message boxes indicating “R6034” errors. These errors are not generated by WinSxS – they are VC8 runtime errors.
What happens is that when the VC8 runtime is loaded, it does its own check to see how it was loaded. According to the comments in the function that performs the check (“check_manifest” in crtlib.c), this is to “discourage the practice of not using a manifest to load the crt DLL”.
The check is performed by getting hold of the current activation context and checking whether it includes a mapping for “msvcr80.dll” (or “msvcr80d.dll” for debug builds). If it doesn’t, a message box is displayed and the DLL will fail to load.
One possible cause for a R6034 error is that something, somewhere in your process, is using ::LoadLibrary to load the VC8 runtime directly. Sometimes this done for the purpose of hooking runtime functions, eg. for debugging functionality. The solution is to ensure that when ::LoadLibrary is called, the current activation context is created from a manifest that references the VC8 CRT as a dependency, directly or indirectly. An alternative cause might be that a DLL that depends on the VC8 runtime is missing its manifest for some reason.
ALLOWISOLATION:NO is a linker option in VC8, which can only by used for EXEs. If it is set, a flag called IMAGE_DLLCHARACTERISTICS_NO_ISOLATION is set in the DllCharacteristics field of the PE header. This flag can also be set with the editbin utility.
According to MSDN:
“When isolation is disabled for an executable, the Windows loader will not attempt to find an application manifest for the newly created process. The new process will not have a default activation context, even if there is a manifest inside the executable or placed in the same directory as the executable with name executable-name.exe.manifest.”
As far as I have been able to tell from testing on Windows XP, this is simply untrue. I have not been able to detect any change in behaviour as a result of setting this flag. I asked on Google Groups, but didn’t get a response.
Lack of Uniqueness of Object Identifiers Across Contexts
As described earlier, object identifiers (DLL names, CLSIDs, WndClasses) in an activation context have to be unique. However, they do not have to be unique between two activation contexts.
Say a DLL is loaded in a particular activaction context, and then a new context is activated which maps the DLL name to a different file. If another attempt is made to load the DLL it will succeed and the second file will load. There will therefore be two DLLs with the same name in the process. I’ve confirmed this by testing on Windows XP.
Quirks of the Assembly Search Sequence
One quirk of the Assembly Searching Sequence is that when searching for an assembly of a particular name, WinSxS will first look for a DLL of that name and will stop the search if it finds one. Regardless of whether the DLL turns out to contain a manifest, WinSxS will look no further. This means that if you give an assembly the same name as one of the DLLs in the assembly, you have to embed the manifest in the resources of that DLL. If you want to deploy the manifest as a separate file, the assembly must not have the same name as any of the DLLs contained within.
Another oddity is that the Assembly Searching Sequence topic implies that if the manifest is embedded in the resources of the DLL, it has to be under resource ID 1 in order to be recognised by the assembly search. There’s no mention of this in Using Side-by-Side Assemblies as a Resource, which actually states that resource ID 1 is not “used for a DLL” (though I think that column is actually talking about a completely different mechanism – see “Loading DLLs” above). I haven’t tested this, but if the Assembly Searching Sequence article is correct, assemblies consisting of a single DLL built with a manifest in resource ID 2 (the default for DLLs in VC8) will not be recognised by the assembly search sequence.
/MANIFESTDEPENDENCY and #pragma comment(linker, “/MANIFESTDEPENDENCY:…”)
/MANIFESTDEPENDENCY is a VC8 linker option that lets you add dependencies to the manifest VC8 generates. The #pragma comment form allows you to embed such dependency options in an object file, effectively inserting the dependency into the manifest of any binary that includes the object file. This may be useful when producing static libs that have WinSxS depedencies.
If you are going to use #pragma comment to insert dependencies into the manifests of client binaries of static libs, you need to make sure the client applications will be built with VC8 or later. VC7.1 and earlier spit out a warning and ignore the embedded linker option.
Depends.exe can produce misleading results when statically viewing WinSxS dependencies. This appears to be because it can only go on the information in the binaries, whereas the true loading behaviour is determined by the activation context, which only exists at run-time. For more accurate dependency information try using depends.exe’s profiling feature.