Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Managed C++ and the Side-by-Side Cache

Source Code Accompanies This Article. Download It Now.


Richard is the author of Programming with Managed Extensions for Microsoft Visual C++ .NET 2003 (Microsoft Press, 2003). He can be contacted at [email protected] richardgrimes.com.


Try this: At the command line, create a C++ source file and enter Listing One. While I have used the C++/CLI syntax, the "feature" I'm demonstrating will appear whether you use C++/CLI or Managed C++ syntax, or write unmanaged C++.

Compile this code as a managed library assembly:

cl /clr /LD lib.cpp

It is important that you compile the code as mixed mode (/clr) or as old syntax Managed C++ (/clr:oldsyntax, if you change the code appropriately), as will become apparent.

Next, create a C# process that calls this library (Listing Two). You can use Visual Basic .NET, but C# is just fine. Now compile it to use the library:

csc app.cs /r:lib.dll

Run the process and an exception is thrown:

Unhandled Exception:
System.IO.FileNotFoundException:
The specified module could not be found.
(Exception from HRESULT: 0x8007007E)
at App.Main()

So what have you done? Looking at the folder contents, you see that the library is there. The top WORD of the HRESULT is 0x8007, which is FACILITY_WIN32, meaning that it is a Win32 error. The low WORD is 126 in decimal, and winerror.h lists this as ERROR_MOD_NOT_FOUND. This error result is returned if LoadLibrary cannot find a module, so clearly the error indicates that an unmanaged DLL cannot be found.

To find the list of modules used by the library, load it in ILDASM and take a look at the MANIFEST. If the library loads DLLs through Platform Invoke, then these DLLs are listed as .module entries. However, for this library you'll find that it only uses the managed assemblies mscorlib and Microsoft.VisualC; both of these are in the .NET Global Assembly Cache (GAC). Another possibility is that there is unmanaged code in the assembly that is making calls to unmanaged libraries (for example, code that uses the Managed C++ It Just Works! technology).

To investigate this, select Headers from the View menu on ILDASM. This lists the PE file headers in the library. Scroll down until you find the Import Address Table (IAT) to get a list of all the methods imported from unmanaged libraries. Because the library was compiled as mixed mode, the library will use the C Runtime Library (CRT). The IAT confirms this—it lists msvcr80.dll and msvcm80.dll. The former is the multithreaded DLL version of the CRT, the latter a mixed-mode library containing a managed version of some of the CRT. Clearly, the error is being generated because Windows cannot find one—or both—of these libraries.

Finally, check %systemroot%\system32 to see if the libraries are there—but they won't be. At this point, you will be accusing the Visual Studio installer of not installing the latest version of the CRT on your machine, while in fact, the installer has installed these CRT libraries—just not where you expect them.

The Side-by-Side Cache

The Visual Studio installer places Visual Studio shared libraries in a location called the "side-by-side cache." The location of this folder is %systemroot%\WinSxS and only SYSTEM and Administrators have write access; all other users have just read and execute access. The side-by-side cache contains "assemblies"—not managed assemblies, but the unmanaged equivalent.

Under the WinSxS folder is a folder for each assembly and two folders, Manifests and Policies, that contain information used for versioning. There are two folders associated with the CRT:

x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3 b_8.0.50727.42_x-ww_0de06acd
x86_Microsoft.VC80.DebugCRT_1fc8b3b9a1
e18e3b_8.0.50727.42_x-ww_f75eb16c

Clearly one is the release build and the other the debug build. The important point is that a version and a public key token are part of the folder name. If you list the contents of the former folder, you see that it contains msvcm80.dll, msvcp80.dll, and msvcr80.dll. These are the contents of version 8.0.50727.42 of the unmanaged assembly called "Microsoft.VC80.CRT." An unmanaged assembly can contain one or more files, and these files can be DLLs containing native code or COM objects. An unmanaged assembly is deployed as a single unit and the files in an assembly are described by an XML file called a "manifest."

