The post is about the DLL hell (classical MSDN article by Rick Anderson) of course.
Static and Dynamic DLL Loading
Basically, a DLL can be loaded and used by an executable in two ways: static and dynamic.
- Static DLL loading. When the executable is linked, the DLL name that it depends on is written into the executable file. This is done by instructing the linker the import library the executable is linked against, and the import library dictates what DLL must be present at run-time. When Windows loads the executable, it sees the depended DLL name, and it then searches for the DLL and tries to load it. When the DLL is loaded, the entries in the executable to the DLL are updated and a function call or variable access to the DLL are just like a normal one. Just before the executable is unloaded, the depended DLL is unloaded. Of course, an executable can depend on more than one DLLs, and these DLLs, since they are executables too, can also further depend on other DLLs.
- Dynamic DLL Loading. In contract to static DLL loading, dynamic DLL loading is not known to the linker. The import library is not need for the process. The executable does not advertise that the DLL is required. Instead, the programmer calls Windows API function LoadLibrary to load the DLL and FreeLibrary to unload the DLL. Between the LoadLibrary and FreeLibrary calls, GetProcAddess has to be called to gain access to a DLL exported function or variable, using either the function/variable name or an ordinal number. The programmer also needs to know the signature of the exported function to correctly make a call after GetProcAddress. So every access to the DLL must have at least two steps now.
Most of the developers use static DLL loading much more than the dynamic one. Some of the usage is implicit and never noticed, for example, linking to Windows system DLLs such as KERNEL32, USER32 and GDI32 is automatic and often mandatory. The benefits of static loading include:
- DLL resolving is OS responsibility, so the developer does not need to handle “DLL not found” errors in code (compared with dynamic loading).
- Once the executable is loaded successfully (along with its depended DLLs), the developer can assume to have access to all exported symbols in the DLL in the way agreed by the import library. While in dynamic loading, not only two steps are needed to gain the access, but also errors can occur due to missing symbols, and disaster can happen if the signature is not correct in calling a DLL function.
How does Windows find a DLL to load? In the past for Windows before XP/Vista and Server 2003, the rules were (relatively) simple. In dynamic loading through LoadLibrary, the procedure went as follow:
- If the DLL module is already loaded, reuse it; otherwise
- If you specify a full DLL path, only that DLL is tried; otherwise
- The directory containing the executable for the calling process (i.e., the EXE file most of the time) is searched (to allow private DLLs); otherwise
- The directories in PATH are searched, one by one.
In static loading, the OS calls LoadLibrary in the background for the executable, therefore the searching order is similar.
What is the problem here? A lot of problems. The only identifiable to the DLL is the file name. Version number does not count. You put a DLL at a public location, but somebody replaces it with a newer DLL (with the same name), and your application fails. You revert the DLL, and his application now fails. Hacker replaces the DLL and breaks into your (probably sensitive) application. Hacker puts a DLL to your local folder and eclipses the system DLL. And so on.
New Concepts in Resolving DLLs
Since Windows XP/Vista and Windows Server 2003, Microsoft has coined a few new concepts to solve the problem, together with the help from its development suite Visual Studio.
- Assembly: “Fundamental unit for naming, binding, versioning, deploying, or configuring a block of programming code.”
- Side-by-Side Assembly: Starting with Windows XP, multiple versions of an assembly can co-exist in the system and used by applications running at the same time, hence the name side-by-side (SxS) assembly. An SxS assembly contains a group of DLLs, classes, COM servers, type libraries and so on as a single unit, and is described by manifests. In this post, all assemblies mentioned below are assumed SxS assemblies, unless specifically noted.
- Shared Assembly. A shared SxS assembly is available for use by multiple applications on the computer. The shared assemblies are installed in the %WINDOWS%\WinSxS directory. They are not registered globally in the registry, but still globally available to applications that refer to them in the application manifests. Multiple versions are differentiated using different version numbers. Prior to Windows XP, shared assemblies were registered globally and installed in Windows System folder, and only the latest installed version is usable by applications.
- Private Assembly. An assembly can be installed as a private assembly exclusively for the application. A private assembly is typically installed in the application’s folder or subfolder with its manifest. Installing a private assembly does not involve the system, and can be as simple as copying the files.
- Isolated Application. An isolated application is installed with a manifest describing the assemblies it uses, including shared and private assemblies. An application is fully isolated if all its components are either shared SxS or private assemblies. It is partially isolated if it uses other non SxS components, which means it could be affected by installation or removal of other applications. A fully isolated application can be installed by just copying the files without implicating the registry, and will run given the depended assemblies are available on the target system.
- Manifest: XML text accompanies and describes an SxS assembly or isolated application. A manifest uniquely identifies an assembly using the assemblyIdentity element, which contains the assembly name and version among others. A manifest can be a separate text file, or embedded as resource in the DLL or EXE file.
- Assembly Manifest: describes an SxS assembly.
- Shared Assembly Manifest: must be a separate XML file, and stored under %WINDOWS%\WinSxS\Manifests. The typical extension is .manifest. The manifest describes the assembly members such as DLL filenames and so on. The DLL in a shared assembly does not normally have embedded manifest.
- Private Assembly Manifest: can be a separate XML file along with the assembly members; Or, if the private assembly is a single DLL, the manifest can be embedded as a resource with ID 1.
- Application Manifest: The isolated application describes the names and versions of shared and private assemblies that it binds to in the application manifest. Note the application manifest does not normally name the depended DLLs (because assembly is the granularity). However, if you check the EXE’s dependency using Dependency Walker you can still reveal the depended DLLs, which may fall into the required assemblies. An application manifest is normally embedded as a resource in the executable (you can open the exe or dll with Visual Studio and check its resources).
- Publisher Configuration File: This XML file globally redirects a specific shared assembly from a version to a different version. These manifests are stored in %WINDOWS%\WinSxS\Policies directory. Typical use is when an assembly is upgraded with a security fix, and the publisher configuration file can redirect the applications to use the new assembly without their notice.
- Application Configuration File: This XML file redirect a specific application from using one version to another version of an SxS assembly. It is stored locally with the application, with the filename extension .config. When present, the application configuration file always overrides the default configuration (as comprised by the application manifest and assembly manifests). On Windows XP, the application configuration file also overrides the publisher configuration. On Windows Server 2003, it may or may not override the publisher configuration depending on the context.
- Activation Context: Data structure resolving the redirection of DLL versions, window classes, COM servers, type libraries and interfaces in memory. It is maintained by the system.
- Assembly Version: in the format of major.minor.build.revision. Each field is in the range of 0-65535. The major.minor part dictates compatibility, i.e., all versions with the same major.minor fields are compatible for the assembly user. Therefore, redirections by publisher configuration or application configuration normally only happen with varied build.revision.
- DLL/COM Redirection: Old technology. Do not use for Windows XP SP2+. This method creates a .local file with the application, such that the application will use a local component in the same directory instead of the globally available component.
Error: Application failed to initialize properly (0xc0150002)
You may get this error when you launch an improperly installed application. Informally and often, this means some DLLs that the application depends on are not available.
To be exact, 0xc0150002 is a NTSTATUS code returning from driver level, literally STATUS_SXS_CANT_GEN_ACTCTX, or SxS cannot generate the activation context.
So what is an activation context? Basically, when the application loads, its manifest is parsed/probed by Windows, then all depended (and further depended…) manifests are parsed/probed. When there is no error, e.g., missing assemblies, the collection of manifests is considered integral, and this is an activation context. Obviously, making ready the activation context is an important step to load the application; this is similar to the DLL resolving for older Windows. Due to the costly nature of creating an activation context, Windows has a cache mechanism for this. Activation context is managed in CSRSS.exe, a system process, where activation contexts can be shared by different processes.
The most often reason of 0xc0150002 is that one or more referenced assemblies are missing, when SxS tries to create the activation context. The error message does not show details about why it fails, for example, it does not tell you what assembly is missing.
If you are on Windows Vista or 7, you can find the exact missing assembly information in the event log (use event viewer), or use the stock Windows tool sxstrace.exe, details here.
With Windows XP, there is no easy way. Junfeng Zhang suggests using the sysinternals tool FileMon.exe (now Process Monitor ProcMon.exe) to monitor CSRSS.exe. The log generated by Process Monitor will show the manifest/assembly files that CSRSS.exe tries to access, and this gives clues about why the application initialization fails. For details see Diagnose SideBySide failures in Windows XP/Windows Server 2003.
[to be finished]