本文是为了学习程序集而整理的网上资料,主要包括两个部分,概念和使用,前部分讲怎样理解程序集,后部分讲述怎样使用的细节。
程序集与托管模块的概念
我相信现在大家对程序集和托管模块分别是什么以及两者间的关系有了较好的理解。但是如果有源代码辅助一下那就更好了。是的。对程序员来说,源代码比什么都亲切。好的,下面就举两个简单的例子(用C#语言表述)。一个是单模块程序集,一个是多模块程序集。两者都是前台程序集(后缀名是exe)。我用Visual C# 2003 集成开发环境试了一下,竟然发现它不支持多程序集的开发(希望是我没有找到)。没关系,我们还有DotNet FrameWork SDK呢,不用怕。它自带的C#编译器csc.exe很好用。至于csc的用法我就不多说了。
/* *hello.cs 在控制台上显示一行字符串 */ using System; namespace nsApp { public class CEnterPoint { static void Main() { Console.WriteLine("Hello, verybody!"); } } }
/* Dog.cs */ using System; namespace nsZoo { public class Dog { public void SayHello() { Console.WriteLine("I am a dog, wang wang wang"); } } } /* Cat.cs */ using System; namespace nsZoo { public class Cat { public void SayHello() { Console.WriteLine("I am a cat, miao miao miao"); } } } /* Main.cs */ using System; using nsZoo; namespace nsApp { class CEnterPoint { static void Main(string[] args) { Dog aDog = new Dog(); Cat aCat = new Cat(); aDog.SayHello(); aCat.SayHello(); Console.ReadLine(); } } }
任何时候构建一个EXE或DLL文件时必须使用/t:library编译参数创建与该应用程序相对应的包含清单(manifest)的程序集,清单(manifest)记录了.NET运行时程序集的相关信息。另外,使用/t:module编译参数,创建一个以.netmodule为扩展名的DLL 文件,这个DLL文件不包含清单。换句话说,虽然逻辑上模块(module)依然是一个DLL文件,但是它不属于程序集,当编译应用程序或使用程序集生成工具(the Assembly Generation tool)时必须使用/addmodule开关将这个DLL文件加入到其他程序集中。
清单数据(Manifest Data)
程序集的清单有不同的方法存储。若编译一个独立的应用程序或DLL文件,清单与程序或DLL的PE结合在一起,这称为单一文件的程序集(single-file assembly)。多文件程序集(multifile assembly)是指清单以另外一个独立实体的形式作为程序集的一部分,或者作为程序集中的一个模块。
同样程序集的定义很大程度取决你如何使用它。从客户的角度来看,程序集是模块、导出类型、(必需、可选)资源的命名和版本化(versioned)的集合。从程序集创建者的角度来看,程序集是客户能使用的一组相关联的模块、类型、资源、导出的方法的封装包(mean of packaging)。也就是说,清单是程序集和用户之间沟通的桥梁。下面列出程序集中清单包含的信息:
· 程序集名称(Assembly name)
· 版本信息(Versioning information)
版本信息是由四个不同的部分组成的版本号,分别是主版号、子版号、构建序号、修订号。
· (可选的)共享名和带符号的程序集散列(An(Optional)shared name and signed assembly hash)
主要是关于程序集部署的相关信息。
· 文件(Files)
包含在程序集中的文件列表。
· 引用的程序集(Referenced assemblies)
直接引用的外部程序集列表。
· 类型(Types)
程序集内部的所有类型及模块映像包含的类型的列表。(This is list of all types in the assembly with a mappig to containing the type.)
· 安全(Security)
安全权限的列表,权限已明确地被程序集抛弃。(permissions This is a list of security permissions that are explicity refused by the assembly.)
· 定制属性(Custom attributes)
· 产品信息 包含公司、商标、产品及版权信息。
程序集的优势
· 程序集的封装:将多个模块封装到一个物理文件,起到性能优化的作用。当你创建一个应用程序并使用多文件程序集时,.NET运行时只需加载相关的模块。这种策略起到减少应用程序的工作集。
· 程序集的部署
在.NET框架中程序集是最小的部署单元。虽然它诱人的说,程序集是部署应用程序的方法,但是技术上并不是如此。许多关于程序集部署的正确的观点是:在.NET中程序集应该看作一个窗体的类部署(class deployment)-就像Win32中的一个DLL文件。一个单独的应用程序是由多个程序组成的。
因为程序集是自描述的(self-desciribing),所以部署程序集最简单的方法就是直接把程序集复制到目的文件夹。当尝试运行包含这个程序集的应用程序时,清单将向.NET运行时提供包含在这个程序集中的方法信息。另外,清单(manifest)也向应用程序提供该程序集所引用的外部程序集的相关信息。
许多通过公共方法部署的程序集依然是私有的程序集,即程序集被复制到文件夹,但它们不是共享的。缺省情况下程序集是私有的,除非明确地指定程序集为共享程序集(shared assembly)。
· 程序集的版本
使用程序集另外一个主要优势是程序集内置了版本号。程序集中止了DLL地狱。DLL地狱是指当一个应用程序重写了一个被其他应用程序引用的DLL文件,而该应用程序引用的是低版本的同名DLL文件,这就造成了引用低版本DLL的应用程序运行出错,这种情况。虽然Win32资源文件格式内置了版本资源类型,但是操作系统不会强制执行任何版本控制,以便依赖这个DLL文件的应用程序能正常运行。
基于这个问题,清单不仅包含程序集本身的版本信息,也包含了该程序集所有被引用的程序集和这些程序集的版本信息。因为有了这个体系结构,.NET运行时能确保版本规则被支持,当出现新版本的程序集时,版本不兼容的共享DLLs由操作系统自动安装,使得应用程序可以继续正常运行。
用多个模块创建程序集(Creating Assembies with Multiple Modules)
// NetModuleTestServer.cs // Build with the following command-line switches: // csc /t:library NetModuleTestServer.cs public class NetModuleTestServer { public static void Bar() { System.Console.WriteLine("NetModuleTestServer.Bar(NetModuleTestServer.netmodule)"); } } // NetModuleTestClientApp.cs // Build with the following command-line switches: // // csc addmodule:NetModuleServer.netmodule NetModuleTestClientApp.cs using System; using System.Diagnostics; using System.Reflection; class NetModuleTestClientApp { public static void Main() { Assembly DLLAssembly = Assembly.GetAssembly( typeof(NetModuleTestServer)); Console.WriteLine(""nNetModuleTestServer.dll Assembly " + "Information"); Console.WriteLine(""t" + DLLAssembly); Process p = Process.GetCurrentProcess(); string AssemblyName = p.ProcessName + ".exe"; Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName); Console.WriteLine("NetModuleTestClient.exe Assembly " + "Information"); Console.WriteLine(""t" + ThisAssembly + ""n"); Console.WriteLine("Calling NetModuleTestServer.Bar"); NetModuleTestServer.Bar(); } }
若要创建一个在多个应用程序使用的程序集,并且版本信息相当重要,则必须共享程序集。若要共享程序集,必须给程序集指定强名称。可以通过.NET SDK提供强名称工具(the Strong Name tool)创建强名称。使用强名称有四个主要理由:
· 它是.NET生成唯一的全局名称的机制
· 因为强名称工具生成的密钥对包含了一个签名,你可以判断程序集创建之后是否被篡改。(Because the generated key pair includes a singature,you can tell whether an assembly has been tampered with after its original creation.)
· 强名称能防止第三方在你构建的程序集的基础上发表新的版本。这是因为密钥对包含签名起了作用,因为第三方不知道你的私钥。
· 当.NET加载程序集时,运行时会自动验证调用者是否合法。(When .NET load an assembly,the runtime can verify that the assembly came from the publisher that the caller is expecting.)
创建强名称的步骤:
· 使用强名称工具为程序集创建一个强名称。示例:
sn -k InsideCSharp.key
· 在客户端源代码文件中添加属性AssemblyKeyFile,把强名称指定给程序集。
使用全局程序集缓存工作
.NET每次加载程序集时都会创建一个代码缓存区,通常成为全局程序集缓存。使用全局程序集缓存有三个作用:
· 存储从Internet或文件服务器下载的代码。注:从一个特定的应用程序加载的代码存储在缓冲中的私有区域,以防止其他程序集访问。
· 存储被多个.NET应用程序共享的组件数据。利用全局缓存工具将程序集加载到缓存全局区域,使得该程序集可以被本地机器的所有应用程序访问。
· 程序集的本地代码是预运行时编译的,同时存储在全局程序集缓存区。(native code versions of assemblies that have been preJITted are stored in the cache.)
缓存视图
1、在windows"assembly文件夹中可以查看程序集的相关信息,如版本号、语言(culture)、公钥标记等等信息。
// DllTestServer.cs // Build with the following command-line switches: // csc /t:library DllTestServer.cs public class DllTestServer { public static void Foo() { System.Console.WriteLine("DllTestServer.Foo " + "(DllTestServer.DLL)"); } } // DllTestClient.cs // Build with the following command-line switches: // csc DllTestClient.cs /r:DllTestServer.dll using System; using System.Diagnostics; using System.Reflection; class DllTestClientApp { public static void Main() { Assembly DLLAssembly = Assembly.GetAssembly(typeof(DllTestServer)); Console.WriteLine(""nDllTestServer.dll Assembly " + "Information"); Console.WriteLine(""t" + DLLAssembly); Process p = Process.GetCurrentProcess(); string AssemblyName = p.ProcessName + ".exe"; Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName); Console.WriteLine("DllTestClient.exe Assembly " + "Information"); Console.WriteLine(""t" + ThisAssembly + ""n"); Console.WriteLine("Calling DllTestServer.Foo"); DllTestServer.Foo(); } }