Locating Assemblies

We will now examine the question of where the system will search for assemblies. There are three separate times at which a search for assemblies will be performed and there are different rules for each:

  • Compiling - when a compiler is searching for assemblies that are referenced in the code it is compiling

  • Adding Project References - when VS.NET is working out which assemblies to display in its Add Reference dialog

  • Loading - when an assembly is running, and the CLR needs to locate and load other referenced assemblies

The third of this list, the CLR's search for assemblies to load into a process is officially known as probing. It's arguably somewhat cheeky of me to include the other two cases in the same list since these cases are not under the control of the CLR, but are application-specific. If you take into account all the third-party software that is around, there may well be many other applications that need to explicitly locate assemblies, and which have their own rules for which folders they search in. However, the cases of compiling and adding project references in VS.NET are situations that you're likely to encounter frequently when writing managed code, so it seems sensible to consider those cases here.

You may wonder why Microsoft has gone for several sets of rules. Wouldn't it be simpler to have for example, compilers look in the same places that the CLR looks in when loading an assembly? The trouble is of course that we're not normally talking about the same machine. The compiler is at work on the developer's machine, while the CLR is more likely to be loading an assembly on the end-user's machine. The circumstances are different, and the rules for locating assemblies need to take account of the fact.

How the Microsoft Compilers Locate Assemblies

What I'm talking about here is, when you type in something like this:

 csc /r:MyControl.dll /r:MyOtherControl.dll MyProgram.cs 

Where is the compiler going to look for MyControl.dll and MyOtherControl.dll? Well, assuming you haven't actually specified the path in the csc command, it will search through the following locations in order:

  • The working directory

  • The CLR system directory

  • If you've specified any folders using the /lib flag in the csc command (/libpath for the VB compiler), these folders will be searched

  • If the LIB environment variable specifies any folders, these will be searched

If none of these folders yield the required assemblies then the compiler will flag an error.

One interesting point to note here is that I've made no mention of the Global Assembly Cache in the above list. The assembly cache is something that is really the prerogative of the CLR itself, and while it would in principle be possible for compilers to look there for libraries, Microsoft has chosen for them not to do so. Thus any assembly that the compiler looks up will be a local copy rather than something in the assembly cache. That arguably makes sense because the chances are many of the referenced assemblies are ones that you're working on and debugging at the moment - and it would add some considerable extra work to the build process if you had to install the most up-to-date copies of them all to the assembly cache every time you rebuilt the project.

This does of course mean that in many cases, the compiler won't be looking up the same copy of the assembly as is loaded at run time. That's no problem as long as the copies contain identical metadata - since all the compiler is doing with the referenced assemblies is examining their metadata to make sure the types and methods, etc. that your code refers to do exist, and that your code has passed the correct number and type of arguments to the methods.

There is one other point to be aware of. When you build a project, it's common to only give the names of referenced assemblies, as in the above command-line operation (although you can specify a complete assembly identity if you prefer). If you only specify a name, then the compiler will use the first assembly whose name matches, and it will read the full assembly identity out of this assembly (including version, culture, and public key, if present) and place this information in the AssemblyRef token in the generated assembly. This means that when your assembly is executed, it will by default load the same version of any referenced assembly as the code was compiled against.

How VS.NET Locates Assemblies

