5. Utilities
One of the biggest problems with the Win32 LoadLibrary function is that if it cannot find a DLL
the function only returns a status code. You don't know where the function has searched, nor what it
was looking for. Fusion is more expressive because it keeps a log of each step it takes to locate a library and if it loads and binds to a library it notes the full name of the library (significantly, the version) so that you can use the same configuration in the future.
5.1 Fuslogvw
Before you start this example make sure that you have removed copies of the library from the GAC and ensure that there is no configuration file.
You will use the process file,
library and
key file from the previous page. Change the library so that it compiles to version
1.0.0.0, compile both the library and the process. Delete the library. Run the process and you'll find that a
FileNotFoundException exception is thrown. To get more information type
fuslogvw at the command line to start the Assembly Binding Log Viewer.
If you find that fuslogvw does not show any items the reason is that you have not enabled logging of failures.
On .NET v.1.1 fuslogvw has a check box called Log Failures,
ensure that this box is checked, and then re-run the application and refresh fuslogvw.
When the utility starts you will see a list of the runs that ended in an error. You can select and view more details about a particular error. For example, select the entry for the error you have just generated and click on the View Log button. This will open a browser window with the following text:
The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.
Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\fusion.dll
Running under executable C:\TestFolder\app.exe
--- A detailed error log follows.
=== Pre-bind state information ===
LOG: DisplayName = lib, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=ede6789fb0b13219 (Fully-specified)
LOG: Appbase = C:\TestFolder\ Note 1
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = NULL
Calling assembly : app, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: Processing DEVPATH. Note 2
LOG: DEVPATH is not set. Falling through to regular bind.
LOG: Attempting application configuration file download. Note 3
LOG: Download of application configuration file was attempted from
file:///C:/TestFolder/app.exe.config.
LOG: Application configuration file does not exist.
LOG: Publisher policy file is not found.
LOG: Host configuration file not found.
LOG: Using machine configuration file from
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\config\machine.config.
LOG: Post-policy reference: Note 4
lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ede6789fb0b13219
LOG: Cache Lookup was unsuccessful. Note 5
LOG: Attempting download of new URL file:///C:/TestFolder/lib.DLL.
LOG: Attempting download of new URL file:///C:/TestFolder/lib/lib.DLL.
LOG: Attempting download of new URL file:///C:/TestFolder/lib.EXE.
LOG: Attempting download of new URL file:///C:/TestFolder/lib/lib.EXE.
LOG: All probing URLs attempted and failed.
Note 1 This is the application's
folder
Note 2 These two lines indicate whether DEVPATH was used
Note 3 These lines obtain configuration from the application and machine.config
Note 4 This is the full name of the library to be loaded
Note 5 First search the GAC, then search AppBase
|
.NET Version 3.0 The output for .NET version 3.0/2.0 gives essentially the same information. The main difference is that it is more compact, for example, even though you can use DEVPATH in .NET 3.0/2.0, if the environment variable is not present
then .NET 3.0/2.0 does not mention this in the log. Similarly the log does not
mention that a configuration file was not found for the application assembly. |
You can see every step that Fusion takes to locate
the assembly, and identify the problem. In this case you can see that
DEVPATH is not set and there was no configuration file, so there's no
<codeBase>, <bindingRedirect> nor privatePath.
If there was an application configuration file, or were entries in the machine
configuration file, these could change the name of the library by redirecting
the version. After the configurations have been checked Fusion announces the
definitive name for the library. Finally, Fusion tries to locate the library
with this name, first looking in the GAC for an assembly with this complete name and then
by initially looking in the private
application folder for files with the short name and extensions of dll
and exe, and then looking in a subfolder with the short name of the
assembly.
This tool shows the application of Fusion policy. In effect, three
policies are used one after the other, so that the next policy is applied to the
results of the previous policy. The three types of policy and the order is
apparent from the results from fuslogvw: first the application
policy is applied, then the publisher policy and finally the machine policy is
applied. This results in the name of the assembly that Fusion will attempt to
find (Note 4, above).
The first policy is given by the application's configuration file. The idea here is the application's developer realizes that there may be versioning issues and tries to take steps to correct them. If the developer has full access to the source code for all of the assemblies used in the project, then the best course of action is to recompile the assemblies. However, this is not always the case.
For example, an application could use two libraries called one.dll
and two.dll, and both of these could use a third library called
lib.dll. Now imagine that one.dll uses version
1.0.0.0 of lib.dll, then you decide that you want to use
two.dll which uses version 1.1.0.0 of lib.dll.
If lib.dll is strong named then, as you've found out, the full name
of the library, including its version will be placed in the assembly using it.
By default, Fusion will attempt to load the specific version of the library and
this will result in two versions of the same library being loaded. Fusion is
happy to do this, but there could be incompatibilities between the two versions.
In particular the public types exported from lib.dll are named
according to the assembly name, so a type exported from one version of
lib.dll is not the same as the same named type exported from a
different version of lib.dll.
If the developer has access to the
source code for one.dll and two.dll then they can be
recompiled to use a single version of lib.dll (and if necessary,
changes can be made to these libraries). However, sometimes the source is not available and so the only solution is to
make sure that the library that uses the older version of lib.dll
should use the newer version instead. This can be carried out with Fusion
application policy.
The next policy that is applied is the publisher policy, which is covered in detail in the next section. This is important for shared libraries because there could be many applications on a machine using one library and if there is a serious problem with that library (for example, a security bug) which means that the old version was superseded, it would involve changing many application configuration files to perform the redirection. A publisher policy file allows the redirection information to be placed in one location, the GAC, so that all applications are affected.
The final policy is the machine wide policy that is applied through the
machine.config file. Like publisher policy, these settings are global,
however, unlike publisher policy, the machine policy cannot be overridden.
However, it was stated earlier that the
machine.config file is dependent on the version of the runtime. Your
machine can have more than one version of the runtime installed and running side
by side, and each of these versions will have its own copy of
machine.config.
By default the Fusion log is kept in memory, you can get access to this
through the FusionLog property of the FileNotFoundException.
To test this, add a using statement for System.IO and
then change the Main method to catch the exception:
|
try
{ LibraryCode code = new LibraryCode(); Console.WriteLine("library {0}", code.GetVersion()); } catch(FileNotFoundException e) { Console.WriteLine(e.FusionLog); } Compile this and perform the tests above on this code. |
The settings for the fuslogvw tool are kept in the following
registry key:
but as you have seen, if you
check Log Failures then the data will be saved to the disk. By default
this will be saved to the Internet Explorer cache, but this will present a
problem if your application is a service or ASP.NET. The reason is that the IE
cache is for a specific user; services and ASP.NET applications will run
under a user other than the interactive user. You can change the location where fuslogvw
saves logs by creating a string (REG_SZ) value called LogPath.
This value should be the absolute path to an existing folder. If you use
LogPath then you should select the Custom setting in the group of
radio buttons. Note that the ASP.NET setting has no effect at all.

