11.5 私有程序集
到现在为止,我们在本章中所创建的程序集都被部署为私有程序集。私有程序集要求放置在客户端应用程序所在目录(应用程序目录)或者其子目录下。在先前构建CSharpCarClient.exe和VbNetCar- Client.exe过程中,添加对CarLibrary.dll的引用的时候,Visual Studio 2005会把CarLibrary.dll复制到客户端应用程序目录下。
当一个客户端程序使用定义在外部程序集的类型时,CLR只是加载CarLibrary.dll本地的副本。由于.NET运行库在查找被引用的程序集过程中并不查询注册表,因此我们可以把CSharpCarClient.exe (或者VbNetCarClient.exe)和CarLibrary.dll程序集一起放到机器上的某个位置,然后运行应用程序(这就是时常听到的Xcopy部署)。
卸载(复制)排他性使用私有程序集的应用程序非常容易,只需直接删除(复制)应用程序文件夹就可以了。不需要像使用COM应用程序那样担心许多遗留在注册表的设置。更重要的是,不需要担心移除私有程序集会破坏机器上其他应用程序的正常运行。
11.5.1 私有程序集的标识
私有程序集的全标识包括友好名称和数字版本号,两者都被记录在程序集的清单中。友好名称就是模块(包含程序集清单的)名字减去文件扩展名。例如,如果查看CarLibrary.dll程序集,会看到如下内容(读者的版本号会有所不同):
.assembly CarLibrary
{
...
.ver 1:0:454:30104
}
鉴于私有程序集的独立性,CLR在解析它的位置时并不忙于使用它的版本号。我们假定私有程序集并不需要详细检查版本,因为客户端应用程序是唯一知道其存在的实体。因此,一台机器上很有可能会有同一个私有程序集的副本在不同的目录下多次出现。
11.5.2 探测过程
.NET运行环境使用一种叫探测(probing)的技术解析私有程序集的位置,这项技术其实远不如它的名字听起来那样具有侵略性。探测是一种把外部程序集请求映射到被请求的二进制文件位置的过程。严格来说,一个加载请求可以是显式的或者隐式的。隐式的加载请求发生在CLR查询清单的.assembly外部标记来解析程序集位置的时候:
// 隐式加载请求。
.assembly extern CarLibrary
{ ...}
显式的加载请求则发生在以编程方式调用System.Reflection.Assembly类的Load()或者LoadFrom()方法时,这两个方法主要在后期绑定或者动态调用类型成员时用到。第12章有相关的详细介绍,以下就是一个显式加载请求的示例代码:
// 显式加载请求。
Assembly asm = Assembly.Load("CarLibrary");
不管是隐式还是显式,在CLR获得程序集的友好名称之后,便开始探测客户端应用程序目录下的CarLibrary.dll文件。如果找不到文件,它就尝试去查找具有相同友好名称的可执行程序集(CarLibrary.exe)。如果还是找不到,运行库就在运行时抛出FileNotFound的异常。
注解 如果在客户端应用程序目录下找不到被请求的程序集复制,CLR会尝试查找该目录下具有程序集友好名称的子目录(例如C:\MyClient\CarLibrary)。如果被请求的程序集在这个子目录下,CLR就可以成功地把该程序集加载到内存中。
11.5.3 配置私有程序集
虽然可以采用这种部署方式——即把所需的程序集复制到(用户硬盘中)单个文件夹,但是我们可能更想定义多个子目录来把相关内容进行更好地区分。例如,假设应用程序目录是C:\MyApp,其中包含CSharpCarClient.exe。在该目录下有一个名为MyLibraries的子目录,其中放有CarLibrary.dll。
不考虑两个目录的关系,只有提供一个配置文件,CLR才会探测到MyLibraries子目录。配置文件包含多个XML元素,它们可以决定探测的过程。根据“规则”,配置文件必须拥有和运行的应用程序相同的名称,带*.config的文件扩展名,同时,它们必须被部署到客户端应用程序目录下。因此,如果为CSharpCarClient.exe创建配置文件的话,该配置文件的名称必须是CSharpCarClient.exe.config。
下面让我们实践一下。首先使用Windows资源管理器在C盘创建一个名为MyApp的新目录。然后将CSharpCarClient.exe和CarLibrary.dll复制到这个目录,双击前者运行程序。你的程序应该可以成功运行(记住,该程序集并没有注册)。接着在C:\MyApp下创建一个名为MyLibraries的子目录(如图11-11所示),移动CarLibrary.dll到该目录中。

