(一)程序集的含义
程序集是.NET用于部署和配置单元的术语。
.NET应用程序包含一个或多个程序集。通常扩展名是EXE或DLL的.NET可执行程序称为程序集。
程序集是自我描述的安装单元,由一个或多个文件组成。程序集可以是私有或共享的。
1、程序集的功能
- 程序集是自描述的。
- 版本的相互依赖在程序集的清单中进行了记录。
- 程序集可以并行加载。
- 应用程序使用应用程序域来确保其独立性。
- 安装非常简单。
2、程序集的结构
程序集由描述它的程序集元数据、描述导出类型和方法的类型元数据、MSIL代码和资源组成。所有这些部分都在一个文件中,或者分布在几个文件中。
3、程序集清单
程序集的一个重要部分是程序集清单,它是元数据的一部分,描述了程序集和引用它所需要的所有信息,并列出了它所有的依赖关系。
- 标识(名称、版本、文化和公钥)。
- 属于该程序集的一个文件列表。
- 被引用程序集的列表。
- 一组许可请求——运行这个程序集需要的许可。
- 导出的类型,假定它们在一个模块中定义,该模块从程序集中引用,程序集就包含他们;否则它们就不是程序集清单的一部分。
4、名称空间和程序集
名称空间完全独立于程序集。名称空间只是类型名的一种扩展,它属于类型名的范畴。
5、私有程序集和共享程序集
私有程序集或者位于应用程序所在的同一个目录下,或者位于其子目录中。在使用共享程序集时,程序集必须是唯一的,因此,必须有一个唯一的名称(称为强名)。
6、附属程序集
附属程序集是只包含资源的程序集,它尤其适用于本地化。
(二)构建程序集
1、创建程序集
在Visual Studio中,所有的C#项目都会创建一个程序集,无论是DLL还是EXE。
2、程序集的特性
1 using System.Reflection; 2 using System.Runtime.CompilerServices; 3 using System.Runtime.InteropServices; 4 5 // 有关程序集的一般信息由以下 6 // 控制。更改这些特性值可修改 7 // 与程序集关联的信息。 8 [assembly: AssemblyTitle("ConsoleApp4")] 9 [assembly: AssemblyDescription("")] 10 [assembly: AssemblyConfiguration("")] 11 [assembly: AssemblyCompany("")] 12 [assembly: AssemblyProduct("ConsoleApp4")] 13 [assembly: AssemblyCopyright("Copyright © 2017")] 14 [assembly: AssemblyTrademark("")] 15 [assembly: AssemblyCulture("")] 16 17 18 // 将 ComVisible 设置为 false 会使此程序集中的类型 19 //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 20 //请将此类型的 ComVisible 特性设置为 true。 21 [assembly: ComVisible(false)] 22 23 24 // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 25 [assembly: Guid("0f9598cd-b504-4b26-bbbb-0ccf74f16781")] 26 27 28 // 程序集的版本信息由下列四个值组成: 29 // 30 // 主版本 31 // 次版本 32 // 生成号 33 // 修订号 34 // 35 // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 36 // 方法是按如下所示使用“*”: : 37 // [assembly: AssemblyVersion("1.0.*")] 38 [assembly: AssemblyVersion("1.0.0.0")] 39 [assembly: AssemblyFileVersion("1.0.0.0")]
以上代码用于配置程序集清单。
assembly:前缀把特性标记为程序集级别特性。
3、创建和动态加载程序集
要动态的编译C#代码,可以使用Microsoft.CSharp名称空间中的CSharpCodeProvider类。
(三)应用程序域
在.NET体系结构中,应用程序除了以前的进程边界以外有了一个新的边界:应用程序域。使用托管IL代码,运行库可以确保在同一个进程中不能访问另一个应用程序的内存。多个应用程序可以运行在一个进程的多个应用程序域中。
AppDomain类用于创建和终止应用程序域,加载、卸载程序集和类型,以及枚举应用程序域中的程序集和线程。
1 namespace ConsoleApp6 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 AppDomain currentDomain = AppDomain.CurrentDomain; 8 Console.WriteLine(currentDomain.FriendlyName);//输出当前程序集的程序域 9 AppDomain secondDomain = AppDomain.CreateDomain("New AppDomain"); 10 secondDomain.ExecuteAssembly("ConsoleApp5.exe");//ConsoleApp5.exe需要放置到binDebug文件夹中 11 secondDomain.CreateInstance("ConsoleApp5", "ConsoleApp5.Demo", true, System.Reflection.BindingFlags.CreateInstance, null, new object[] { 1, 2 }, null, null);//需要引用ConsoleApp5 12 } 13 } 14 } 15 16 namespace ConsoleApp5 17 { 18 class Program 19 { 20 static void Main(string[] args) 21 { 22 Console.WriteLine("我是程序集ConsoleApp5.exe"); 23 Console.ReadKey(); 24 } 25 } 26 public class Demo 27 { 28 public Demo(int val1, int val2) 29 { 30 Console.WriteLine("Constructor with the values {0}, {1} in domain " + "{2} called", val1, val2, AppDomain.CurrentDomain.FriendlyName); 31 } 32 } 33 }
如果程序集是动态加载的,且需要在使用完以后卸载程序集,应用程序域就非常有用。在主程序于中,不能删除已加载的程序集,但可以终止应用程序域,在该应用程序域中加载的所有程序集都会从内存中清除。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 AppDomain codeDomain = AppDomain.CreateDomain("CodeDriver"); 6 var dirver = new CodeDriver(); 7 codeDomain.CreateInstanceAndUnwrap("ConsoleApp5", "ConsoleApp5.CodeDriver"); 8 bool isError; 9 string textOutput = dirver.CompileAndRun(Console.ReadLine(), out isError); 10 if (isError) 11 { 12 Console.WriteLine("Error"); 13 } 14 Console.WriteLine(textOutput); 15 AppDomain.Unload(codeDomain); 16 Console.ReadKey(); 17 } 18 } 19 public class CodeDriver : MarshalByRefObject//类需继承MarshalByRefObject类,否则无法跨应用程序域访问 20 { 21 private string prefix = "using System;" + 22 "public static class Driver" + 23 "{" + 24 " public static void Run()" + 25 "{"; 26 private string postfix = "}}"; 27 public string CompileAndRun(string input, out bool hasError) 28 { 29 hasError = false; 30 string returnData = null; 31 32 CompilerResults results = null; 33 using (var provider = new CSharpCodeProvider()) 34 { 35 var options = new CompilerParameters(); 36 options.GenerateInMemory = true; 37 38 var sb = new StringBuilder(); 39 sb.Append(prefix); 40 sb.Append(input); 41 sb.Append(postfix); 42 43 44 results = provider.CompileAssemblyFromSource(options, sb.ToString()); 45 } 46 47 48 if (results.Errors.HasErrors) 49 { 50 hasError = true; 51 var errorMessage = new StringBuilder(); 52 foreach (CompilerError error in results.Errors) 53 { 54 55 errorMessage.AppendFormat("{0} {1}", error.Line, error.ErrorText); 56 } 57 } 58 else 59 { 60 TextWriter temp = Console.Out; 61 var writer = new StringWriter(); 62 Console.SetOut(writer); 63 Type driverType = results.CompiledAssembly.GetType("Driver"); 64 driverType.InvokeMember("Run", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, null); 65 Console.SetOut(temp); 66 returnData = writer.ToString(); 67 } 68 return returnData; 69 } 70 }
(四)共享程序集
共享程序集一般安装在全局程序集缓存(GAC)中。
1、强名
共享程序集名必须是全局唯一的,并且必须可以保护该名称。任何其他人不能使用同一个名称创建程序集。
强名是一个简单的文本名称、附带版本号、公钥和文化。
2、使用强名获得完整性
在创建共享组件时,必须使用公钥/私钥对。
3、全局程序集缓存
全局程序集缓存(Global Assembly Cache,GAC)是全局使用的程序集缓存。大多数共享程序集都安装在这个缓存中,也可以使用共享目录(也在服务器上)。
GAC位于(如果没有修改VS的安装路径可参考:C:Windowsassembly)目录下。
(五)配置.NET应用程序
1、配置类别
- 启动设置——用于指定需要的运行库版本。
- 运行库设置——用于指定运行库如何进行垃圾回收,如何进行程序集绑定。
- WCF设置——用于利用WCF配置应用程序。
- 安全设置——加密配置和许可。
以下三种配置使用这些设置:
- 应用程序配置文件——包含应用程序的特性设置,如程序集的绑定信息,远程对象的配置等。这个配置文件放在可执行文件所在的目录下,例如web.config。
- 计算机配置文件——可以用于系统范围的配置(位于)。
- 发行者策略文件——由组件的创建者用于指定共享程序集可以与旧版本兼容。
2、绑定程序集
如果要在应用程序之间共享一个程序集,但不希望它在全局程序集缓存中共享它,就可以把该程序集放在一个共享目录中。
查找程序集的正确目录有两种方式:使用XML配置文件中的codeBase元素(仅用于共享程序集),或者使用probing元素(可用于私有和共享程序集)。
(1)<codeBase>
<configuration> <runtime> <assemblyBinding xml="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="ShardDemo" culture="neutral" publicKeyToken="f946433fdae2512d"/> <codeBase version="1.0.0.0" href="http://www.christiannagel.com/WroxUtil2/SharedDemo.dll"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
<codeBase>元素有特性version和href,使用version特性必须指定程序集的原始引用版本,使用href特性可以定义应从中加载程序集的目录。上面的例子,使用的是一个远程路径的共享程序集。
(2)<probing>
如果没有配置<codeBase>元素,程序集也没有存储在全局程序集缓存中,运行库就会利用probing元素来查找程序集。.NET运行库会在应用程序目录或与所搜索程序集同名的子目录中查找文件扩展名为.dll和.exe的程序集。
<configuration> <runtime> <assemblyBinding xml="urn:schemas-microsoft-com:asm.v1"> <probling privatePath="bin;utils;"/> </assemblyBinding> </runtime> </configuration>
<probing>元素只有一个必需的privatePath特性。上面的例子告诉运行库,在应用程序的根目录下搜索程序集,再在bin和util目录中搜索。
(六)版本问题
1、版本号
程序集的版本号由四部分组成:
<Major>.<Minor>.<Build>.<Revision>.
如果进行的改动与以前的兼容则改变Build和Revision,否则,修改Major和Minor。Build的值是自2000年1月1日以来的天数,Revsion的值是自午夜开始的秒数除以2。
2、通过编程方式获取版本
Console.WriteLine(Assembly.GetExecutingAssembly().FullName);
3、绑定到程序集版本
使用配置文件可以指定应绑定到共享程序集的另一个版本。
<configuration> <runtime> <assemblyBinding xml="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="ShardDemo" culture="neutral" publicKeyToken="f946433fdae2512d"/> <bindingRedirect oldVersion="1.0.0.0-1.0.3300.0" newVersion="1.0.3300.0"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
为了重定向到另一个版本上,要使用<bindingRedirect>元素。oldVersion特性指定应把程序集的那个版本重定向到新版本上。使用oldVersion特性可以指定一个范围,例如应重定向1.0.0.0-1.0.3300.0之间的所有程序集版本。新版本用newVersion特性指定。
4、发行者策略文件
使用全局程序集缓存中的共享程序集,可以使用发行者策略避免版本冲突问题。创建发行者策略文件,把所有这些应用程序重新定向到指定共享程序集的新版本上。
(1)创建发行者策略文件
发行者策略文件是一个把已有版本或某个版本范围重定向到新版本的XML文件。
(2)创建发行者策略程序集
要把发行者策略文件与共享程序集关联起来,必须创建一个发行者策略程序集,并把它放到全局程序集缓存中。
(3)将发行者策略程序集添加到全局程序集缓存中
(4)重写发行者策略
添加XML<publisherPolicy>元素和apply="no"特性,就可以禁用发行者策略。
<configuration> <runtime> <assemblyBinding xml="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="ShardDemo" culture="neutral" publicKeyToken="f946433fdae2512d"/> <publisherPolicy apply="no"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
5、运行库的版本
不仅可以安装和使用程序集的多个版本,还可以安装和使用.NET运行库(CLR)的多个版本。
<configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/> </startup> </configuration>
<supportedRuntime>的顺序可以定义系统上可用的运行库版本的优先级,sku特性定义了.NET Framework的版本。