How are DLLs loaded by the CLR?
The following copied from Don Box's excellent Essential .Net. (available here)
(and, imho, a must have for any professional .Net developer)
The CLR Loader
The CLR Loader is responsible for loading and initializing assemblies, modules, resources, and types. The CLR loader loads and initializes as little as it can get away with. Unlike the Win32 loader, the CLR loader does not resolve and automatically load the subordinate modules (or assemblies). Rather, the subordinate pieces are loaded on demand only if they are actually needed (as with Visual C++ 6.0's delay-load feature). This not only speeds up program initialization time but also reduces the amount of resources consumed by a running program. In the CLR, loading typically is triggered by the just in time (JIT) compiler based on types. When the JIT compiler tries to convert a method body from CIL to machine code, it needs access to the type definition of the declaring type as well as the type definitions for the type's fields. Moreover, the JIT compiler also needs access to the type definitions used by any local variables or parameters of the method being JIT-compiled. Loading a type implies loading both the assembly and the module that contain the type definition. This policy of loading types (and assemblies and modules) on demand means that parts of a program that are not used are never brought into memory. It also means that a running application will often see new assemblies and modules loaded over time as the types contained in those files are needed during execution. If this is not the behavior you want, you have two options. One is to simply declare hidden static fields of the types you want to interact with the loader explicitly.
The loader typically does its work implicitly on your behalf. Developers can interact with the loader explicitly via the assembly loader. The assembly loader is exposed to developers via the LoadFrom
static method on the System.Reflection.Assembly
class. This method accepts a CODEBASE string, which can be either a file system path or a uniform resource locator (URL) that identifies the module containing the assembly manifest. If the specified file cannot be found, the loader will throw a System.FileNotFoundException
exception. If the specified file can be found but is not a CLR module containing an assembly manifest, the loader will throw a System.BadImageFormatException
exception. Finally, if the CODEBASE is a URL that uses a scheme other than file:
, the caller must have WebPermission access rights or else a System.SecurityException
exception is thrown. Additionally, assemblies at URLs with protocols other than file:
are first downloaded to the download cache prior to being loaded.
Listing 2.2 shows a simple C# program that loads an assembly located at file://C:/usr/bin/xyzzy.dll
and then creates an instance of the contained type named AcmeCorp.LOB.Customer
. In this example, all that is provided by the caller is the physical location of the assembly.
When a program uses the assembly loader in this fashion, the CLR ignores the four-part name of the assembly, including its version number.
Example 2. 2. Loading an Assembly with an Explicit CODEBASE
using System;
using System.Reflection;
public class Utilities {
public static Object LoadCustomerType() {
Assembly a = Assembly.LoadFrom(
"file: //C:/usr/bin/xyzzy. dll") ;
return a.CreateInstance("AcmeCorp.LOB.Customer") ;
}
}
Although loading assemblies by location is somewhat interesting, most assemblies are loaded by name using the assembly resolver. The assembly resolver uses the four-part assembly name to determine which underlying file to load into memory using the assembly loader. As shown in Figure 2.9, this name-to-location resolution process takes into account a variety of factors, including the directory the application is hosted in, versioning policies, and other configuration details (all of which are discussed later in this chapter).
The assembly resolver is exposed to developers via the Load
method of the System.Reflection.Assembly
class. As shown in Listing 2.3, this method accepts a four-part assembly name (either as a string or as an AssemblyName reference) and superficially appears to be similar to the LoadFrom method exposed by the assembly loader. The similarity is only skin deep because the Load method first uses the assembly resolver to find a suitable file using a fairly complex series of operations. The first of these operations is to apply a version policy to determine exactly which version of the desired assembly should be loaded.
Example 2.3. Loading an Assembly Using the Assembly Resolver
using System;
using System.Reflection;
public class Utilities {
public static Object LoadCustomerType() {
Assembly a = Assembly.Load(
"xyzzy, Version=1. 2. 3.4, " +
"Culture=neutral, PublicKeyToken=9a33f27632997fcc") ;
return a.CreateInstance("AcmeCorp.LOB.Customer") ;
}
}
This is a little off your question, but you can load all the assemblies at once if you would prefer not having it happen on demand. Something like this should do the trick
foreach (AssemblyName asn in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
var asm = Assembly.Load(fn);
// I've found get types does a good job of ensuring the types are loaded.
asm.GetTypes();
}