分享

C#程序集(assembly)(精)

 悟静 2012-05-02

程序集

===============================================================================================
     ----------------------------------------------------------------------1 什么是程序集? ----------------------------------------------------------------------
DLL的版本问题,程序不知道到底该使用哪个DLL版本,从而产生中断。
DLL-Hell----〉与DLL相关的问题
.NET对DLL-Hell及其所有问题的答案是使用程序集
程序集-是自我描述的安装单元,由一个或多个文件组成,一个程序集可以是一个包括元数据的DLL或EXE,也可以由多个文件组成。
另一优点是可以使私有或共享的。
私有程序集和共享程序集有很大的区别
版本冲突问题必须在开发阶段解决
程序集的特性: 1 程序集是自我描述的;
      2 版本的互相依赖性在程序集的清单中进行了记录
      3 程序集可以进行并行加载 ,同一个DLL的不同版本可以在同一个系统上同时使用
      4 应用程序使用应用程序域来确保其独立性(Application Domain)
      5 安装非 常简单,只要复制一个程序集中的所有文件,一个xcopy命令就够了--无干涉部署(No-touch Deployment)

应用程序集和应用程序域

      -----------------------   |     ----------------------------
       进程一       进程二
____________   |   ____________
应用程序域A
应用程序域B   |
      ......
____________   |   ____________
    -------------------------     ----------------------------
进程之间有边界,应用程序域存在于某个进程中,之间也有边界,处在不同应用程序域中的应用程序不能互相访问
应用程序域可以互相包含

------------------------------------------
======================
进程〉应用程序域〉程序集〉对象
======================
------------------------------------------
应用程序域之间可以通信,或使用代理
AppDomain类用于创建和中断应用程序域,加载和卸载程序集和类,枚举域中的程序集和线程
例如: 1--创建一个控制台应用程序AssemblyA
2--第二个应用程序DomainTest加载AssemblyA.exe程序集
/////////////////////////////////////////////////////////////////////////////////////////
using System;
namespace Wrox.ProCSharp.Assemblies.AppDomains
{
   class Class1
   {
    public Class1(int val1,int val2)
    {//构造函数,具有两个参数,以了解如何创建AppDomain类的实例
     Console.WriteLine("Constructor with the values {0},{1}"+
       "in domain {2} called",val1,val2,
       AppDomain.CurrentDomain.FriendlyName);
    }
    [STAThread]
    static void Main(string[] args)
    {//在Main中,从而知道什么时候调用Console.WriteLine
     Console.WriteLine("Main in domain {0} called",
       AppDomain.CurrentDomain.FriendlyName);
    }
   }
}
//////// /////////////////////////////////////////////////////////////////////////////////
using System;
namespace Wrox.ProCShap.Assemblies.AppDomains
{
   class Test
   {
    [STAThread]
    static void Main(string[] args)
    {
     AppDomain currentDomain = AppDomain.CurrentDomain;
     Console.WriteLine(currentDomain.FriendlyName);
     //使用AppDomain类的FriendlyName属性显示当前域的名称

     ////FLAG////
     AppDomain.secondDomain = AppDomain.CreateDomain("New AppDomain");
     //创建一个新的应用程序域New AppDomain

     secondDomain.ExecuteAssembly("AssemblyA.exe");
     //把程序集AssemblyA加载到新域中,通过调用ExecuteAssembly来调用Main()方法
    }
   }
}

在启动DomainTest.exe前,必须把程序集AssemblyA.exe复制到DomainTest.exe所在的目录下,这样程序才能找到
这个程序集,不能添加AssemblyA.exe程序集的引用,因为在VS.NET中,只能给以DLL格式存储的程序集添加引用,不支持
EXE格式,但是EXE格式的程序集可以在命令行上执行,如果找不到这个程序集就会抛出System.IO.FileNotFoundExection异常
结果:Main in domain New AppDomain called

在上面第二个程序表明的FLAG处可以改为:再创建一个实例,用于替代Main()方法
/////////////////////////////////////////////////////////////////////////////////////////
AppDomain secondDomain = AppDomain.CreateDomain("New AppDomain");

secondDomain.CreateInstance("AssemblyA" ,"Wrox.ProCShap.Assemblies.AppDomains.Class1",
     true,System.Reflection.BindingFlags.CreateInstance,null,new object[]{7,3}
     null,null,null);
