.NET Fusion
Home About Workshops Articles Writing Talks Books Contact

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.

.NET Version 3.0 
This utility has changed in .NET version 3.0/2.0. Curiously, there is a radio button group on the dialog determining the log location, I say curiously, because the log location is specified on the Log Settings dialog obtained by clicking on the Settings button. The settings page gives you the option of not logging anything, provide the log in the exception, provide the failure logs in the exception and store it to disk, and to log all bind attempts. The setting you will want here is to log all failures to disk, but I have found that this does not work if you log the failure to the default location (which is the internet explorer Temporary Files cache). However, if you provide a custom log path then the logging works.

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:

*** Assembly Binder Log Entry (15/03/2004 @ 19:38:04) ***

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:

HKEY_LOCAL_MACHINE\Software\Microsoft\Fusion

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:

public string GetVersion()
{
   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:

%USERPROFILE%\Local Settings\Application Data\ApplicationHistory

On my machine the file for the test process is called app.exe.98b79bb3.ini and it contains this data:

[PolicyResolutionHistory]
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.

Page Six

This page is (c) 2007 Richard Grimes, all rights reserved