There are three other values in this registry key. If the first,
LogFailures (REG_DWORD), is 1, it indicates
that binding failures will be logged. The second value is ForceLog
(REG_DWORD) and if this has a value of 1 then all logs
will be saved to disk, successful and failed binding. Finally, if
LogResourceBinds (REG_DWORD) has a value of 1
then Fusion will log bind failures for satellite resource files. The default is
not to log binding failures for satellites. You can find out more about
satellites on page 9.
|
.NET Version 3.0 As mentioned above, the version of fuslogvw in .NET version 3.0/2.0
allows you to set the custom path and to force all binding logs to be stored,
through the Log Settings dialog. |
Note that fuslogvw will read the registry only when it starts,
so if you change the registry settings you will have to restart fuslogvw
for them to take effect. Also, note that you will have to be an administrator to
change the registry, but whatever values used in the registry will affect all
users regardless of whether they are administrators.
5.2 Fixing Applications
|
.NET Version 3.0 This functionality has been deprecated in .NET version 3.0/2.0 (announcement is here), so this section is not applicable to this version of the runtime. However, the configuration tool still displays a link to allow you to fix a specific application. |
If Fusion successfully loads and binds a library it
will record this information for future use. To try this out build the library
twice. Compile the library with a version of 1.0.0.0, compile the
process against this version and then install the library into the GAC. Run the
process and confirm that the code runs without an exception. Now imagine that
you decide to change the library and deploy version of 1.1.0.0 and
indicate that the new version should be used with the application configuration
file (this is better performed with a publisher policy file, described later).
However, this new version has a bug.
To simulate this, change the library so that it deliberately throws an exception:
{
throw new Exception("Deliberate exception");
Assembly a = Assembly.GetExecutingAssembly();
return String.Format("version: {0} codebase: {1}",
a.GetName().Version.ToString(), a.CodeBase);
}
Remember to change the version to 1.1.0.0. Compile only the
library (the compiler will issue a warning, which it is safe to ignore in this
example). Add this library to the GAC. To indicate that the new version should
be used, add a configuration file with a binding redirect using the
configuration tool to redirect version 1.0.0.0 to version
1.1.0.0.
Now run the process and you'll find that an exception is thrown. We know that
the problem is the <bindingRedirect> in the configuration file says
that we should use a faulty library in preference to a perfectly good one, but
in a real application there may be many libraries and different configuration
files redirecting versioning and codebase. To help you fix such
problems, you can use the configuration tool. For .NET version 1.1 click on the
Applications node and in the right hand pane there is a link to Fix an
Application:

