C#-程序集
C#-程序集
程序集
===============================================================================================
----------------------------------------------------------------------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
#i nclude <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>目录,所以http://www.cnblogs.com/mykey.snk引用项目目录中的
一个密钥,在开始建立一个项目时,该密钥安装到Crypto Service Provider(CSP)中,如果该密钥已经安装到CSP中,就可以
使用AssemblyKeyName属性
下面是对Assemblyinfo.cs的修改
[assembly:AssemblyDelaySign(false)]
[assembly:AssemblyKeyFile("http://www.cnblogs.com/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("http://www.cnblogs.com/mypublickey.snk")]
3>关闭签名的验证功能,因为程序集没有包含签名
sn -Vr ShareDemo.dll
4>在发布之前,程序集可以用sn工具重新签名
-R选项用于对以前已签名或延迟签名的程序集进行重新签名
sn -R MyAssembly.dll mykey.snk
注意:签名的验证功能只能在开发过程中关闭,不经过验证是不能发布程序集的,因为这个程序集可能被怀有恶意的程序集代替
摘自:http://www.5blogs.com/kianhsu/6058/archives/2006/12689.html