2010-06-03 145 views
13

我的设想总是让CLR加载启动应用程序域时所需的所有DLL。然而,我写了一个让我质疑这个假设的例子。我启动我的应用程序并检查加载了多少个模块。DLL如何由CLR加载?

Process[] ObjModulesList; 
ProcessModuleCollection ObjModulesOrig; 

//Get all modules inside the process 
ObjModulesList = Process.GetProcessesByName("MyProcessName"); 
// Populate the module collection. 
ObjModulesOrig = ObjModulesList[0].Modules; 

Console.WriteLine(ObjModulesOrig.Count.ToString()); 

然后我重复完全相同的代码,我的计数是不同的。额外的DLL是C:\ WINNT \ system32 \ version.dll。

我真的很困惑,为什么计数会不同。

有人能详细说明CLR在做什么以及它是如何加载这些东西的,以及它是按照什么逻辑进行的?

回答

21

以下内容复制自Don Box的优秀必备.Net。 (可here
(和,恕我直言,一个必须有任何专业.NET开发人员)

CLR的装载机

的CLR加载程序负责加载和初始化组件,模块,资源和类型。 CLR加载器会尽可能少地加载和初始化它。与Win32加载器不同,CLR加载器不解析并自动加载下级模块(或程序集)。 相反,只有在实际需要它们的情况下(如使用Visual C++ 6.0的延迟加载功能),才会按需加载从属块。这不仅加速了程序初始化时间,而且还减少了正在运行的程序所消耗的资源量。 在CLR中,加载通常由基于类型的即时(JIT)编译器触发。当JIT编译器试图将方法体从CIL转换为机器代码时,它需要访问声明类型的类型定义以及类型字段的类型定义。此外,JIT编译器还需要访问由JIT编译的方法的任何局部变量或参数使用的类型定义。加载一个类型意味着加载程序集和包含类型定义的模块。 按需加载类型(以及程序集和模块)的策略意味着程序中未使用的部分永远不会被带入内存。这也意味着正在运行的应用程序经常会看到随着时间的推移而加载的新程序集和模块,因为在执行期间需要包含在这些文件中的类型如果这不是你想要的行为,你有两种选择。一种是简单地声明你想要与加载器明确交互的类型的隐藏静态字段。

加载程序通常会以您的名义隐式执行其工作。开发人员可以通过程序集加载器显式地与加载程序进行交互装配加载程序通过类的System.Reflection.Assembly类暴露给开发人员。此方法接受CODEBASE字符串,该字符串可以是文件系统路径,也可以是标识包含程序集清单的模块的统一资源定位符(URL)。如果找不到指定的文件,则加载器将抛出一个异常System.FileNotFoundException。如果可以找到指定的文件,但不是包含程序集清单的CLR模块,则加载程序将抛出异​​常。最后,如果CODEBASE是一个使用file:以外的方案的URL,则调用者必须具有WebPermission访问权限,否则将引发System.SecurityException异常。另外,在加载之前,首先将协议号为file:的URL下载到下载缓存中。

清单2.2显示了一个简单的C#程序,它加载位于file://C:/usr/bin/xyzzy.dll的程序集,然后创建一个名为AcmeCorp.LOB.Customer的包含类型的实例。在这个例子中,调用者提供的全部内容都是程序集的物理位置。 当程序以这种方式使用程序集加载程序时,CLR将忽略程序集的四部分名称,包括其版本号。

实施例2 2.装载的组件,带

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") ; 
    } 
} 

虽然位置装载组件是有些有趣,最组件由名称用组件装载解析器显式CODEBASE。程序集解析器使用四部分程序集名称来确定使用程序集加载程序将哪个基础文件加载到内存中。如Figure 2.9所示,此名称到位置解析过程考虑了各种因素,包括应用程序所在的目录,版本控制策略以及其他配置详细信息(本章稍后会讨论所有这些因素)。

装配解析器通过System.Reflection.Assembly类的Load方法向开发人员公开。如清单2.3所示,此方法接受一个由四部分组成的名称(作为字符串或作为AssemblyName引用),并且表面上看起来类似于程序集加载器公开的LoadFrom方法。由于Load方法首先使用程序集解析器来使用相当复杂的一系列操作来找到合适的文件,因此相似性只是皮肤很深。第一个操作是应用版本策略来确定应该加载所需组件的确切版本。

例2.3。加载程序集使用程序集解析器

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") ; 
    } 
} 
+0

是否有可能预先加载一个模块?或者它是否加载了第二个对象创建? (该dll被添加到项目的引用部分。) – priehl 2010-06-03 15:58:14

+0

@priehl,yr问题听起来像是你在设计时讨论的(当......“该dll被添加到项目的引用部分”)。这不会导致任何东西被加载。加载仅在程序运行时发生,然后它不会在程序集或模块级别发生,事实上,它发生在Type级别,即CLR在您的第一次运行程序尝试加载类型的代码时从尚未加载的类型中执行一行IL代码。 – 2010-06-03 16:31:52

+0

好的。为了测试这个,我做了以下操作 //检查使用我的例子中的方法加载的模块。 //创建一个新的Excel.Application(w /在设计时添加了dll) //检查加载的模块。 我期望加载模块的第一次检查不包括Microsoft.Office.Interop.Excel.dll,但它确实如此。 这是如何,因为共识是加载不会发生,直到您使用由dll定义的类型? – priehl 2010-06-03 16:51:52

1

CLR按需加载程序集。当你执行一个方法时,它会查看它在哪里(哪个模块等),如果没有加载,它会加载它。

下面是有关装载程序集CLR性能和谈判的article

当CLR只是在实时(JIT)编译它需要加载方法中引用的所有组件的启动方法。这意味着异常处理程序中引用的所有程序集都将被加载,即使应用程序执行的大部分时间可能都不需要它们。

这个article适用于SilverLight,但它确实谈到了CLR会发生什么。

0

无论何时创建相应的COM对象,COM DLLS都会按需加载。对于非COM DLL也可能会发生这种情况。

+0

我不认为这是正确的。我正在为此专门进行测试。我按照上面的方法检查模块。然后我创建一个Microsoft.Office.Interop.Excel。应用程序对象,然后我检查加载的模块,并在两种情况下列出Excel.dll模块。 – priehl 2010-06-03 18:42:38

+0

你什么时候启动你的excel对象?正如Kevin上面提到的,start方法中引用的所有程序集都将在启动时加载。尝试将您的实例化转换为其他方法,看看会发生什么。 – Vlad 2010-06-03 21:01:40

1

这是有点过你的问题,但你可以一次加载所有的组件,如果您不想让它发生需求。像这样的事情应该诀窍

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(); 
}