/////////////////////////////////////////////////////////////////////////////////////////
//1-程序集名;2-应实例化的类;3-true表示不区分大小写;4-邦定标志枚举值,指定应调用的构造函数;
得到的输出为:Constructor with the values 7,3 in domain New AppDomain called


在运行期间主应用程序域会自动创建,
ASP.NET为每个运行在WEB服务器上的Web应用程序创建一个应用程序域,
Internet Explorer创建运行托管控件的应用程序域,
卸载应用程序域只能通过中断应用程序域来进行


     ----------------------------------------------------------------------2 程序集的结构 ----------------------------------------------------------------------
程序集由=描述它的元数据(程序集元数据)+描述导出类型和方法的类型元数据+MSIL代码+资源
存在于一个文件中或分布在多个文件中
例如: 1 程序集由一个文件组成:Component.dll
2 Component.dll=描述它的元数据+描述导出类型和方法的类型元数据+MSIL代码,
   资源不在其中
   这个程序集使用了一个图:Pictrue.jpeg,该图没有嵌入在dll中,而是在程序集的元数据中引用
   程序集的原数据还引用了一个模块:Util.netmodule,该模块只包含一个类的类型元数据和MSIL代码
   不包含程序集的元数据,所以这个模块没有版本信息,也不能单独安装
   这3个文件构成了程序集,这个程序集是一个安装单元,还可以在另外一个文件中放置程序集清单
    Component.dll   Util.netmodule
   ________________________ ____________
Picture.jpeg<------- 程序集元数据   -------〉 类型元数据
__________     -------〉 IL代码
资源   类型元数据   
   IL代码

程序集的清单
元数据的一部分,描述了程序集和引用它所需的所有信息,并列出,了所有的依赖关系
清单=标识(名称,版本,文化,公钥)+属于该程序集的一个文件列表+
引用程序集的列表+一组许可请求--运行的许可证+
导出的类型(当它们在一个模块中定义,该模块在程序集中引用,否则不属于清单)

---------------------------------------------
======================
命名空间,程序集和组件
1 命名空间完全独立于程序集
2 一个程序集中可以有不同的命名空间
3 一个命名空间也可以分布在多个程序集中
4 命名空间只是类名的一种扩展,它属于类名的范畴
======================
---------------------------------------------

======================
私有程序集和共享程序集
在使用共享程序集时,程序集必须是唯一的,名称唯一(强名),该名称的一部分是一个强制的版本号,如第三方控件
======================
======================
查看程序集
命令行工具:ILDASM

FILE-->OPEN-->打开程序集
======================
======================
构建程序集
1-可以创建模块:csc /target:module hello.cs 模块是一个没有程序集特性的DLL,可以添加到程序集中
创建了hello.netmodule
2-生成一个程序集B.DLL ,它包含模块A.netmodule: csc /target:library /addmodule:A.netmodule /out:B.dll
3-模块的作用1是可以更快的启动程序集,因为并不是所有类都在一个文件中
   模块只在需要时加载
2是是否需要使用多种编程语言来创建一个程序集:一个模块用VB.net,一个模块用C#,这两个模块都包含在一个程序集中
4-使用VS创建程序集
VS2003不支持直接创建模块
创建一个项目时,系统自动生成源文件AssemblyInfo.cs,在该文件中可以使用一般的源代码编辑器配置程序集的属性
[assembly]和[module]是程序集的全局属性

System.Reflection命名空间中的类:
AssemblyCompany     ---指定公司名
AssemblyConfiguration    --指定建立信息,例如零售或调试信息
AssemblyCopyright/Assembly Trademark --包含版权和商标信息
AssemblyDefaultAlias    --如果程序集名称不容易理解,如动态创建程序集名称时的GUID,就可以使用该属性,指定一个别名
AssemblyDescription    --描述程序集或产品,如果查看可执行文件的属性,这个值就会显示为Comments
AssemblyProduct     --指定了属于该程序集的产品名称
AssemblyInfomationalVersion    --在引用程序集时,这个属性不用于版本检查,仅用于版本信息,非常适用于指定使用多个程序集的应用程序的版本,打开可执行程序的属性,这个值就显示为Product Version
AssemblyTitle     --是程序集的描述性名称,可以包括空格,查看属性时显示为Description