When you click on this hyperlink and the tool will give a dialog with a list of all of the applications that have run successfully on your machine.

Select the application you want to fix and click OK. The tool may say that you have previously tried to fix the application and would you like to go back to the old configuration, in this situation click No. This dialog will occur if the first run of the process had a configuration file. Next, the tool will search through the logs to see when the application last ran successfully and offer to restore those settings.
Note that you can get to this dialog through your process's entry in the Applications node: right click on the application's node and select Fix Application.
Click on the Advanced button to get a list of previous runs:

The Application SafeMode turns off publisher policy files, so it is
your responsibility to investigate further where the problem occurred. It is
better to select the configuration of a previous run that was successful, in the
example above, the selected item. The tool will then write the application
configuration file to use the version of the library that was successful before.
Since the version was the same as the version in the process manifest the tool
merely comments out the <bindingRedirect> node.
So where is the configuration tool obtaining this information? When you run an application the runtime will store information about the libraries that were loaded in an ini file in the following folder:
On my machine the file for the test process is called
app.exe.98b79bb3.ini and it contains this data:
ExecutablePath=C:\temp\Code\3.1\app.exe
ApplicationName=app.exe
NumResolutions=2
ActivationSnapShot_1=29680760.2363665040
ActivationSnapShot_2=29680741.487403664
[29680741.487403664]
RuntimeVersion=v1.1.4322
System/b77a5c561934e089/NULL/1.0.5000.0=29680741.487403664/System/b77a5c561934e089/NULL/1.0.5000.0
lib/c5f9b7fc0eb357b6/NULL/1.0.0.0=29680741.487403664/lib/c5f9b7fc0eb357b6/NULL/1.0.0.0
System.Drawing/b03f5f7f11d50a3a/NULL/1.0.5000.0=29680741.487403664/System.Drawing/b03f5f7f11d50a3a/NULL/1.0.5000.0
[29680741.487403664/System/b77a5c561934e089/NULL/1.0.5000.0]
VerReference=1.0.5000.0
VerAppCfg=1.0.5000.0
VerPublisherCfg=1.0.5000.0
VerAdminCfg=1.0.5000.0
[29680741.487403664/lib/ede6789fb0b13219/NULL/1.0.0.0]
VerReference=1.0.0.0
VerAppCfg=1.0.0.0
VerPublisherCfg=1.0.0.0
VerAdminCfg=1.0.0.0
[29680741.487403664/System.Drawing/b03f5f7f11d50a3a/NULL/1.0.5000.0]
VerReference=1.0.5000.0
VerAppCfg=1.0.5000.0
VerPublisherCfg=1.0.5000.0
VerAdminCfg=1.0.5000.0
[29680760.2363665040]
RuntimeVersion=v1.1.4322
lib/ede6789fb0b13219/NULL/1.0.0.0=29680760.2363665040/lib/ede6789fb0b13219/NULL/1.0.0.0
[29680760.2363665040/lib/ede6789fb0b13219/NULL/1.0.0.0]
VerReference=1.0.0.0
VerAppCfg=1.1.0.0
VerPublisherCfg=1.1.0.0
VerAdminCfg=1.1.0.0
The Fix an Application tool reads this information and uses it to locate a combination of library versions that worked together.
Now that you have finished the example you should remove the two versions of
the library from the GAC (gacutil -u lib) and edit the library code to remove
the line that throws the exception and return the version to 1.0.0.0.
| I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com. |
Errata
If you see an error on this page, please contact me and I will fix the problem.