The manifest is stored in the Manifest folder and has the same name as the assembly but with the .manifest extension. This file lists the files in the assembly. In addition, there is a file with the same name as the assembly, but with the .cat extension. This is a signed security catalog file that contains the hashes for the assembly files, but since it is signed, its contents are secure from tampering, and so Windows can use these hashes to check to see if any part of the assembly has been tampered with after deployment.

Client Manifests

A client file (process or library) can be built against a file in an assembly, but the client will be built against a specific version of the assembly (hence, the constituent file). Windows loads only the specific version required. To indicate the version of the shared assembly, an executable file (process or library) also has a manifest. The linker creates a file containing the manifest for the executable file it is building, so if you take a look at the folder where you built the library previously in this article, you'll find a file called "lib.dll.manifest"; Listing Three is its contents. As you can see, this says that the managed assembly, lib.dll, depends upon files in the Microsoft.VC80.CRT shared side-by-side assembly. Although this file is in the same folder as the library it describes (lib.dll), Windows clearly did not use it. This is contrary to the documentation in MSDN that says:

You may include the application manifest in the application's binary executable header file...As an alternative, you can place a separate manifest file in the same directory as your application's executable file. The operating system loads the manifest from the file system first, and then checks the resource section of the executable. The file system version takes precedence.

However, this is not the case. Windows ignores the manifest file for a library. Still, the documentation does give a clue how to resolve this issue. The manifest file should be bound to the executable file as an unmanaged resource RT_MANIFEST with a resource ID of 2.

There are two ways to do this. The first way is to create a resource script that contains a reference to the manifest:

#include <winuser.h>
2 RT_MANIFEST lib.dll.manifest

This is then compiled as a resource with the Windows resource compiler rc.exe, and the resource is bound as an unmanaged resource using the linker. The problem with this approach is that it is the linker that creates the manifest file, so you have to run the linker twice: once to generate the manifest, and a second time to link the resource to the output file. Listing Four is a sample makefile that shows how to do this.

The other way to bind the resource to the output file is to use the undocumented switches of mt.exe, the manifest tool. This is the way that Visual Studio 2005 creates C++ libraries (managed mixed mode or unmanaged) that will load. The two switches are /manifest, which is used to specify the name of the manifest file, and /outputresource, which is used to give the name of the PE file that will be altered and the resource ID for the manifest resource. Typically, for libraries, the resource ID will be 2; for a process, it should be 1. For example:

mt /manifest lib.dll.manifest
/outputresource:lib.dll;#2

Note the inconsistency here: The parameter for /manifest is separated from the switch with whitespace, whereas the parameter for /outputresource must be separated from the switch with a colon. Clearly these switches are the result of different developers.

Once you have bound the manifest to the library, Windows is able to determine the correct version of the assembly to load. If you run the C# process after these changes, you find that it runs as expected.

This example uses a C# process. If you build a mixed mode (/clr) or pure IL (/clr:pure) Managed C++ process to access this mixed-mode library, then the linker creates an application manifest file. When such a process is run, Windows looks for a manifest bound as manifest resource ID 1, or it looks for an appropriately named manifest file. Because a mixed mode or pure process uses the CRT, it means that the CRT assembly will be mentioned in the manifest file. Consequently, in this specific case the library need not have a manifest. However, you should not rely on this mechanism because, in this example, it is coincidence that the process uses the same unmanaged assemblies as the library.

Versioning

Take another look at the manifest file created for the library. Notice that the version of the assembly required is given as 8.0.50608.0. Again, the assembly installed by the Visual Studio 2005 installer is 8.0.50727.42. This is a policy version redirect. Take a look in the Policies folder in the side-by-side cache folder where you find a folder with this name:

x86_policy.8.0.Microsoft.VC80.CRT_1fc8b3b
9a1e18e3b_x-ww_77c24773

Notice that this has the full name of the assembly, except for the version part. This folder contains a policy and security catalog file that have a name based on the version that will be the result of a redirect:

8.0.50727.42.policy