图11-11 现在CarLibrary.dll处于MyLibraries子目录下
再次尝试运行程序。由于CLR在应用程序目录下不能直接找到CarLibrary,因此你会收到一个令人厌恶的且未被处理的FileNotFound异常。
为了矫正这种情况,让我们创建一个名为CSharpCarClient.exe.config的配置文件,然后把它放到CSharpCarClient.exe所处的目录下(C:\MyApp)。打开该文件,输入以下内容(注意XML是区分大小写的):
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="MyLibraries"/>
</assemblyBinding>
</runtime>
</configuration>
.NET的*.config文件通常以<configuration>元素作为根元素。内嵌的<runtime>元素可能还定义了一个<assemblyBinding>子元素,该子元素会进一步内嵌一个<probing>元素。在这个例子里面,<probing>元素的privatePath特性是关键,它用于通知CLR需要在(应用程序目录下的)哪些子目录下进行探测。
注意<probing>元素并没有指明哪一个程序集位于它指定的子目录下。换句话说,不能认为“CarLibrary就在MyLibraries子目录下,而MathUtils在Bin子目录下”。<probing>元素只是指示CLR去所有指定的子目录去查找被请求的程序集,直到找到第一个匹配的。
注解 注意privatePath特性并不能用于指定一个绝对路径(C:\SomeFolder\SomeSubFolder)或者一个相对路径(..\\SomeFolder\\AnotherFolder)!如果想指定应用程序目录以外的另一个目录,必须使用另外一个XML元素:<codeBase>元素(该元素将会在本章后面部分详细介绍)。
使用分号定界符,privatePath特性可以指定多个子目录。现在并不需要这样做,但下面展示一个例子,它通知CLR去探测MyLibraries和MyLibraries\Tests这两个子目录:
<probing privatePath="MyLibraries; MyLibraries\Tests"/>
现在已经完成CSharpCarClient.exe.config的创建,在Windows资源管理器下双击客户程序。CSharpCarClient.exe应该会正常运行(如果不行,就详细检查一下配置文件,改正错漏的地方)。
为了测试一下,请改变配置文件名称(随便取一个名字),然后再次运行程序。这一次程序将会运行失败。请记住*.config文件必须以相关的应用程序名作为自己的前缀。最后再做一个测试,打开配置文件,更改其中任意XML元素的大小写。保存文件,你会发现你的程序再次不能正常运行(前面提到过,XML是区分大小写的)。
11.5.4 配置文件和Visual Studio 2005
你当然可以使用文本编辑器手动编写XML配置文件,但Visual Studio 2005允许在开发客户程序的同时创建配置文件。用Visual Studio 2005打开CSharpCarClient解决方案,选择Project→Add New Item,添加一个新的应用程序配置文件。在按下“OK”按钮前,可以看到该文件的名字为App.config(请不要重命名它,后面会解释原因)。按下按钮,将会在解决方案窗口中看到新添加的App.config文件(如图11-12所示)。
现在可以为创建的应用程序输入需要的XML元素。奇妙的事情发生了,每次编译项目时,Visual Studio 2005会自动把App.config复制到\Bin\Debug目录下,并替它改一个合适的名称(如CSharpCarClient.exe.config)。但是,请记住,做一切的前提是该配置文件的名字必须是App.config。
使用这种方法,我们需要做的事情只是维护App.config的内容,Visual Studio 2005会确保应用程序目录包含最新的内容(就算我们偶然地把项目重命名了)。
11.5.5 .NET Framework 2.0配置工具简介
尽管手动创建*.config文件并不十分烦琐,.NET Framework 2.0 SDK还是提供了一个具有友好图形界面的工具让你编写XML配置文件。我们可以在控制面板中的管理工具里找到.NET Framework 2.0配置工具。启动该工具,会看到一系列的配置选项(如图11-13所示)。

图11-13 .NET Framework 2.0配置工具
为了使用该工具创建客户程序的*.config文件,首先,通过鼠标右键单击Applications节点并选择Add,把应用程序添加到配置中。在打开的对话框中,可能会看到想要配置的应用程序(能看到的前提是曾经使用Windows资源管理器执行过它)。如果没有,按Other按钮找到需要配置的客户程序。在本例子中,选择先前创建的VbNetCarClient.exe应用程序(Bin文件夹下)。完成后,在Applications节点下将会看到名为VbNetCarClient的新节点(如图11-14所示)。

图11-14 准备配置VbNetCarClient.exe
右击VbNetCarClient节点,打开属性页,会发现在对话框底部有一个文本框供你输入前面提到过的privatePath特性的具体值。为了测试一下,我们输入名为TestDir的子目录(如图11-15所示)。
按下OK按钮,然后查看VbNetCarClient\Debug目录,可以看到原来的*.config文件(Visual Studio 2005为VB.NET程序提供的)里面的<probing>元素已经被更新。
注解 读者可能已经想到了,我们可以把.NET Framework 2.0配置工具生成的XML内容复制到Visual Studio 2005的App.config文件中,然后进行进一步的编辑。这样,就可以利用工具帮助我们生成初始的内容,减轻手动输入的工作量。