System.Runtime.CompilerServices命名空间中的类:
AssemblyCulture      --程序集的文化背景,如en-US
AssemblyDelaySign/AssemblyKeyFile/AssemblyKeyName --用于创建共享程序集的强名
AssemblyVersion      --指定程序集的版本号,版本问题在共享程序集中具有非常重要的地位
======================

======================
跨语言支持
.NET 使用通用类型系统Common Type System-CTS-定义了如何在.net中定义值类型和引用类型,以及这些类型的内存布局
但CTS没有确保在任何语言中定义的类型都可以用于其它语言。
这应是公共语言规范Common Language Specification-CLS 的任务。
CLS定义了.net语言必须支持的最低要求
======================

CTS=common type system=通用类型系统
CLS=common language specification=公共语言规范
======================
例如:
**********************************************************************************
Visual C++编写的基类HelloMCPP/   继承于HelloMCPP的类HelloVB/ 继承于HelloVB的类HelloCSharp
HelloMCPP   HelloVB    HelloCSharp

+Hello()    +Hello()    +Hello()
+Hello2()    +Add()    +Add()
+Add()       +Main()
**********************************************************************************
--使用VS2003创建一个VC的类-class library(.net)
1>HelloMCPP
//HelloMCPP.h
#pragma once
#include <stdio.h>
using namespace System;
namespace Wrox
{namespace ProCSharp
{namespace Assemblies
{namespace CrossLanguage
{public _gc class HelloMCPP //_gc 标记类HelloMCPP,使类成为一个托管类
{public:
   virtual void Hello()
   {Console::WriteLine(S"Hello,ManagedC++");}//S作为字符串的前缀,托管的字符串就会写入程序集,并用ldstr推入堆栈
  
   virtual void Hello2()
]   {printf("Hello,calling native code \n");}

   int Add(int val1,int val2)
   {return val1+val2;}
};
}}}}
**********************************************************************************
--使用VS2003创建一个VB.net的类-class library
--打开项目属性,在Root Namespace 中将项目的根命名空间改为Wrox.ProCSharp.Assemblies.CrossLanguage
,这样就改变了类的命名空间
--添加对HellpMCPP的引用:Project-->Add Reference
给项目添加引用就是将引用的程序集复制到VB.net项目的输出目录上(/bin),然后对原引用程序集的改变就是独立的
2>HelloVB
public class HelloVB
   Inherits HelloMCPP

   public Overrides Sub Hello()
    MyBase.Hello() 'MyBase关键字代表基类,此处调用基类的方法
    Console.WriteLine("Hello,VB.NET")
   End Sub

   public Shadows Function Add(ByVal val1 as integer,_
     ByVal val2 as integer) as integer
    'Shadows关键字用来隐藏基类的方法Add(),因为在基类中Add()不是虚拟(virtual)的
    '不能被重写
    return val1+val2
   End Function
End class
**********************************************************************************
--创建一个C#控制台应用程序,添加对HelloVB和HelloMCPP的引用
3>HelloCSharp
using System;
namespace Wrox.ProCSharp.Assemblies.CrossLanguage
{
   public class HelloCSharp:HelloVB
   {
    public HelloCSharp()
    {}

    public override void Hello()
    {
     base.Hello();
     Console.WriteLine("Hello,C#");
    }

    public new int Add(int val1,int val2)
    {
     return val1+val2;
    }

    [STAThread]

    public static void Main()
    {
     HelloCSharp hello new HelloCSharp();
     hello.Hello();
    }
   }
}
    
**********************************************************************************
最后控制台应用程序的输出如下:
Hello,Managed C++
Hello,VB.NET
Hello,C#
*************************
因为所有的.net语言都生成MSIL代码,所有的语言都使用.NET FRAMEWORK中的类,所以在性能上是没有区别的
但仍有一些小的差别,首先,由于语言的不同,某些语言支持的数据类型其它语言不支持
其次,生成的MSIL代码仍有差别
在默认配置下,VB.NET上的执行比较安全,C#上的执行比较快,C#也更灵活
*************************
在基类中定义的方法在都可以在派生类中调用,如果方法的参数是System.UInt32数据类型,就不能在VB.NET中使用它
,因为VB.NET不支持无符号数据
无符号的数据类型与CLS不兼容,.net的语言不必支持这种数据类型
**********************************************************************************
.NET的语言
.NET consumer工具
   只使用.NET FRAMEWORK中的类,不能创建用于其它语言的.NET类;
   可以使用任何与CLS兼容的类