When you build a project with VS.NET, VS.NET is of course invoking the same compiler that you use from the command line to do the build, so the paths searched are the same as those listed above. However, when you click on Add Reference to bring up the Add References dialog, the places that VS.NET searches in order to populate the .NET tab of that dialog box are rather different. Here's where VS.NET looks:

  • The .NET Framework installation folder.

  • In two Registry keys:

     HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\AssemblyFolders HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders 

    And any subkeys of these keys. For any such key, VS.NET will look at the default value of the key, which is assumed to be a folder path. VS.NET will display any .dll assemblies (not executable assemblies - VS.NET won't let you add these as references, though it is possible to do so when compiling from the command line) in all the folders so identified.

Notice that, once again, the assembly cache is not searched for files. VS.NET will pick up all the .NET Framework Class Library DLL'S because copies of them are stored in the .NET Framework installation folder. So if you've ever registered an assembly in the global assembly cache and wondered why VS.NET wouldn't display it in the Add References dialog to let you add a reference to it, that's the reason.

If you do want some of your assemblies that are not part of the VS.NET solution you are working on to be displayed in the Add References dialog, then the best procedure is to add a subkey under the .NETFramework\AssemblyFolders Registry key, and set its default value to the folder containing your assemblies. The following regedit screenshot should show you the idea - it shows that I've registered a folder, E:\IL\GroovyLibrary\bin\release as a location that contains assemblies for VS.NET to display in the Add References dialog:

click to expand

Note that the .NETFramework\AssemblyFolders key seems to be the one that is used by third party software, with VisualStudio\7.0\AssemblyFolders intended for use by VS.NET itself to indicate its own DLL's. Also bear in mind that each time you compile your code, VS.NET will copy referenced files into the working directory - because otherwise the compiler, with its different rules for where to look for assemblies, wouldn't be able to find them. The exception to this is any assemblies referenced from the CLR system folder - these don't need to be copied because compilers look in that folder anyway.

How the CLR Probes for Assemblies

After the complicated rules of compiler and VS.NET searches, you may be relieved to know that the way that the CLR searches for assemblies when it is loading them into a process to be executed is by default relatively simple. Only one or two locations are checked for each assembly (which, obviously, is good for run time performance):

  • If the identity of the requested assembly does not include a strong name, then the only place that the loader will search is the application folder: this is the folder that contains the entry point assembly for the process that needs to load the referenced assembly.

  • If the identity of the requested assembly contains a strong name, then the CLR will search in the GAC and in the application folder.

  • mscorlib.dll is a special case and is always loaded from the CLR system folder.

  • There are separate rules for satellite assemblies, which we'll cover later in the chapter.

These rules for assembly probing are of course the reason why it is normal to place all assemblies that form part of one application in the same folder.

Unfortunately, very little in life is quite so simple. The complicating factor when probing for assemblies is that it is possible to override the default probing policy for any application - the mechanism for this is by means of an XML configuration file called <AppName>.exe.config, which is placed in the application folder alongside the main application assembly whose probing policy you want to modify. There are quite a few possibilities for changing the probing rules, but we'll briefly summarize the most important points here.

The most common purposes of overriding the default probing policy for an assembly are to:

  • Allow referenced assemblies to be located in different locations.

  • Tell the CLR to load a different version of a referenced assembly from that indicated in the AssemblyRef token in the assembly that needs to load the new assembly. This will typically be done if you have just shipped a new version of some library, and you want dependant assemblies to call up the new version, but you don't want to have to recompile and reship all the dependant assemblies (or perhaps some dependant assemblies are written by another company so you don't have access to them to recompile them).

Although customization of probing policy for each application is controlled through the .config XML file, provided you're not doing anything too exotic, the easiest way to manipulate probing policy is to use a tool that generates the .config file for you. Such a tool is available - it's an MMC snap-in called mscorcfg.msc. The easiest way to run the tool is from the control panel, under Administrative Tools, then .NET Framework Configuration. mscorcfg.msc is a generic .NET configuration tool - we'll use it a fair amount in Chapter 12 when we examine security policy.

If you run the snap-in to configure an assembly, you should navigate to the Applications/Configured Assemblies node as shown in the screenshot below. For assemblies in the GAC, you should navigate to the Configured Assemblies node:

click to expand

Right-clicking on the Applications node will bring up a context menu that allows you to add applications to configure. The screenshot above shows that I have three applications for which I've overridden probing policy. In particular, for my assembly called GroovyTest, I've set up an override for the referenced assembly, GroovyLibrary.dll. Double-clicking on this assembly in the listview allows us to set the custom probing rules for this assembly:

click to expand

In this screenshot, you can see that I've asked that, if the GroovyTest.exe application requests to load any version of the library between 1.0.0.0 and 1.0.9999.9999, the CLR should load version 2.0 instead - making sure that a newer version of the library is used.

If I navigate to the folder containing GroovyTest.exe, I can see what mscorcfg.msc has actually done. Beside the GroovyTest.exe assembly is a new configuration file, GroovyTest.exe.config, which contains this code:

 <?xml version="1.0"?> <configuration>   <runtime>     <assemblyBinding xmlns = "urn:schemas-microsoft-com:asm.v1">       <dependentAssembly>         <assemblyIdentity name="GroovyLibrary" />         <bindingRedirect oldVersion="1.0.0.0-1.0.9999.9999"                          newVersion="2.0.1.0" />       </dependentAssembly>     </assemblyBinding>   </runtime> 

The key XML element here is the <bindingRedirect> element - that's the one that tells the CLR to load version 2.0.1.0 of the GroovyLibrary instead of the one specified in the main assembly.

Redirecting a referenced assembly can be a powerful technique. I've only scratched the surface here, but that is hopefully enough to point you in the right direction to explore further.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net