程序集的四部分名称与位置无关。即,与程序集 DLL 的名称无关,该程序集 DLL告诉公共语言运行库 (CLR) 或宿主应用程序有关实际程序集文件位置的信息。因为程序集 DLL 能够以几种不同的方式部署到许多不同的位置,所以这提供了难能可贵的灵活性。这还意味着 CLR 必须使用程序集名称以外的内容来在运行时查找它。
主要有三种方法来在目标计算机上部署程序集。第一种方法涉及到通过在 ApplicationBase 目录中查找 DLL 来将其作为私有程序集来部署。第二种方法涉及到在名为全局程序集缓存 (GAC) 的计算机范围内的储存库中安装 DLL。第三种方法涉及到用<codeBase>元素配置程序集 DLL,该元素允许 CLR 在宿主应用程序第一次使用 DLL 时,从网络中按需下载它。
本页内容
![]() | 私有程序集 |
![]() | 全局程序集缓存 |
![]() | 使用配置好的基本代码部署程序集 |
![]() | 下载缓存 |
![]() | 程序集加载 |
![]() | 小结 |
私有程序集
将 DLL 作为私有程序集部署是可以采用的最简单的方法。如果设置了 PrivateBinPath 属性,则可以将 DLL 部署到宿主应用程序的 ApplicationBase 目录或者 ApplicationBase 目录的子目录中。在大多数情况下,它的确是那么容易。
私有程序集部署的最大好处之一是它允许部署 XCOPY。这是由于应用程序、它的配置文件及其所有的私有程序集都包含在单个目录结构中。在应用程序及其私有程序集经过彻底测试之后,您只需使用诸如 XCOPY.EXE 的实用工具或者诸如 FTP 的网络传输协议,将 ApplicationBase 目录结构复制到目标计算机上,即可部署整个应用程序。您甚至只需通过在 Windows Explorer 中使用拖放功能,即可部署应用程序。在复制了 ApplicationBase 目录结构以后,就可以使用应用程序了(假设公共语言运行库存在)。
私有程序集部署有一个值得注意的局限,那就是它决不能部署到 ApplicationBase 目录的外部。您不能在两个或多个已部署到单独目录中的应用程序之间共享私有程序集。
那么,CLR 如何查找和加载私有程序集呢?唔,CLR 通过一个名为探测的搜索过程在运行时发现物理路径。当 CLR 开始探测私有程序集时,它通过提取程序集的友好名称并添加 .dll 扩展名来确定程序集的文件名。
一旦 CLR 确定了目标程序集文件的名称,它先在 GAC 中查看是否有目标程序集,然后在 ApplicationBase 目录内部进行搜索,查看目标程序集是否在该目录中。如果它在其中,探测会停止,CLR 会将程序集加载到内存中。否则,探测进程在 ApplicationBase 目录下与程序集本身同名的子目录中继续。例如,如果 CLR 正在探测名为 MyLibrary 的程序集,它将查看 ApplicationBase 目录中是否有名为 MyLibrary 且包含程序集文件 MyLibrary.dll 的子目录。
如果 CLR 在该子目录中找到程序集文件,则加载该程序集。如果找不到,CLR 会继续在相同的两个目录中探测具有 .exe 扩展名(而不是 .dll 扩展名)的程序集文件。这意味着 CLR 在探测私有程序集时自动查找四个不同的路径。如果应用程序将要拥有一个路径为 C:/MyApp 的 ApplicationBase 目录,CLR 将使用以下四个文件路径来自动探测程序集文件:
c:/MyApp/MyLibrary.dll c:/MyApp/MyLibrary/MyLibrary.dll c:/MyApp/MyLibrary.exe c:/MyApp/MyLibrary/MyLibrary.exe
因此,CLR 将在探测与区域性无关的程序集时自动检查四个文件路径。当 CLR 正在探测具有区域性标识符的附属程序集时,它还将在与区域性标识符本身同名的子目录中查找。有了这个额外的支持,在部署多个仅限资源且已本地化为不同语言的程序集时会容易得多。
请注意,尽管您可以在 ApplicationBase 目录下的任何子目录中部署私有程序集,如果您选择的名称与以前介绍的不同,CLR 将需要使用额外的配置信息来帮助它进行探测。您必须向应用程序配置文件中添加特殊的<probing>元素,以便为 CLR 提供线索。
让我们假设您希望在 ApplicationBase 目录内部创建名为 MyAssemblies 的子目录,然后希望在 MyAssemblies 内部部署一些依赖程序集。只有在您修改应用程序配置文件以包括探测元素以后,应用程序才能够加载这些私有程序集,如下所示:
<configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="MyAssemblies"/> </assemblyBinding> </runtime> </configuration>
正如您所看到的那样,<probing>元素包含 privatePath 属性,该属性通知 CLR 在何处查找。如果您希望在私有路径中添加多个子目录,则可以使用通过分号分隔的字符串来将它们连接起来。
还应当记住的是,CLR 在探测过程中检查预先确定的目录序列。假设 MyAssemblies 的值为 privatePath,CLR 现在将按如下顺序探测名为 MyLibrary 的程序集:
C:/Apps/MyLibrary.DLL C:/Apps/MyLibrary/MyLibrary.DLL C:/Apps/MyAssemblies/MyLibrary.DLL C:/Apps/MyAssemblies/MyLibrary/MyLibrary.DLL C:/Apps/MyLibrary.EXE C:/Apps/MyLibrary/MyLibrary.EXE C:/Apps/MyAssemblies/MyLibrary.EXE C:/Apps/MyAssemblies/MyLibrary/MyLibrary.EXE
CLR 在其中探测程序集文件的文件路径的顺序非常重要,这是由于在 CLR 找到具有正确文件名的程序集文件之后,探测进程会停止。如果您将应用程序和某个版本的 MyLibrary.dll 部署到 ApplicationBase 目录中,将另一个版本部署在 MyAssemblies 子目录中,那么,CLR 将加载哪个 DLL 文件?您应当会发现,CLR 将要加载 ApplicationBase 目录中的 DLL,这是由于在探测过程中,CLR 总是首先在该目录中进行搜索。
全局程序集缓存
不能使用私有程序集部署来在同一台计算机上的几个应用程序之间共享程序集 DLL。为此,最好的方法是在 GAC 中安装程序集。
在 GAC 中安装程序集 DLL 会消除应用程序和依赖 DLL 之间的路径依赖性问题。这不会影响应用程序的 ApplicationBase 目录的位置+。只要在 GAC 中安装了程序集 DLL,CLR 总是能够找到并加载它。
使用 GAC 还有另外一个重大好处,那就是,只要您需要,就可以在单个计算机上安装同一个程序集 DLL 的任意多个版本。这非常重要,因为不同的应用程序可以加载最适合它们的 MyLibrary.dll 版本,这举例说明了 GAC 如何促进并行程序集部署。
GAC 设计为程序集的安全储存库。因此,CLR 对于在 GAC 中安装 DLL 设计了两个重要限制。首先,除非您在目标计算机上具有 Administrators 或 Power Users 特权,否则您就不能从 GAC 中添加或删除程序集。第二,除非 GAC 中包含强名称,否则您就不能在其中安装程序集,这是由于在设计上,当您在 GAC 中安装程序集 DLL 时,CLR 会针对程序集的数字签名执行强名称验证检查,从而确保 GAC 只包含那些已经由拥有正确私钥的用户签名的程序集。
一个值得注意的有趣现象是,当 CLR在运行时在 GAC 中加载程序集时,它不针对程序集的数字签名执行强名称验证检查。这是因为 CLR 假设已经验证了 GAC 中的程序集。因此,在 GAC 中部署程序集会稍微提高性能。如果不想付出在运行时执行强名称验证检查的代价,就只能从 GAC 加载强名称的程序集。
现在,让我们讨论一下 CLR 如何在内部管理 GAC¡£CLR 包含一个名为程序集管理器的系统组件,该组件负责将程序集文件存储在 GAC 中,并在这些文件首次由应用程序使用时在运行时加载它们。程序集管理器是从系统组件 FUSION.DLL 加载的。
我必须指出,程序集管理器在目标计算机上存储和检索程序集文件的方式应当被视为 CLR 的私有实现细节。我打算描述其中的一些细节,其目的只是为了使您更好地了解 GAC 如何工作。您所设计的应用程序或者所使用的部署方法决不要依赖于这些私有细节,因为它们有可能会在未来版本的 CLR 中有所更改。
程序集管理器使用 Windows 文件系统中特殊的目录结构,将程序集文件存储在 GAC 中。该目录结构是作为 Windows 目录中的子目录创建的,其路径如下所示:
C:/Windows/assembly/GAC
当您将程序集安装到 GAC 中时,程序集管理器会创建存储它们的新目录。实际上,程序集管理器将为存储在 GAC 中的每个程序集创建一个唯一的目录。这样做的原因在于,GAC 必须能够容纳两个不同的程序集,而且这两个程序集的名称只是公钥值或版本号不同。毕竟,可能会存在许多文件名为 MyLibrary.dll 的不同程序集。因此,程序集管理器会使用程序集名称的全部四个部分为每个程序集创建唯一的目录。例如,设想您在 GAC 中安装 1.0.24.0 版的 MyLibrary.dll,而且此程序集有一个公钥标记 29989d7a39acf230。当您安装时,程序集管理器会创建具有如下路径的新目录:
C:/WINDOWS/assembly/GAC/MyLibrary/1.0.24.0__29989d7a39acf230
正如您所看到的那样,程序集管理器在将程序集存储到 GAC 中时,会使用其目录各自的内部命名方案。当程序集管理器开始加载具有特定四部分名称的程序集时,因为该程序集符合同样的命名方案,所以程序集管理器知道在何处查找它。
当您希望将程序集安装到 GAC 中时,无需与 FUSION.DLL 直接交互,而是使用已经为您编写的、与 FUSION.DLL 进行交互的实用工具。如果您希望将程序集安装到用于测试的开发工作站上的 GAC 中,您可以使用 Microsoft.NET Framework SDK 提供的名为 GACUTIL.EXE 的命令行实用工具。GACUTIL.EXE 提供了许多用来在 GAC 中安装和管理程序集的命令行开关。例如,/i 开关用于程序集的一般安装,如下所示:
GACUTIL.EXE /i MyLibrary.dll
为了使用该方法在 GAC 中安装某个程序集,您必须能够访问该程序集文件。但是,还要特别注意的是,在将程序集文件安装到 GAC 中时,程序集管理器会创建程序集文件的副本。在安装之后,程序集管理器只关心它所创建的副本。这意味着,在将初始程序集文件安装到 GAC 中之后,您可以删除它。如果您从 CD 安装程序集文件,则可以从驱动器中取出 CD,而不会出现任何问题;如果您从网络安装程序集文件,则可以断开网络连接,这同样不会出现问题。
您还可以使用名为程序集缓存查看器的基于 GUI 的实用工具来检查和管理 GAC 中的程序集,程序集缓存查看器是一个管理实用工具,该工具作为名为 SHFUSION.DLL 的 Windows shell 扩展透明地运行在 Windows 资源管理器中。该实用工具位于 /Windows/assembly 目录下(请参阅图 1)。
![](https://i-blog.csdnimg.cn/blog_migrate/427e1ee85d757c28d60369e8ff54ea49.png)
图 1 程序集缓存查看器
请注意,由程序集缓存查看器提供的视图实际上不会向您显示由 GAC 维护的目录结构的物理布局。相反,它向您显示平铺视图,在该视图中,所有的程序集都作为单个可滚动列表显示。您还应当注意,对于每个程序集都显示了其友好名称、版本号、区域性和公钥标记。
程序集缓存查看器为管理员和开发人员提供了从 GAC 中安装和删除程序集的简单方法。如果您希望安装程序集,则可以将它从 Windows 资源管理器拖放到程序集缓存查看器中。当您希望从 GAC 中删除程序集时,可以在程序集缓存查看器中选择它,并按键盘上的 Delete 键。
请记住,FUSION.DLL 是唯一一个允许向 GAC 中读写文件的组件。当您使用程序集缓存查看器管理程序集时,应当了解 Windows shell 扩展 SHFUSION.DLL 正在后台与 FUSION.DLL 进行交互,从而执行您发出的命令。
使用配置好的基本代码部署程序集
在目标计算机上部署依赖程序集的最后一个选项是使用<codeBase>元素对其进行配置。配置好的<codeBase>元素之所以功能强大,是因为它允许您通过网络下载程序集 DLL。这意味着在应用程序首次使用程序集 DLL 时,CLR 可以根据需要将它下载到目标计算机。
您可以使用<codeBase>元素在目标计算机上的任何目录中部署具有强名称的程序集,甚至还可以在文件服务器或 Web 服务器上部署具有强名称的程序集,然后可以使用<codeBase>元素在目标计算机上配置应用程序,以便根据需要下载该程序集。如果您打算使用<codeBase> 元素来部署程序集,则可以用强名称来构建它们,以便允许获得这种额外的灵活性。
图 2 显示了应用程序特定的<codeBase>元素的示例。请注意,您可以向 machine.config 文件(而非应用程序配置文件)中添加<codeBase>元素,以便在计算机范围内管理依赖程序集。
请检查图 2中显示的<codeBase>元素。请注意,必须将<codeBase>元素放在<dependentAssembly> 元素中。在本例中,<dependentAssembly>元素内部也有一个<assemblyIdentity>元素,后者具有一些属性来标识所配置的依赖程序集的友好名称和公钥标记。
尽管<dependentAssembly>元素只包含一个<assemblyIdentity> 元素,但是它可以有许多内部<codeBase>元素,这是由于程序集的每个独立版本都需要一个单独的<codeBase>元素。图 2中的<dependentAssembly>元素只包含一个版本为 1.0.24.0 的<codeBase> 元素,但是您可以为该程序集的其他版本添加其他<codeBase> 元素。
请注意,<codeBase> 元素除了包含版本属性以外,还包含一个 href 属性。href 属性的目的在于,为 CLR 提供统一资源标识符 (URI) 以允许它确定程序集文件的位置。如果您希望使用<codeBase> 元素在本地文件系统上配置依赖程序集,则应当使用具有类似如下 URI 的 href 属性:
href="file:///c:/AcmeCorpSharedAssemblies/MyLibrary.dll"
如果您希望将依赖程序集配置为使用统一命名约定路径名从文件服务器下载,则应当使用类似如下的 URI 来配置 href 属性:
href="file://AcmeCorpFileServer1/Downloads/MyLibrary.dll"
如果您希望将依赖程序集配置为使用 HTTP 从 Web 服务器下载,则应当用类似如下的 URI 来配置 href 属性:
href="http://www.AcmeCorp.com/Downloads/MyLibrary.dll"
请记住,您还可以使用 .NET Framework 配置管理工具 MSCORCFG.MSC 为依赖程序集配置<codeBase>元素,MSCORCFG.MSC 是一个 Microsoft 管理控制台 (MMC) 管理单元,可以从 Windows“开始”菜单下“管理工具”组中的快捷方式启动。这样,就无需直接处理进入到应用程序配置文件或 machine.config 文件中的 XML。顺便说一句,当您开始配置应用程序及其依赖程序集时,一定要知道 Microsoft .NET Framework 配置管理工具是基于 GUI 的方便的工具。图 3 显示了该实用工具的 UI。
![](https://i-blog.csdnimg.cn/blog_migrate/a6adc01a3593fc0f7c924a0ae89b0e73.png)
图 3 .NET Framework 配置
.NET Framework 配置管理工具之所以方便,是因为它可以为您创建应用程序配置文件。您只需通过与标准的 Windows 控件(如文本框、单选按钮和复选框)进行交互即可配置应用程序。当您以这种方式配置应用程序时,Microsoft .NET Framework 配置管理工具会执行创建适当的 XML 内容并将其添加到应用程序配置文件中这些繁琐的工作。
下载缓存
现在,让我们讨论一下当 CLR 的程序集管理器使用<codeBase>元素,通过网络下载依赖程序集时所发生的事情。程序集管理器不直接将程序集文件加载到正在运行的应用程序的内存中,相反,程序集管理器下载程序集文件并将其写入到磁盘中名为下载缓存的临时存储区域中。
这个缓存方案具有明显的优势。没有必要通过网络多次复制程序集文件。程序集文件只需在应用程序首次使用时由程序集管理器下载并保存到磁盘中。程序集管理器可以从本地硬盘驱动器上的下载缓存中加载程序集文件。
在下载缓存和 GAC 之间有两个重要区别。GAC 确实是计算机范围内的储存库,而下载缓存不是。下载缓存实际上是由 CLR 基于每个用户管理的。例如,如果名为 Bob 的用户运行某个应用程序,该应用程序使用<codeBase>元素通过网络下载程序集文件,那么,程序集管理器会将程序集文件存储在该用户的私有子目录下的如下路径中:
C:/Documents And Settings/Bob/Local Settings/Application Data/
另一个重要区别就是,因为 GAC 是本地的,所以程序集管理器将它视为程序集的完全受信任的安全储存库。下载缓存就不太安全且不是完全受信任的。因此,程序集管理器将从下载缓存加载的程序集视为受制于其他安全限制的移动代码。一定要知道这一点,因为 CLR 在受限制的沙箱中运行移动代码,从而防止主机受到攻击。
程序集加载
现在,可以讨论当程序集管理器需要将依赖程序集加载到正在运行的应用程序中时,在运行时所发生的情况。程序集管理器首先将格式字符串拆分为它要查找的程序集的四部分名称。通常,程序集管理器通过在另一个程序集的清单中查找引用来确定格式字符串。
让我们看一个示例。设想您运行 MyApp.exe,而且它执行第一行代码,该代码要求 CLR 加载 MyLibrary.dll。程序集管理器检查 MyApp.exe 的程序集清单,并发现 MyLibrary.dll 的程序集文件的四部分名称,在对所使用的应用程序进行编译时就已经存在 MyLibrary.dll。如果 MyLibrary.dll 有一个强名称,程序集管理器就会生成一个包含公钥标记的格式字符串,如下所示:
MyLibrary, Version=1.0.24.0, Culture=neutral, PublicKeyToken=29989D7A39ACF230
此时,程序集管理器具有在编译 MyApp.exe 的过程中就已经存在的依赖程序集的四部分名称。在下一步中,程序集管理器会查看是否有任何配置信息来对应用程序进行重定向,以便让其使用另一个版本号。让我们假设在本讨论中,版本号将不进行重定向。
在程序集管理器知道了它要查找的程序集的四部分名称后,现在就可以开始搜索程序集文件了。程序集管理器按如下方式执行其搜索。首先,它检查配置文件的重定向位置(如<codeBase>),然后在 GAC 中搜索程序集。接着,它使用<codeBase>元素中的配置信息搜索程序集。最后,它通过在 ApplicationBase 目录中进行探测来搜索程序集。
程序集管理器总是在 GAC 中进行查找具有重要的含义。设想一下,如果您部署了同一个程序集的三个副本,将会发生什么情况。例如,设想您已经将程序集 DLL 的一个副本放在 ApplicationBase 目录中,使用<codeBase>元素部署了第二个副本,并将第三个副本安装到 GAC 中。程序集管理器将总是从 GAC 中加载程序集。一旦它在 GAC 中找到程序集,就会停止搜索。
如果程序集管理器查找配置好的<codeBase>元素并找到一个,它随后会查看 URI 是否指向本地硬盘上的位置。如果指向的话,程序集管理器会直接从磁盘上的该位置加载程序集文件。
如果<codeBase>元素中的 URI 指向另一台计算机上的程序集文件,程序集管理器知道它只能从下载缓存加载程序集文件的本地副本。因此,程序集管理器会检查下载缓存,查看程序集文件是否已经下载。如果尚未下载,则程序集管理器会将它从网络复制到下载缓存中。当程序集文件下载到下载缓存中之后,程序集管理器将对其进行加载。
如果程序集管理器在 GAC 中找不到程序集文件,而且它发现该程序集没有配置好的<codeBase> 元素,它会探测 ApplicationBase 目录。程序集管理器通过以下方法来执行探测过程:直接在 ApplicationBase 目录中查找,然后按照以前在有关私有程序集的章节中介绍的规则在各个子目录中查找。
正如您所能想到的那样,当 CLR 尝试加载程序集时,会出现很多问题。例如,如果程序集管理器在 ApplicationBase 目录中探测时找不到程序集文件,它会将 FileNotFoundException 引发到一行代码中,该行代码导致程序集管理器首先查找程序集。CLR 会在以下情况下引发 FileNotFound 异常:当它找不到在<codeBase>元素中配置的程序集文件时,以及当它确定尝试加载的程序集没有正确的版本号、区域性设置或公钥值时。
小结
本月,我介绍了三种用来部署程序集 DLL 的主要方法。私有程序集部署很有价值,因为它提供了 XCOPY 部署具有的所有优点。但是它也有一些局限,因为您必须将程序集 DLL 部署到 ApplicationBase 目录结构中。在 GAC 中部署程序集非常有用,因为它允许您在几个不同的应用程序之间共享程序集 DLL,而与每个应用程序的 ApplicationBase 目录所在的位置无关。GAC 还提供了程序集作为完全受信任的安全储存库所带来的好处。最后,如果使用 元素部署程序集 DLL,则允许您将具有强名称的程序集部署到本地硬盘驱动器上的任何位置或者网络上的任何可访问位置。使用 元素主要有两个好处。首先,您不限于将程序集 DLL 部署到 ApplicationBase 目录结构中。第二,您可以将程序集 DLL 配置为由 CLR 按需从网络中下载。