.NET extends工具
   可以满足客户的要求,可以继承任何与CLS兼容的.NET类;
   定义了可以由客户使用的新CLS兼容类
   C++,VB.NET,C#都是.NET extender工具,使用这些语言可以创建CLS兼容类

**********************************************************************************
CLSCompliant属性
利用它可以把程序集标记为与CLS兼容,这样可以确保这个程序集中的类能用于所有的.NET consumer工具;
在公共方法或受保护的方法中使用与CLS不兼容的数据类型时,编译器会给出警告;
在私有方法中使用什么样的数据类型则不重要,因为在类的外部使用其它语言时,根本就不能访问私有方法;
当在公共方法和受保护的方法中使用与CLS不兼容的类型时,为了让编译器发出警告,可以设置程序集中的属性
CLSCompliant,把这个属性添加到AssemblyInfo.cs中:
[assembly:System.CLSCompliant(true)]
这样,在程序集中定义的所有类型和公共方法就都是兼容的,当参数的数据类型是不兼容的UINT时,编译器就会发出如下
警告:error CS3001:Argument type uint is not CLS-compliant
把程序集标记为兼容时,仍可以定义不兼容的方法,如果要重写某些方法,使其参数是兼容和不兼容的数据类型,就必须把
类中的不兼容的方法的CLSCompliant属性设置为false
CLSCompliant 属性可以用到类型,方法,属性,字段和事件上:
[CLSCompliant(false)]
void Method(uint i)
{//......
**********************************************************************************
CLS规则
程序集和与CLS兼容的要求:
方法原形中的所有类型都必须与CLS兼容;
数组元素的元素类型必须与CLS兼容。数组的第一个元素的下标必须是0;
CLS兼容类必须继承与CLS兼容类,当然,System.Object 是与CLS兼容的;
在CLS兼容类中,方法名士不区分大小写的,两个方法不能仅根据其名称中字母的大小写来区分;
枚举的类型必须是Int16,Int32,Int64,其它类型的枚举都是不兼容的;
上述要求只适用于公共成员和受保护的成员,私有方法则无需考虑这些要求,它们可以使用不兼容的类型,
而程序集仍然是兼容的
还应该遵循更一般的命名约定
C# VB.NET 更一般
int integer Int32
long long Int64
float single Single
在利用CLS规范和规则进行编译时,很容易创建出可以用于多种语言的组件,不需要使用所有的.NET FrameWork语言来测试该组件
**********************************************************************************
全局程序集缓存 Global Assembly Cache
可全局使用的程序集的缓存
大多数共享程序集都安装在这个缓存中,其中也安装了一些私有程序集
如果私有程序集使用本机图像生成器编译为本机代码,编译好的本机代码也会存储在这个缓存中
**********************************************************************************
本机图像生成器 native image generator Ngen.exe
可以在安装期间把IL代码编译为本机代码,这样程序启动就比较快,因为不再需要在运行时进行编译
Ngen工具在本机图像缓存中安装本机图像,本机图像缓存是全局程序集缓存的一部分

**********************************************************************************
全局程序集缓存查看器
全局程序缓存可以使用shfusion.dll来显示,它是一个Windows外壳扩展程序,可以查看和处理缓存的内容
Windows外壳扩展程序是一个与Windows资源管理器集成的COM DLL
用户启动资源管理器,进入<windir>/assembly目录即可
可以查看全局程序集的名称,类型,版本,文化和公钥标记,通过查看全局程序集的类型可以确定程序集是否
是使用本机图像生成器安装的
在该目录下,有GAC和NativeImages_<runtime version>目录,分别是共享程序集的目录和编译为本机代码的程序集

**********************************************************************************
全局程序集缓存工具 gacutil.exe
全局程序集缓存查看器可以查看和删除程序集,但不能在脚本代码中使用该工具,例如创建安装程序。
gacutil.exe 可以使用命令行安装,卸载和显示程序集
选项: gacutil /l   --显示程序集缓存中的所有程序集
   gacutil /i mydll --把共享程序集mydll安装到程序集缓存上
   gacutil /u mydll --卸载程序集mydll
   gacutil /ungen mydll --从本机图像缓存中卸载程序集
**********************************************************************************
创建共享程序集
程序集可以由一个应用程序使用,在默认下部共享程序集,在使用私有程序集时,不需要考虑共享时需要考虑的任何要求
共享程序集名:必须是全局唯一的,1要保护该名称,2要使得其他人不能使用这个名称创建程序集
   COM使用全局唯一标识符GUID只解决了第一个问题,第二个问题仍然没有解决,每个人
都可以盗用这个GUID,用相同的标识符创建不同的对象
   这两个问题使用.NET程序集的强名都可以解决
   强名由以下项目组成:
     程序集本身的名称
     版本号
     公钥,保证强名是独一无二的,并且保证引用的程序集不能被另一个源替代
     文化
共享程序集必须有一个强名,来唯一的标识该程序集
每个程序集不能有新的公钥,但可以在公司中有这样一个公钥,这样该密钥就唯一的标识了公司的程序集
但是 ,这个密钥不能用作信任密钥,程序集可以利用Authenticode签名来建立信任关系,
Authenticode中的密钥可以与强名中使用的密钥不同

**********************************************************************************
公钥的加密
对称加密:使用同一个密钥进行加密和解密
公钥/私钥加密:使用一个公钥加密,使用对应的私钥解密/使用一个私钥加密,使用对应的公钥解密
    成对创建公钥和私钥,公钥可以任何人使用,甚至可以放在web站点上,但私钥必须安全的加锁
Sarah-->one email -->Julian,除了Julian外的人都不能看
使用Julian的公钥加密,Julian打开该email,并使用他秘密存储的私钥解密
但还有一个问题,Julian不能确保email是Sarah发来的,任何人都可以使用Julian的公钥加密发送email给他
解决办法是党Sarah发送email给Julian时,使用Julian的公钥加密邮件之前他添加了自己的签名,再使用自己的
私钥加密该签名,然后使用Julian的公钥加密email,这个签名可以使用Sarah的公钥来解密,而Julian可以
访问Sarah的公钥,在解密了签名后,Julian就可以确定是Sarah发送了email

**********************************************************************************
将公钥/私钥应用于程序集
创建共享组件,必须使用公钥/私钥对
编译器把公钥写入程序集清单,创建属于该程序集的所有文件的散列表--用私钥标记这个散列表
私钥不存储在程序集中,确保没有人可以修改这个程序集,签名可以使用公钥来验证
在开发过程中,客户程序集必须引用 共享程序集。编译器把引用程序集的公钥写入客户程序集的清单中
要减少存储量,就不应把公钥写入客户程序集的清单,而应写入公钥标记,公钥标记是公钥散列表中的最后8位字节,且是唯一的
在运行期间加载共享程序集时(如果客户程序集是使用本机图像生成器安装的,则应在安装期间加载),
共享程序集的散列表可以使用存储在客户程序集中的公钥来验证,除了私钥的主人外其他人都不能修改共享程序集
例如:
销售商A创建了一个组件Math,在客户机上引用该组件,黑客的组件就无法替代它,只有私钥的主人才能用
新版本来替换原来的共享组件,保证了其完整性

**********************************************************************************
创建共享程序集
例子
1 建立一个Visual C# Class Library 项目 SharedDemo,把命名空间改为Wrox.ProCSharp.Assemblies.Sharing
     类名改为SimpleShared
using System;
using System.Collection.Specialized;
using System.IO;

namespace Wrox.ProCSharp.Assemblies.Sharing
{
   public class SharedDemo
   {
    private StringCollection quotes;//类的构造函数将文件的所有行都读到其中
       //文件名作为参数传递到构造函数
    private Random random;
   
    public SharedDemo(string filename)
    {
     quotes = new StringCollection();
     Stream stream =File.OpenFile(filename);
     StreamReader streamReader = new StreamReader(stream);
     string quote;
    
     while((quote=streamReader.ReadLine())!=null)
     {
      quotes.Add(quote);
     }
     streamReader.Close();
     stream.Close();
     random = new Random();
    }
    public string GetQuoteOfTheDay()
    //返回这个集合的一个随机字符串
    {
     int index = random.Next(1,quotes.Count);
     retrun quotes[index];
    }
   }
}
*************************
* a>创建强名称 *--程序集中有了公钥
*************************
   要共享这个组件,需要一个强名称,要创建这个名称可以使用强名称工具sn:
   sn -k mykey.snk
强名称工具生成和编写一个公钥/私钥对,并把该密钥对写到文件中,此处的文件是mykey.snk,现在可以
在向导生成的文件Assemblyinfo.cs中设置属性AssemblyKeyFile,该属性可以设置为密钥文件的绝对路径,也可以
设置为密钥文件的相对路径%ProjectDirectory\obj\<configuration>目录,所以../../mykey.snk引用项目目录中的
一个密钥,在开始建立一个项目时,该密钥安装到Crypto Service Provider(CSP)中,如果该密钥已经安装到CSP中,就可以
使用AssemblyKeyName属性
下面是对Assemblyinfo.cs的修改
[assembly:AssemblyDelaySign(false)]
[assembly:AssemblyKeyFile("../../mykey.snk")]
[assembly:AssemblyKeyName("")]
在重新建立该文件后,使用ildasm查看该程序集,则该程序集的清单中应有一个公钥

*************************
* b>安装共享程序集 *
*************************
使用全局程序集缓存工具gacutil及其/I选项把它安装到全局程序集缓存中:
gacutil /i SharedDemo.dll
可以使用全局程序集缓存查看器检查共享程序集的版本,看看它是否安装成功

*************************
* c>使用共享程序集 *
*************************
创建一个C#控制台应用程序Client,不是把新项目添加到原来的解决方案中,而是创建一个新的解决方案
这样在重新建立客户时,就不会重新建立该共享程序集了。
以引用私有程序集方式引用程序集SimpleShared.dll,使用菜单Project|Add Reference
有了共享程序集,引用属性CopyLocal就可以设置为false,这样,共享程序集就不会复制到输出文件的目录下
而会从全局程序集缓存中加载
下面是客户机应用程序的代码:
using System;
namespace Wrox.ProCSharp.Assemblies.Sharing
{
   class Client
   {
    [STAThread]
    static void Main(string[] args)
    {
     SharedDemo quotes = new SharedDemo(@"C:\ProCSharp\Assemblies\Quotes.txt");
     for(int i=0;i<3;i++)
     {
      Console.WriteLine(quotes.GetQuoteOfTheDay());
      Console.WriteLine();
     }
    }
   }
}
**********************************************************************************
公钥的标记也可以使用强名称工具sn在共享程序集中查看:sn -T会显示程序集中的公钥标记
sn -Tp显示标记和公钥
**********************************************************************************
程序集的延迟签名
公司的私钥应安全存储,大多数公司不允许所有的开发人员访问私钥,只有几个有安全权限的人才能访问
这就是程序集的签名可以以后(例如发布前)添加的原因
全局程序集属性AssemblyDelaySign设置为true时,签名就不会存储在程序集中,但保留了足够的空间,以便
以后添加。
但是不使用密钥就不能测试程序集,在全局程序集缓存中安装它
但可以使用临时密钥进行测试,以后再用真正的密钥代替这个临时密钥
程序集的延时签名需要执行以下步骤:
1>使用sn创建一个公钥/私钥对,生成文件mykey.snk,包含公钥和私.snsn -k mykey.snk
2>提取公钥,使之可以用于开发人员。选项-p提取密钥文件的公钥,文件mypublickey.snk仅包含公钥
   sn -p mykey.snk mypublickey.snk
公司中所有开发人员都可以使用这个密钥文件mypublickey.snk
在文件AssemblyInfo.cs中设置AssemblyDelaySign和AssemblyKeyFile属性
[assembly:AssemblyDelaySign(true)]
[assembly:AssemblyKeyFile("../../mypublickey.snk")]
3>关闭签名的验证功能,因为程序集没有包含签名
sn -Vr ShareDemo.dll
4>在发布之前,程序集可以用sn工具重新签名
-R选项用于对以前已签名或延迟签名的程序集进行重新签名
sn -R MyAssembly.dll mykey.snk
注意:签名的验证功能只能在开发过程中关闭,不经过验证是不能发布程序集的,因为这个程序集可能被怀有恶意的程序集代替
**********************************************************************************

程序集(assembly)是包含编译好的、面向.NET Framework的代码的逻辑单元。程序集是完全自我描述性的,也是一个逻辑单元而不是物理单元,它可以存储在多个文件中(动态程序集的确存储在内存中,而不是存储在文件中)。如果一个程序集存储在多个文件中,其中就会有一个包含入口点的主文件,该文件描述了程序集中的其他文件。

注意可执行代码和库代码使用相同的程序集结构。惟一的区别是可执行的程序集包含一个主程序入口点,而库程序集则不包含。

程序集的一个重要特性是它们包含的元数据描述了对应代码中定义的类型和方法。程序集也包含描述程序集本身的元数据,这种程序集元数据包含在一个称为程序集清单的区域中,可以检查程序集的版本及其完整性。

注意:

ildasm是一个基于Windows的实用程序,可以用于检查程序集的内容,包括程序集清单和元数据。第15章将介绍ildasm

程序集包含程序的元数据,表示调用给定程序集中的代码的应用程序或其他程序集不需要指定注册表或其他数据源,以便确定如何使用该程序集。这与以前的COM有很大的不同,以前,组件的GUID和接口必须从注册表中获取,在某些情况下,方法和属性的详细信息也需要从类型库中读取。

把数据分散在3个以上的不同位置上,可能会出现信息不同步的情况,从而妨碍其他软件成功地使用该组件。有了程序集后,就不会发生这种情况,因为所有的元数据都与程序的可执行指令存储在一起。注意,即使程序集存储在几个文件中,数据也不会出现不同步的问题。这是因为包含程序集入口的文件也存储了其他文件的细节、散列和内容,如果一个文件被替换,或者被塞满,系统肯定会检测出来,并拒绝加载程序集。

程序集有两种类型:共享程序集和私有程序集。

1.4.1 私有程序集

私有程序集是最简单的一种程序集类型。私有程序集一般附带在某些软件上,且只能用于该软件中。附带私有程序集的常见情况是,以可执行文件或许多库的方式提供应用程序,这些库包含的代码只能用于该应用程序。

系统可以保证私有程序集不被其他软件使用,因为应用程序只能加载位于主执行文件所在文件夹或其子文件夹中的程序集。

用户一般会希望把商用软件安装在它自己的目录下,这样软件包没有覆盖、修改或加载另一个软件包的私有程序集的风险。私有程序集只能用于自己的软件包,这样,用户对什么软件使用它们就有了更多的控制。因此,不需要采取安全措施,因为这没有其他商用软件用某个新版本的程序集覆盖原来的私有程序集的风险(但软件是专门执行怀有恶意的损害性操作的情况除外)。名称也不会有冲突。如果私有程序集中的类正巧与另一个人的私有程序集中的类同名,是不会有问题的,因为给定的应用程序只能使用私有程序集的名称。

因为私有程序集完全是自含式的,所以安装它的过程就很简单。只需把相应的文件放在文件系统的对应文件夹中即可(不需要注册表项),这个过程称为“0影响(xcopy)安装”。

1.4.2 共享程序集

共享程序集是其他应用程序可以使用的公共库。因为其他软件可以访问共享程序集,所以需要采取一定的保护措施来防止以下风险:

       名称冲突,另一个公司的共享程序集执行的类型与自己的共享程序集中的类型同名。因为客户机代码理论上可以同时访问这些程序集,所以这是一个严重的问题。

       程序集被同一个程序集的不同版本覆盖——新版本与某些已有的客户机代码不兼容。

这些问题的解决方法是把共享程序集放在文件系统的一个特定的子目录树中,称为全局程序集高速缓存(GAC)。与私有程序集不同,不能简单地把共享程序集复制到对应的文件夹中,而需要专门安装到高速缓存中,这个过程可以用许多.NET工具来完成,其中包含对程序集的检查、在程序集高速缓存中设置一个小的文件夹层次结构,以确保程序集的完整性。

为了避免名称冲突,共享程序集应根据私有密钥加密法指定一个名称(私有程序集只需要指定与其主文件名相同的名称即可)。该名称称为强名(strong name),并保证其惟一性,它必须由要引用共享程序集的应用程序来引用。

与覆盖程序集相关的问题,可以通过在程序集清单中指定版本信息来解决,也可以通过同时安装来解决。

1.4.3 反射

因为程序集存储了元数据,包括在程序集中定义的所有类型和这些类型的成员的细节,所以可以编程访问这些元数据。这个技术称为反射,第11章详细介绍了它们。该技术很有趣,因为它表示托管代码实际上可以检查其他托管代码,甚至检查它自己,以确定该代码的信息。它们常常用于获取特性的详细信息,也可以把反射用于其他目的,例如作为实例化类或调用方法的一种间接方式,如果把方法上的类名指定为字符串,就可以选择类来实例化方法,以便在运行时调用,而不是在编译时调用,例如根据用户的输入来调用(动态绑定)


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多