Again, this is an XML file (see Listing Five). This policy file is for version 8.0.50727.42, which is the version installed by the Visual Studio installer. It indicates the versions that will be redirected towards this version through the <bindingRedirect> element. Listing Five indicates that a request for the assembly with a version between 8.0.41204.256 and 8.0.50608.0 is redirected towards the version of 8.0.50727.42. Unlike Fusion (the assembly-loading technology in .NET), the version redirects for side-by-side shared assemblies can only be for versions varying by the build or revision values. They cannot be used for versions varying by the major and minor version values.

This raises questions: Why is this redirection needed? Why didn't the linker simply specify the version of the assembly installed by the installer in the manifest file? The reason is that the linker gets the version of the assembly from the import static library for the library that will be imported (msvcmrt.lib). This raises another question: Why is the linker using import libraries for versions of the DLLs other than the one that is installed? The reason is that these are the import libraries that have been installed.

The discussion so far has been for the Managed C++ compiler (both C++/CLI and the old syntax). However, before you native C++ developers get too smug, note that you are also affected by this "feature." If your code uses one of the shared Visual Studio native libraries (MFC, ATL, or the CRT), then your code must have a manifest either bound to the executable or, for an .exe only, supplied as a separate .manifest file.

Conclusion

I have been using Microsoft's C++ compilers for 12 years, and for all versions of the compiler/linker that I have used, I have been able to create libraries that will be loaded by Windows and run. Version 14, supplied with Visual Studio 2005, departs from this tried and trusted mechanism because version 14 will produce libraries that do not run.

When this "feature" first appears to you, it is a shock. However, there are two workarounds. The first option is to run the linker twice—once to generate the manifest file, which you can then compile into an unmanaged resource, then again to bind the manifest to the PE file. This is my preferred option because, on the second invocation, you can provide the name of the key file or container if you are building a strong-named assembly, but invoking the linker twice seems to me to be overkill.

The other option is to alter the assembly you have built using the undocumented switches of the manifest tool (mt.exe). However, if you used the linker to build a strong-named assembly, the action of mt.exe invalidates the strong-name signature and the assembly will not load. When you build strong-named managed assemblies using this technique, you can only do so if you delay signing the assembly, which means that you have to perform yet another step before the library can be used. The manifest tool will give you a warning that it has altered a strong-named assembly, but you really should never get into this position.

DDJ



Listing One

using namespace System;
public ref class Test
{
public:
   void CallMe()
   {
      Console::WriteLine("called me");
   }
};
Back to article


Listing Two
using System;

class App
{
   static void Main()
   {
      Test test = new Test();
      test.CallMe();
   }
}
Back to article


Listing Three
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type='win32' name='Microsoft.VC80.CRT' 
         version='8.0.50608.0' processorArchitecture='x86'
          publicKeyToken='1fc8b3b9a1e18e3b' />
    </dependentAssembly>
  </dependency>
</assembly>
Back to article


Listing Four
# The main target
all: app.exe

# A C# process that depends upon a Managed C++ library
app.exe : app.cs lib.dll
   csc app.cs /r:lib.dll
# This is the second invocation of the linker, so the object file and 
# manifest will already exist, so they do not need to be rebuilt.
lib.dll : lib.cpp lib.res lib.obj
   link /DLL /manifest:no /machine:x86 lib.res lib.obj
lib.res : lib.rc
   rc lib.rc
# Create a temporary resource script that binds the manifest file to the DLL
lib.rc : lib.dll.manifest
   type <<[email protected]
#include <winuser.h>
2 RT_MANIFEST lib.dll.manifest
<< KEEP
# Create the object file, and invoke the linker to create the manifest file
lib.dll.manifest lib.obj : lib.cpp
   cl /LD /clr lib.cpp
Back to article


Listing Five
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Copyright (r) 1981-2001 Microsoft Corporation -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

    <assemblyIdentity type="win32-policy" name="policy.8.0.Microsoft.VC80.CRT" 
      version="8.0.50727.42" processorArchitecture="x86"
      publicKeyToken="1fc8b3b9a1e18e3b"/>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" 
               processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"/>
            <bindingRedirect oldVersion="8.0.41204.256-8.0.50608.0" 
               newVersion="8.0.50727.42"/>
        </dependentAssembly>
    </dependency>
</assembly>
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.