微软.NET开发认证(高级篇)

来源:互联网 发布:庞贝末日知乎 编辑:程序博客网 时间:2024/05/21 10:09

微软.NET开发认证基础技术知识大局观——高级篇 

摘要: 本文从实际学习知识的角度出发,讲述Microsoft.NET开发认证涵盖的基础技术知识,这些知识是通过Asp.net、Windows Forms、WCF、WPF等开发认证的基础。 文章分为核心技术和高级技术两部分。
 
概述
Microsoft.NET 认证是在.NET技术面世后微软向软件开发者提供的开发系列认证。本文从学习知识的角度出发讲解作为一名合格的.NET开发认证工程师需要掌握的基础技术。虽然.NET的发行版本已经到了3.5,但CLR(Common Language Runtime)作为.NET框架中的核心部分还处于2.0版本。因此本文提到的.NET Framework功能都基于2.0,这也符合目前各项.NET开发认证的技术要求。
 
文章的大部分内容在微软官方教程和MSDN文档都有体现,本文以微软官方教程为基础,进行概括总结,使读者树立良好的.NET认证基础技术知识大局观。如果读者想深入学习这些知识建议阅读微软官方教程和MSDN文档。文章中所述的各项内容最终解释权归微软所有。
 
高级篇内容包括:实现数据序列化、使用GDI+、实现代码访问安全性、实现加密解密、COM组件与.NET程序集互操作、实现服务应用程序和使用电子邮件消息、使用.NET类型元数据、创建多线程应用程序和应用程序域等八项内容。
 
1. 实现数据序列化
在开发基于分布式架构的应用程序时,首先需要解决的问题是如何将对象或数据在若干应用程序之间传递,常见的做法是将对象传输到任何目标位置之前将其转换为一种适当的格式以便于在两者之间实现通信,常见的格式包括二进制格式、自定义结构化数据和基于简单对象访问协议的SOAP格式。
 
序列化和反序列化。序列化是指将对象或数据转换为另外一种格式以便于存储和传输。序列化是将对象的状态保存到流或缓冲区的过程。反序列化是指将序列化后的数据或对象转换回该对象尚未序列化时的原始状态。.NET Framework提供了一些格式化类和接口用于将对象转换为二进制格式、SOAP格式或自定义的XML格式实现多个平台之间数据交换。另外,.NET Framework还提供了类和接口允许用户创建自己的格式化程序,从而将对象转换为某种应用程序特有的自定义格式,实现自定义的序列化和反序列化。
 
序列化的不同格式。采用二进制格式进行序列化是对象和数据在不同应用程序(域)之间存储或传递的最常用方式。这些应用程序(域)可以位于同一台计算机或者一个网络环境中。二进制格式是一系列字节,用于表示序列化的对象或数据的原始状态。有时,用户可能需要在完全不同的应用程序之间传输数据或对象,这时,你可以通过使用Web服务在应用程序之间传递序列化的对象和数据。SOAP作为一种专门的XML语法,已经成为在Web上传输所有数据类型的行业标准。.NET Framework提供了BinaryFormatter类和SoapFormatter类分别支持将对象序列化为二进制格式和SOAP格式。
 
XML 序列化和反序列化。.NET Framework提供XmlSerializer类来序列化对象,通过XmlSerializer类可将对象序列化为XML格式或反序列化为对象。另外,.NET Framework提供SoapFormatter类支持将对象序列化为SOAP格式和反序列化为对象。
 
控制XML序列化和反序列化方式。.NET Framework提供了一组XML序列化特性应用于类和类成员,从而控制XmlSerializer对类的实例进行序列化或反序列化的方式。这些特性类包括XmlAnyAttributeAttribute、XmlAnyElementAttribute、XmlArrayAttribute、 XmlArrayItemAttribute、XmlAttributeAttribute、 XmlChoiceIdentifierAttribute、XmlElementAttribute、XmlIgnoreAttribute、 XmlIncludeAttribute、XmlRootAttribute、XmlTextAttribute和XmlTypeAttribute等。另外,用户可以使用以下的XML序列化特性来控制已编码的SOAP序列化。这些特性包括SoapAttributeAttribute、 SoapElementAttribute、SoapEnumAttribute、 SoapIgnoreAttributeSoapIncludeAttribute和SoapTypeAttribute等。
 
处理序列化事件。.NET Framework提供了一组序列化和反序列化事件处理程序特性,以便于更灵活的控制序列化和反序列化的行为。这些特性包括 OnSerializingAttribute、OnSerializedAttribute、OnDeserializingAttribute和 OnDeserializedAttribute。
 
实现自定义序列化类。.NET Framework在System.Runtime.Serialization命名空间提供了序列化接口,以创建自定义序列化类。这些接口包括 ISerializable、IXmlSerializable、IDeserializationCallback、IFormatter以及 IFormatterConverter。结合使用序列化类型,开发人员可以收集关于被序列化的对象的信息,这些类型包括 SerializationEntry结构、SerializationInfo类和StreamingContext结构。.NET Framework还提供了三种格式化程序类包括Formatter、FormatterConverter和FormatterServices为将序列化的数据转换和重新构造为对象提供基础。
 
管理反序列化对象。使用 ObjectManager类,可以在对象进行反序列化时对其进行跟踪,以管理反序列化对象。在反序列化过程中,Formatter会查询 ObjectManager实例,以确定对序列化流中对象的引用是否引用已经反序列化的对象(后向引用)或引用尚未反序列化的对象(前向引用)。 ObjectManager遵循一组规则用于指定修正的顺序。
 
2. 使用GDI+
GDI 是 Windows操作系统中提供二维矢量图形、图像处理和版式的图像设备接口,GDI+在 GDI的基础上进行了改进,添加了新功能并优化现有功能。GDI+托管类和接口是.NET Framework的重要组成部分,通过使用GDI+,你可以在应用程序中加入丰富图形来增强用户体验。
 
图形设备接口(GDI+)。GDI+中的对象都为非托管对象,占用了大量的计算机资源,由于是非托管对象,这意味着开发人员需要编写代码手动释放这些对象。.NET Framework的System.Drawing命名空间提供了非托管GDI+ API的.NET Framework包装类库。当托管GDI+ 对象超出范围时,垃圾回收器将其标记为已被释放,但是,仍然能够编写代码显式地释放托管和非托管GDI+对象。
 
创建GDI+绘图图面。Graphics类封装一个GDI+绘图图面,可以在其上绘制各种图形,如直线、矩形和文本等。Windows窗体和几乎所有的Windows UI控件类都包含CreateGraphics方法来检索窗体或控件对应的Graphics类的对象。另外,BufferedGraphics类表示与绘制图面关联的内存缓存区,通过调用BufferedGraphicsContext类的Allocate方法获取BufferedGraphics类的对象,Allocate方法还将BufferedGraphics类的对象与绘制图面及其矩形平面关联。BufferedGraphicsManager类可以用来实现图形的自定义双缓冲,以减少或消除重绘显示图面时产生的闪烁,可通过其Current静态属性访问当前应用程序域的主 BufferedGraphicsContext对象。
 
绘制线条。Pen类定义用于绘制直线和曲线的对象,使用Pen类可以绘制直线和曲线以构成独立矩形和椭圆等图形的轮廓。Pen类所绘制的每条直线的起点和终点由x和y坐标表示,可以是不同宽度和不同样式(例如实线、虚线和点线)。另外,.NET Framework提供了两个帮助器类Pens和SystemPens,Pens类的各个静态属性表示标准颜色的Pen对象,而SystemPens类的各个静态属性表示具有系统颜色的Pen对象。
 
填充图形对象。.NET Framework提供了Brush类的派生类来给矩形和椭圆等图形对象填充颜色。SolidBrush类表示使用单色填充图形对象的画笔,TextureBrush表示使用图像模式填充图形对象的画笔。另外.NET Framework提供了Brushes和SystemBrushes类来获取Brush对象,Brushes的静态属性表示所有标准颜色的画笔,SystemBrushes类的静态属性表示所有Windows 显示元素颜色的画笔。
 
为图形对象应用颜色。.NET Framework使用RGB的扩展实现了32位ARGB。ARGB由alpha、红色、绿色和蓝色组成。Alpha定义了由ARGB表示的颜色的透明度,Alpha值为0时表示颜色绝对透明,为255时表示颜色绝对不透明。.NET Framework使用颜色名称、十六进制值和ARGB值三种方式来指定颜色。为处理ARGB颜色.NET Framework提供了一系列类和结构。Color结构表示一种ARGB颜色,SystemColors类返回表示Windows操作系统的UI元素颜色的Color对象。ColorConverter类用于将颜色值从一种数据类型转换为另外一种数据类型,例如将字符串值转换为Color对象。 ColorTranslator类用于将ARGB转换为十六进制或将十六进制转换为ARGB。
 
绘图表层写入文本。.NET Framework提供了Font类用于封装在特定设备上呈现特定字体所需的纹理和资源,以帮助你在绘制表面以各种形式写入文本。Font类为文本定义了一个特定格式,包括字体、字号和样式属性。FontFamily类定义了一组基本设计相同但样式各异的字样。StringFormat类定义了文本的布局,例如对齐方式和文本方向。SystemFonts类包含一些静态属性返回一个表示特定WindowsUI元素的Font类的对象。 FontConverter类将Font类的对象从一种数据类型转换为另一种数据类型。
 
3. 实现代码访问安全性
.NET Framework 提供的代码访问安全性扩展了内置于Win32 API中的基于角色的安全性模型。基于角色的机制检查用户凭证并查询访问控制列表决定是否允许应用程序执行特定操作及访问特定系统资源。代码访问安全性增加了与程序集关联的证据,此证据可以揭示应用程序的源、编写者以及是否已被签名。基于证据的安全性的关键好处是:它可以保护想要运行不可靠代码的受信任的用户。通过识别程序来自一个可能不安全的源,代码访问安全性可以自动限制程序集权限并有效减少导致代码被破坏的可能性。
 
代码访问安全性的好处。代码访问安全性是.NET 中的核心安全性方案,它帮助限制对重要或敏感操作和资源的访问。当尝试锁定系统并保护它们免受恶意代码袭击时,只提供用户和代码执行任务所需要的最少特权。当试图实施最少特权原则时,代码访问安全性可以为你提供很大的帮助。过去,基于角色的安全系统依赖于授予用户的权限。.NET的代码访问安全性的创新即可以为代码授予访问权限。
 
指定代码安全性权限的方法。指定代码安全性权限的方法包含强制式语法和声明式语法两种。强制式语法要求显式创建用于在运行时进行安全性检查的权限对象。对只有在运行时才知道其名称和位置的文件进行读写等操作时,该方法特别有用。另一种广泛采用的语法是声明式语法,它使用代码特性来指定执行程序集、特定类或类中的特定方法时需要哪些权限。
 
证据。代码访问安全使用程序集提供的证据以及当前在计算机上实施的安全性策略来确定要授予哪些权限。证据是关于程序集的信息,用于描述程序集的表示和来源,主要有主机证据和程序集证据两种类型。主机证据描述代码的源并指示该代码是否经过签名,包括Site、URL、Zone、Application Directory、Strong Name、Publisher和Hash七种。程序集提供的证据类型可能会根据其来源不同而变化。程序集证据由程序集自身提供且必须由用户定义。在.NET Framework中System.Security和System.Security.Policy命名空间提供IEvidenceFactory接口以及Evidence和PermissionRequestEvidence两个主要类来管理证据。
 
安全性策略。安全性策略是指管理员通过定义并配置一组规则,以管理授予不同程序集的信任级别。这些规则使用户可以根据程序集的位置、源或其他证据来指定信任级别。.NET安全性策略模型允许在企业、计算机、用户和应用程序域四个不同级别上设置安全性策略。每个安全性策略都在代码组与权限之间定义一个映射。代码组是一个共享某个证据项的程序集集合。要成为代码组的成员,程序集必须与代码组的成员条件相匹配。
 
管理安全性策略。.NET Framework提供SecurityManager类对与安全系统交互的类提供主访问点。通过使用PolicyLevel和 PolicyStatement类来管理安全性策略;通过使用Code Group类来配置代码组,包括UnionCodeGroup类、FirstMatchCodeGroup类、FileCodeGroup类和 NetCodeGroup类。通过使用条件类型来管理代码组成员,包括AllMembershipCondition类、 ApplicationDirectoryMembershipCondition类、GacMembershipCondition类、 HashMembershipCondition类、PublisherMembershipCondition类、 SiteMembershipCondition类、StrongNameMembershipCondition类、 UrlMembershipCondition类和ZoneMembershipCondition类。另外.NET Framework还提供了IApplicationTrustManager 和 IMembershipCondition 接口来帮助创建自定义安全性策略类型。
 
配置代码访问安全性策略。配置代码访问安全性策略主要有两个工具。第一个是.NET Framework配置工具,它是一个Windows应用程序,用于配置程序集缓存、远程处理服务及代码访问安全性等功能。第二个是命令行工具 caspol.exe,仅用于配置代码访问安全策略,但它提供了比.NET Framework配置工具更多的选项,Caspol.exe还提供了一种关闭代码访问安全性的方法。
 
权限。权限是给予授权对资源(例如执行打开文件、写入注册表和执行代码)操作的对象。权限通常存储在名为PermissionSet的集合类,该集合类可以保存各种类型的Permission对象。权限类型包括三种:代码访问权限,控制代码可资环行的操作类型;标识权限,它们直接基于程序集所提供的证据,而非基于计算机安全性策略;基于角色的安全性权限,与其他标识权限不同,基于角色的安全性权限是基于用户标识,而非代码标识。
 
管理权限。权限使用三种方式。请求,通常通过使用声明式语法为程序集请求权限。当创建程序集时,语言编译器将被请求的权限存储在程序集清单中,它不能提升权限使其超出当前安全性策略级别允许的范围。要求,与请求不同,权限要求可以出现在代码中的任何位置且经常位于类级别和方法级别。授予,只要满足适当的条件,即使你没有特别请求权限,计算机上的安全性策略也会自动授予代码权限。.NET Framework提供IPermission接口和CodeAccessPermission类以作为定义和管理权限的基础结构的一部分。 IPermission接口由所有预定义的Permission类实现,并保证Permission类支持在定义和操作权限时所需的基本方法和属性。该接口还可以由用户创建的任何自定义Permission类实现。CodeAccessPermission是抽象类,很多其他Permission类都从其继承。CodeAccessPermission类和其继承类一个最重要的任务是执行堆栈遍历,以确保方法的调用方是否具有访问资源的足够的权限,如果无,则将引发一个安全异常。
 
访问控制。访问控制是指控制谁可以访问操作系统中资源的安全性。.NET Framework提供了一些管理特定资源类型(例如文件、加密密钥、注册表和互斥锁)的访问控制的基类。这些基类是 AuthorizationRule、AuthorizationRuleCollection、AccessRule和AuditRule。这些类为更多可以实例化的专用类提供基础功能。
 
管理访问控制。在.NET Framework中通过使用访问控制列表类来管理用户对资源的访问。这些类都为GenericAce类和GenericAcl类的派生类,它们是:CompoundAce、CommonAce、GenericAcl、CommonAcl、DiscretionaryAcl和SystemAcl。
 
使用资源安全类来保护资源。.NET Framework包括一组安全类,提供无需直接操作访问控制列表 (ACL) 而控制对目录文件对象的访问的能力。不同于管理访问控制列表,这些类通过分配规则来管理资源访问,这些规则提供直接操ACL相同的等效功能,但使用起来更简单、更安全。安全类可以处理文件、目录、注册表、互斥锁和信号量,这些类都从ObjectSecurity类继承,包括 FileSystemSecurity、FileSecurity、DirectorySecurity、RegistrySecurity、 MutexSecurity和SemaphoreSecurity。
 
管理用户标识信息。标识和主体的概念明确地出现在.NET Framework的一些类中,这些类通常会实现IIdentity接口和IPrinicpal接口中的一个,以帮助定义它们需要支持的基本功能。 IIdentity接口为所有的标识类型定义了基本功能,这些标识类型包括GenericIdentity、WindowsIdentity、 FormsIdentity、PassportIdentity和HttpListenerBasicIdentity。IPrincipal接口确保主体类型提供了关于标识及角色的基本功能。每个主体类型表示代码在其下运行的安全上下文,包括用户标识和用户角色。管理用户标识信息常见的方式是通过使用 GenericIdentity和GenericPrincipal类来管理用户标识;通过使用WindowsIdentity和 WindowsPrincipal类来确定Windows用户;通过使用IdentityReference类来收集用户标识信息;通过使用 WindowsImpersonationContext类来临时模拟用户。
 
4. 实现加密解密
.NET Framework 2.0 在 1.x的基础上提供了新的加密类型,并对支持对称和非对称加密以及哈希的现有类型进行重大加强,通过使用加密类型确保.NET应用程序安全通信并保护敏感数据。.NET Framework提供多个System.Securrity.Cryptography命名空间中的类来实现对称加密算法和非对称加密算法。
 
数据加密和解密。加密是将信息转换为一种隐晦或难读的格式以保护信息的过程。解密是将隐晦格式的信息转换为可读格式的过程。必须使用用来加密的密钥来执行解密。密码应用一个算法来达到加密信息的目的。根据加密算法所使用的密码和管理密码的方法可将加密算法分为两大类。对称加密也称为私钥加密,它使用一个必须保持私有的加密密钥。非对称加密也称为公钥加密,它使用两个密钥,一个保持私有,而另一个公开。
 
对称算法执行对称加密。对称加密也称为单密钥或私钥加密,因为它使用单个私钥来对数据进行加密和解密。对称加密通常用于保护合作伙伴公司或几个公司之间的通信安全。对称加密算法比非对称加密算法简单,因为对称加密算法只适用一个密钥。所以,对称算法的执行速度通常比非对称算法要快很多,但是,必须确保用于对称加密的单个密钥的安全。如果密钥落入别人手中,得到密钥的人就可利用该密钥解密已加密的信息并阅读这些信息。对称算法包括DES、三重DES、RC2和Rijndael。各种对称算法在加密数据的位强度和加密方式方面是不同的。
 
对称算法加密类。 SymmetricAlgorithm类表示所有对称算法的实现都必须从中继承的抽象基类,提供了所有扩展的对称加密类都具有的基本功能。DES、 TripleDES、RC2和Rijndael类扩展了SymmetricAlgorithm类并提供具体对称加密功能,这些类中的每个都会被相应的 CryptoServiceProvider(CSP)类进一步扩展。CSP类是通过包装CLR外部的非托管对象来提供加密服务的具体类。
 
非对称类执行非对称加密。.NET Framework 提供了密码加密类,它们可以实现最流行的非对称算法。.NET Framework支持的非对称算法包括RSA算法和DSA算法。各种非对称算法在加密数据的位强度和加密方式是不同的。
 
非对称算法加密类。 AsymmetricAlgorithm类表示所有不对称算法的实现都必须从中继承的抽象基类,提供了所有扩展的非对称加密类都具有的基本功能。RSA类和DSA类扩展了AsymmetricAlgorithm类并提供具体非对称加密功能。每一个扩展类都会被CSP类进一步扩展。
 
使用SslStream类保护TCP/IP通信的安全。SSL用于通过Web传输安全消息,通过SSL可以获得结合对称加密和非对称加密结合所带来的多个好处。非对称数据签名使用Web服务器上的私钥,服务器将公钥颁发给客户端,并由客户端传递敏感数据和计算密钥的哈希值。对称加密对被传递的内容进行加密。用于SSL的密钥由称为证书颁发机构(CA)的第三方颁发,该机构用于验证Web服务器或客户端的身份。.NET Framework提供AuthenticatedStream类和SslStream类实现服务器与客户端之间基于SSL协议的安全通信。
 
数据哈希值计算。哈希计算的知识对于开发者完整地了解.NET Framework中的安全性和加密是十分关键的。与加密相似,哈希计算有许多用途,但是很难理解哈希算法如何更安全地传输密码和信息。Windows登录是一个典型的例子,当使用用户名和密码登录Windows时,密码本身没有被存储和传递,但是密码的哈希值会被存储并被传递用于身份验证,避免了密码被拦截和窃取。
 
哈希算法。哈希计算是一个与加密相似的业界支持标准。哈希是将信息转换为隐晦值的过程,它是单向的,不能反哈希操作。哈希计算使用一个算法将输入值映射为哈希值或输入值的数字表示形式。如果给出一系列相同的输入值,则哈希算法将总是产生相同的输出值。.NET Framework支持MD5、SHA1和HMAC算法。HashAlgorithm类是所有加密哈希算法实现均必须从中派生的基类。该类被SHA1、 MD5、KeyedHashAlgorithm、RIPEMD160、SHA256、SHA384和SHA512类扩展。另外 KeyedHashAlgorithm类包含HMAC和MACTripleDES子类。HMAC包含HMACMD5、HMACRIPEMD160、 HMACSHA1、HMACSHA256、HMACSHA384和HMACSHA512子类。
 
加密行为扩展。.NET Framework除提供加密类和哈希类外,还提供了其他几种用于扩展或简化加密行为的类,在创建复杂加密代码时相当有用。这些附加类提供的功能包括将加密输出定位到流、配置参数并将其传递给其他加密类、生成随机数以及保护数据和内存等。通过使用CryptoStream类和CryptoConfig类来管理配置信息;通过使用ProtectedData类和ProtectedMemory类来保护存储在文件中和内存中的数据;通过使用 CspParameters类来自定义CSP对象的行为;通过使用CryptoAPITransform类来修改加密信息;通过使用 RandomNumberGenerator类来为机密函数生成强随机函数。
 
5.COM 组件与.NET程序集互操作
托管组件和非托管组件在工作方式上存在很大差异,使得两类组件之间不能直接通信或不能无缝的作为同一应用程序的两个部分来有效运行。在.NET应用程序中使用COM组件的原因有多种,最重要的是保持已有的技术投资。例如,你可能已在早期Visual Basic版本中创建了一个自定义COM组件,且未将其升级为.NET版本,或者你需要使用COM组件来自动执行程序。不过.NET提供了优秀的解决方案来实现托管组件和非托管组件之间的交互。本节介绍如何创建与COM组件和非托管DLL通信的.NET应用程序,在.NET应用程序中使用COM组件以及设计可被COM组件调用的.NET应用程序。
 
使用Interop服务来访问COM组件。当.NET应用程序需要使用COM组件时,必须创建一个特殊的程序集,以处理.NET代码与COM组件之间的通信,该程序集称为Interop程序集,也称为RCW(runtime callable wrapper,运行库可调用包装)。Interop程序集在.NET程序集和COM组件之间提供一个中间层,以允许两者之间的通信。Interop程序集使COM组件表现得好像在与另一个COM组件通信,也使.NET代码表现得好像在与另一个.NET组件通信。
 
创建Interop程序集。通过读取COM组件中定义的类型的元数据来创建Interop程序集,并通过使用该元数据来创建.NET中的相应类,Interop程序集就可以将.NET类型转换为COM组件所支持的相应类型。使用Visual Studio是创建Interop程序集的最简单方法。如果想控制创建Interop程序集的过程,则可以使用TlbImp.exe命令行工具。
 
设计与COM组件进行交互操作的.NET类型。应当专门设计.NET组件,以使其有利于与COM组件进行有效通信。要允许COM组件与.NET组件通信,必须限定与COM组件进行交互操作的.NET类型。限定与COM组件进行交互操作的.NET类型包括Classes应当显式实现接口和设置托管类型访问修饰符为公共,公共类型是COM组件唯一可见的数据成员。
 
应用属性控制COM互操作性的类型转换。通过在.NET代码中应用ComVisibleAttribute特性,以控制向COM组件公开.NET应用程序的方式,可以将该特性应用于类型、方法、属性、参数以及字段。如果想对COM组件隐藏某个公共成员,则应该为该成员添加ComVisibleAttribute特性并设置Value值为 false。有两类属性可以控制.NET组件与COM组件的交互。第一类为设计时属性,它可以直接应用于.NET字段、属性、方法和类。第二类为转换时属性,在创建Interop程序集时,COM Interop工具使用此属性。所有这些属性都是System.Runtime.InteropServices命名空间的成员。
 
打包和部署程序集实现COM互操作性。在创建一个用于列出.NET程序集中所使用的所有数据类型的指定接口后,COM应用程序就可以使用.NET程序集。除这些步骤外,还必须提供程序集的部署指令并包含一个类型库。部署程序集时,如果将Interop程序集安装在全局程序集缓存中以供多个应用程序使用,则必须为程序集分配一个强名称。如果不为程序集分配强名称,则只能作为私有程序集安装,且每个程序集需要包含一个DLL(包含.NET Interop程序集)的副本。
 
创建.NET程序集类型库。COM组件要求在类型库中定义该数据,因为COM客户端无法从程序集元数据中读取类型信息。程序集、模块、类型、参数和字段的表示形式,首先必须要从程序集导出到类型库,在使用类型库之前,必须向COM注册它。可以使用类型库导出工具(TlbExp.exe)、TypeLibConverter类、程序集注册工具(RegAsm.exe)或.NET服务安装工具(RegSvc.exe)来创建.NET程序集的类型库。
 
平台调用服务(Platform Invocation Service)。尽管.NET Framework类库包含大量的类,这些类可提供应用程序要求的绝大部分功能,但有些工作却只能通过非托管代码来提供,例如组成Win32 API的DLL,必须使用平台调用以在.NET应用程序中调用非托管代码。平台调用Win32 API是公开操作系统功能的核心接口集,它是一种服务,最常见的用途是创建包含Win32 API函数的.NET类,调用Win32 API中的函数。它允许.NET应用程序调用非托管Dll,找到并调用函数,并跨交互操作边界封送该函数的参数。要使用平台调用,必须找到要调用的函数和该函数所在的DLL的名称和签名,编写相同签名的托管函数,需要确保所使用的函数数据类型是正确的,还必须为与被调用的非托管DLL中函数具有相同签名的函数添加DllImportAttribute特性。
 
平台调用数据封送。当从托管代码中调用非托管函数时,必须将数据类型转换为适当的非托管类型。转换数据类型和通过函数调用移动数据的过程称为封送处理。直接映射为非托管类型的托管类型为 blittable类型,那些非直接映射的称为non-blittable类型。在与COM通信时,如果想自定义并控制数据封送过程,则可以使用 System.Runtime.IneropServices.Marshal类来控制如何封送数据以及分配和释放内存。Marshal类允许通过分配非托管内存、复制非托管内存块以及将托管类型转换为非托管类型,以控制封送处理过程。如果想改变参数、字段或返回值的默认封送处理行为,则可以使用 System.Runtime.InteropServices.MarshalAsAttribute特性,只有当给定类型可以封送到多个类型时才必须使用该属性。
 
配置非托管函数。调用非托管DLL的一个重要方面就是配置函数。正确地配置完成非托管函数之后,就可以像调用其他.NET函数一样来调用它们。但是,如果将一个结构或对象传递给非托管代码,则必须提供附加信息以保存原始布局和对齐方式,可以使用.NET Framework 中的system.Rumtime.InteropServices.StructLayoutAttribute类来提供此信息。
 
使用Exception类映射HRESULT。 COM方法通过返回HRESULT来报告错误,而.NET方法通过抛出异常来报告错误。COM Interop通过将每个异常类映射到HRESULT来处理这两种错误报告方法之间的差别。每个Exception异常类都有一个HResult属性,该属性用于指定异常所映射到的HRESULT。从托管代码内调用非托管函数后,应该创建一个异常类来处理任何错误或创建一个自定义类来处理指定的错误。在.NET中,你可以通过创建一个继承自Exception类或ApplicationException类的类来创建用户定义的异常类。要确保COM可以理解用户定义的异常,应在自定义异常类的构造函数中设置HResult属性。要确定HRESULT的值,可以通过 System.Runtime.InteropServices.marshal.GetHrForException方法,该方法返回异常的 HRESULT值。如果COM对象实现IErrorInfo接口,则异常将包含错误的所有相关详细信息。如果它未实现此接口,则异常将只包含默认信息。
 
6. 实现服务应用程序和使用电子邮件消息
.NET Framework 通过System.ServiceProcess 命名空间提供用于实现、安装和控制 Windows 服务应用程序的类。服务是长期运行的可执行文件,其运行没有用户界面。实现服务包括从 ServiceBase类继承,也包括定义在传入开始、停止、暂停和继续命令时所处理的特定行为以及定义在系统关闭时所执行的自定义行为和操作。另外,.NET Framework还提供了允许你从服务应用程序发送电子邮件消息的类。
 
Windows 服务应用程序。Windows服务应用程序是一种长时间运行的可执行文件,可设置为在启动计算机时自动运行,无需用户的干预即可运行。服务通常以 LocalService账户在其自己的安全性上下文中运行服务,提供与本地计算机上非特权用户相似的权限。如果服务必须访问远程服务器,则该服务会向远程服务器出示匿名凭据。使用.NET Framework可以创建Win32OwnProcess服务和Win32ShareProcess服务两种类型的服务。 Win32OwnProcess服务可以在其自己的进程中运行,Win32ShareProcess服务可以与其他服务共享一个进程。通过检查 System.ServiceProcess.ServiceController.ServiceType属性确定服务的类型。
 
实现Windows服务应用程序。 System.ServiceProcess.ServiceBase类是一个Windows服务应用程序的核心类,要创建一个Windows服务应用程序,必须创建一个从ServiceBase类继承的服务类并重写ServiceBase类的OnStart和OnStop方法。Windows服务应用程序在其生存期中有几种状态,依次是安装、加载和启动。开始运行该应用程序之后可以将其暂停或停止。在Windows服务应用程序的Main方法中,必须创建一个服务类的实例并将其传递给ServiceBase类的Run方法。
 
安装Windows服务应用程序。要安装一个 Windows服务应用程序,需要创建一个特殊的安装程序类。ServiceInstaller类和ServiceProcessInstaller类都是从ComponentInstaller类继承,ServiceInstaller类用于在Windows服务应用程序中安装一个服务,必须为应用程序中的每一个Windows服务类创建一个ServiceInstaller类的实例,用来写注册表值。ServiceProcessInstaller类用于安装Windows服务应用程序的可执行文件,设置Windows服务应用程序中所有服务公共的注册表项值。
 
管理Windows服务应用程序。Windows 服务应用程序不提供用户界面,可以使用服务控制管理器启动、暂停、恢复或停止服务。如果想创建一个更友好的用户界面来控制Windows服务应用程序,可以使用System.ServiceProcess.ServiceController类通过编程方式控制Windows服务应用程序。使用该类可以创建一个管理程序来管理Windows服务应用程序。
 
启用自定义命令。无法通过服务控制管理器传递自定义命令给Windows服务应用程序。如果想让管理工具提供更改服务状态以外的功能,需要在Windows服务应用程序中启用自定义命令。要做到这些,可以在服务类中重写OnCustomCommand方法。服务有三种基本状态:运行、已暂停和已停止。但是,服务也可以等待一个命令来继续、暂停、启动或停止它。在这些情况下,状态分别是ContinuePending、PausePending、StartPending和StopPending。要确定服务的当前状态,可以检查服务类的Status属性。
 
使用电子邮件。通常需要应用程序将发送电子邮件消息作为日常数据处理的一部分。.NET Framework提供了从应用程序发送电子邮件消息功能类。通过使用MailMessage、MailAddress和 MailAddressCollection类创建电子邮件消息;通过使用MailAttachment类向电子邮件消息添加资源;通过使用 SmtpClient类发送电子邮件消息;通过使用SmtpException和SmtpFailedRecipientException类处理电子邮件异常;通过使用SendCompleteEventHandler委托来处理电子邮件完成事件。
 
7. 使用.NET类型元数据
不同于COM组件,.NET程序集通过使用元数据进行自我描述。在.NET Framework中,通过System.Reflection命名空间中的类进行运行时反射来检索元数据。在.NET应用程序中通过使用反射以加载类型、了解这些类型的成员和执行代码。
 
反射。反射是指在运行时检查程序集清单中的元数据的功能。程序集的元数据提供程序集和程序集中所有类型(包括泛型类型)的相关信息。使用此元数据,可在运行时加载程序集并创建和调用在程序集中定义的类型实例。程序集由包含类型的模块组成,而类型包含成员,因此,需要在创建模块前创建程序集,在创建类型前创建模块并在创建某些类型的成员前创建该类型。.NET Framework中的很多类使用反射来执行特定任务。例如,序列化类使用反射来确定在序列化过程哪些成员保持不变。.NET Framework提供System.Type类并在System.Reflection命名空间中提供了很多类用于执行反射。
 
使用Assembly类访问类型元数据。.NET Framework提供了Assembly类用于在反射期间访问程序集的元数据。可以使用Assembly类检查尚未加载到内存中的程序集,还可以通过使用此类加载并实例化程序集及其类型。Assembly类没有构造函数,需要使用Assembly类的某个静态方法(比如Load、LoadFile、 LoadFrom、GetAssembly、GetCallingAssembly、GetExecutingAssembly等)来创建 Assembly类的一个实例。创建Assembly类的一个实例后,你可以通过使用该类的属性检索程序集的相关元素据,包括CodeBase、 FullName、GlobalAssemblyCache、ImageRuntimeVersion和Location等。Assembly类还提供了检索包含于程序集的模块和类型的相关信息的方法,包括GetExportedTypes、GetModules、 GetReferencedAssemblies和GetTypes等。Assembly.ReflectionOnlyLoad方法将程序集加载到反射上下文中,在反射上下文中可以检查类型但程序集不被执行。
 
使用MemberInfo类访问类型元数据。有时,需要访问程序集成员的相关信息,包括其类型、方法、事件、字段和属性。.NET Framework提供了几个类来访问这些成员的相关信息,所有这些类都继承自System.Reflection.MemberInfo抽象类。 MemberInfo类具有5个子类,Type类表示类型声明的抽象类,是可用于访问程序集相关信息的主要类之一。它帮助你访问程序集内部的任何泛型或非泛型类型。使用 Type 的成员获取关于类型声明的信息,如构造函数、方法、字段、属性和类的事件,以及在其中部署该类的模块和程序集。MethodBase类提供一个类的方法和构造函数的相关信息。EventInfo类帮助你访问程序集中的事件的属性和元数据。FieldInfo类提供字段的属性和元数据的相关信息。 PropertyInfo类提供属性(Property)的属性(Attribute)和元数据的相关信息。
 
通过使用MethodBody类来检查方法的内容。.NET Framework提供了MethodBody类和LocalVariableInfo类用于检查方法的内容。可使用MethodBody类来访问方法内部的元数据和MSIL(Microsoft Intermediate Language,Microsoft中间语言)代码。可以使用LocalVariableInfo类来访问局部变量的属性和元数据,还可以通过读取 MethodBody类的LocalVariables属性检索方法中的局部变量。
 
添加元数据自定义信息。程序集是自我描述的,在 Visual Studio项目中,可在AssemblyInfo文件中添加程序集属性。它们提供如程序集的版本号、版权信息、标题或描述之类的详细信息。要将这些详细信息添加到程序集元数据,可使用程序集特性。各个程序集属性包括AssemblyTitleAttribute、 AssemblyDescriptionAttribute、AssemblyConfigurationAttribute、 AssemblyCompanyAttribute、AssemblyProductAttribute、 AssemblyCopyrightAttribute、AssemblyTrademarkAttribute、 AssemblyCultureAttribute、AssemblyVersionAttribute和 AssemblyFileVersionAttribute等。
 
动态使用程序集。.NET Framework提供了可用于在运行时动态创建程序集的自定义类,可以保存此程序集以供再次使用。通过使用自定义类,可以创建一个在运行时生成其自身代码的工具,通过使用此工具,可以快速创建一个应用程序。通过使用自定义类,可以在运行时动态地加载程序集,使用晚期绑定来调用动态加载程序集的代码,提供了创建灵活的应用程序的能力。
 
动态创建程序集。可以使用 System.reflection.Emit命名空间中的生成器类在运行时动态地生成程序集和类型。.NET Framework提供了AssemblyBuilder类用于动态地创建程序集,此类继承自Assembly类。创建动态程序集时,可通过调用 AssemblyBuilder.Save方法将它保存到磁盘。默认情况下,程序集保存为一个DLL(dynamic link library,动态链接库)。如果想将程序集保存为可执行文件,则必须调用AssemblyBuilde类的SetEntryPoint方法以指定用作应用程序入口点的过程。
 
生成器类。创建好动态程序集之后,可以使用 System.Reflection.Emit命名空间中提供的生成器类来在运行时生成模块、类型、构造函数、事件、字段、属性、方法、参数、局部变量和 IL代码,所有这些类都继承自System.Type类。ModuleBuilder用于定义并表示模块;EnumBuilder类用于定义并表示枚举;TypeBuilder类用于提供一组用于在运行库内定义类、添加方法和字段以及创建类的例程;ConstructorBuilder类用于定义类的构造函数;EventBuilder类用于定义类的事件;FiledBuilder类用于定义字段;PropertyBuilder类用于定义类中的属性;MethodBuilder类用于表示类的方法或构造函数;ParameterBuilder类用于定义参数;GenericTypeParameterBuilder类用于为动态定义的泛型类型与方法定义和创建泛型类型参数;LocalBuilder类用于定义方法或构造函数内的局部变量;ILGenerator类用于生成MSIL代码。这些生成器类互相依赖,必须在创建TypeBuilder之前创建 ModuleBuilder;在创建MethodBuilder、EventBuilder或PropertyBuilder之前创建 TypeBuilder;在创建ParameterBuilder或LocalBuilder之前创建MethodeBuilder。
 
绑定。绑定是寻找与唯一指定类型相对应的实现的过程。有两种绑定类型:早期绑定和晚期绑定。.NET同时提供了早期绑定和晚期绑定。早期绑定创建某种类型的通信信道,此信道帮助编译器有效使用此类型。早期绑定发生在将变量声明为特定类型时,通过将变量声明为特定类型(而不是Object类型)绑定到类型库。这甚至允许编译器在编译应用程序之前使用反射来验证类型。早期绑定对象允许编译器在应用程序执行前分配内存和执行其他优化。晚期绑定发生在直至运行时才将变量设置为特定类型的情况。如果将变量声明为 Object类型,但之后在运行时又将其设置为一个更加特殊的对象,则可以使用晚期绑定。变量直到运行时才能知道其成员,所以不能有效的使用内存或资源。晚期绑定比早期绑定效率低但更灵活,因为在运行前不必知道将要使用的类型。动态地加载类型时必须使用晚期绑定。
 
使用绑定类型控制成员绑定。通过使用反射可以在运行时加载程序集并调用其方法,如果直至运行时才加载类型,则必须使用晚期绑定。当在运行时动态加载类和使用晚期绑定时,必须使用BindingFlags 绑定标志来控制绑定对象的过程,BindingFlags枚举将控制绑定和反射搜索成员和类型的方法。这些标志将与Type.GetMember方法和 Type.InvokeMember方法一起使用实现动态加载类的成员。要在运行时动态加载类型,可以通过使用 System.Activator.CreateInstance方法先创建动态加载类型的实例,然后通过使用Type.InvokeMember方法对该类型调用方法。
 
8. 创建多线程应用程序和应用程序域
在支持大量用户执行多个任务的同时,将性能维持在可接受的水平并高效使用资源的能力称为可伸缩性。设计可伸缩应用程序是一项困难的任务,关键因素是并行而不是顺序执行任务的能力,并且可能需要在应用程序的整个生命周期中进行调整。默认情况下.NET应用程序使用单个执行线程,但是也可以使用多个线程来并行执行多个任务。不仅高端应用程序可以通过使用多个执行线程来提高性能,即便简单的应用程序任务也可以通过多个线程来达到此目的。
 
同步编程。应用程序可能提供可供同时执行多个任务的功能。在传统编程中,同步编程是大多数软件环境的默认模式,它以顺序方式调用多个方法,且只有在完成前一个调用后才可以进行下一个调用。它类似于一个队列,其中每个任务在被处理之前都必须排队等候。根据处理任务所需时间的不同,此同步编程方法的执行效率可能十分低。因此,为了提高效率和缩短响应时间,可以使用单独的线程来管理每个任务,并行执行线程。
 
进程和线程。任何应用程序在执行期间都被称为一个进程。进程将引用由正在执行的应用程序所使用的虚拟内存空间、系统资源和数据。处理器根据接收命令的顺序依次运行由进程发出的命令。线程是一个执行单元。每个进程至少要包含一个线程。如果一个进程包含多个线程,则操作系统会根据线程的优先级为每个独立的线程分别分配处理器时间。对于分配给进程的资源,该进程下的每个线程都拥有访问权,只是优先级高的线程能够更频繁地获得处理器的访问权而已。在多线程进程中,应用程序既可以创建独立的线程来运行耗时任务,同时又可以通过主线程或其他独立的线程来继续发出其他命令。如果执行的线程过多,则操作系统花在切换线程上的时间会比执行某个给定线程的时间还要多,这将导致性能下降。因此,应该根据实际情况来斟酌要使用的线程数,而且要相应地测试代码以验证其性能。
 
管理线程。.NET Framework在System.Threading命名空间提供了一些管理线程的类,使用Thread类可以通过编程方式来控制线程的行为。虽然在运行时可以通过“任务管理器”来设置线程的优先级,但Thread类提供的线程控制远远超出优先级分配,且不需要用户干涉。另外,可以通过Thread类创建销毁和暂停线程。创建线程的过程称为衍生新线程,销毁现有线程的过程称为注销或终止线程,暂停线程的过程称为使线程进入休眠状态。
 
创建线程。要创建线程,用户需要用到委托。与创建 Thread类相关联的委托有两个,ThreadStart和ParameterizedThreadStart。这两个委托都指向某个方法,而该方法可用作线程的起始点。其中,ParameterizedThreadStart委托可用于传递一个对象,而由该对象来自定义Start方法的行为,但 ThreadStart则无这项功能。
 
管理线程池。在需要使用大量短周期线程的情况下,比如复制或移动文件,CLR分配内存及创建线程所花费的时间可能会降低应用程序的整体性能。在这些情况下,.NET Framework提供ThreadPool类来提高性能。ThreadPool类管理一组已准备好被进程使用的线程。每个进程仅具有一个用于执行各种方法的线程池。不需要在每次启动线程时都进行资源分配,因为已经在池中创建了该线程,这将提高应用程序性能。
 
避免使用线程池情况。线程池不是万能的,有时候应该避免使用线程池,不应该在以下情况下使用线程池:需要前台线程;需要某个线程具有特定的优先级;具有可以阻塞线程一段时间的任务;线程池具有最大线程数的限制,因此线程池中的大量线程被阻止可能导致任务无法启动;需要将某些线程放置在单线程的单元中,所有的线程池都位于多线程单元中;需要有与线程关联的稳定标识,或需要将某个线程专用于某个任务。线程池的默认大小为每个可用处理器25个辅助线程,因此同时启动超过25个线程,在某个给定时间只会执行25 个线程,另外的线程会进入等待状态。
 
异步编程。异步编程允许用户界面快速响应用户操作,无须等待长时间运行的进程完成,具有同步编程技术所不具备的特殊功能。通过.NET Framework,可以异步调用任何方法,其中方法调用不需要等待其余的方法完成就可以继续执行应用程序。只需要一个额外的线程,就可以调用.NET应用程序中的任何方法,这样做需要创建一个委托,让它的签名与要调用的方法的签名相同。委托一般会公开BeginInvoke和EndInvoke方法。可以用四种方法处理异步调用:通过从主线程调用EndInvoke方法;通过使用一个WaitHandle方法以进行信号处理;通过检查 BeginInvoke方法所返回的对象的IsCompleted属性;调用BeginInvoke时通过使用一个回调委托。
 
使用异步类型来管理回调方法。你可以通过使用委托来执行异步调用,从而启动任何方法。对于每个异步调用,.NET Framework创建一个IAsyncResult类型的对象,用以管理单独的线程并检索异步调用的结果。在任何给定时间内都可以调用多个异步方法。异步调用适用于多数场景,例如,调用处理器密集型任务时,承载Internet上的Web服务时或同时处理多个任务时。文字处理应用程序需要将接收用户输入和检查拼写同时进行,在这种情况下,主线程可以处理用户输入,同时执行异步调用以检查每个新单词的拼写,同时还可以使用回调委托来将拼写错误的单词用下划线标示出来。该回调委托将使用BeginInvoke方法返回的IAsyncResult对象来检索异步方法的结果。
 
通过异步调用迁移线程的执行上下文。线程是在一个给定的执行上下文中执行,该执行上下文包含关于线程的安全、事务、同步及线程本地上下文信息。异步调用一个方法时,因为该方法可为不同的线程,所以可以在不同的上下文章执行它。一些情况下,你可能需要将执行上下文从一个线程传播到另一个线程,在.NET Framework中,可以通过ExecutionContext类来实现上下文从一个线程传播到另一个线程。当调用线程时,可以将 ExecutionContext类和HostExecutionContext类结合使用,以模拟CLR宿主执行上下文。还可以将 ExecutionContext类和HostExecutionContextManager类结合使用以控制CLR宿主应用程序所使用的上下文。
 
使用SynchronizationContext管理异步环境。在多个线程访问同一个资源时,可能需要某种同步机制以确保在多个线程访问同一数据的过程中它不被错误的更改。保持同步的一个方法是:将同步上下文从一个调用传播到另一个调用。SynchronizationContext类可用于验证线程的当前同步上下文,并将此上下文传播到一个同步或异步调用中。 SynchronizationContext类的Send和Post方法可分别用于同步和异步地调用某个方法。这两个方法都需要一个 SendOrPostCallback类型的参数,使用此参数指向一个方法,让该方法与调用方线程在同一同步上下文中执行。
 
应用程序域。在.NET Framework出现之前,执行中的每个应用程序都由不同的进程承载。而在应用程序运行时,承载它的进程会消耗大量内存。Web应用程序很受欢迎的原因是:单个Web服务器可以同时承载100多个应用程序。但是,如果每个应用程序都可以由属于它自身的进程承载,那么Web服务器将消耗掉其所有资源。为了解决这个问题,Microsoft重新设计了进程承载.NET应用程序的方法,并提出了应用程序域。通过使用应用程序域,单个进程可用作为多个应用程序的宿主。因此,只要在同一进程内,即使位于不同的应用程序域,应用程序之间也可以实现交互。.NET Framework在这方面提供了几个专用类,这些类之间可以交互,且都用于控制应用程序域承载应用程序的方法。
 
应用程序域优点。应用程序域用作应用程序之间的处理隔离边界,因为应用程序域是进程内的边界,所以多个应用程序能够共享单个进程。因为单个进程可具有多个应用程序域,所以单个宿主进程可用于运行多个应用程序,从而消耗更少的资源。此外,应用程序域中承载的应用程序也可以衍生多个线程。在同一进程的不同应用程序域内运行的应用程序彼此隔离。默认情况下,一个应用程序域中运行的应用程序无法访问其他应用程序域的资源。应用程序彼此隔离使得一个应用程序域中运行的代码不会影响其他应用程序域中运行的代码,还可以控制应用程序域级别上的权限。
 
配置应用程序域。在.NET中,执行单元不再是一个进程,而是一个应用程序域。当调用.NET Framework中的可执行文件、一段非托管代码、CLR宿主以及执行指令来创建默认应用程序域并加载所需程序集时,可以利用此基础结构来创建多个共享同一CLR宿主的应用程序,以降低操作系统资源消耗。为了做到这一点,你需要编写代码以创建应用程序域并加载程序集。在创建应用程序域时,可以创建一个 AppDomainSetup类型的对象来控制应用程序域的配置设置,这些设置将影响所有加载到该应用程序域的程序集。
 
创建应用程序域。要充分利用应用程序域,必须编写代码以创建应用程序域并向这些域加载程序集。只要应用程序可以同时运行、同时被加载到内存中以及能够共享某些资源,则这样的情况都适合使用应用程序域。如果在同一进程中运行这些应用程序,则可以节省系统资源,并降低跨应用程序调用的成本,这是因为上下文在同一进程内流动。通过使用AppDomain类可以创建应用程序域,该类的提供 CreateDomain方法来创建一个基于现有AppDomainSetup对象的新应用程序域。在创建应用程序域后,可以使用AppDomain类的各个成员进行检索并应用各种设置,例如应用程序域策略。
 
检索设置应用程序域信息。在一些情况下,可能需要检索关于现有应用程序域的信息,例如验证程序集权限时,向应用程序域加载程序集之前或将数据写入应用程序配置文件时。此时,可以使用AppDomain和 AppDomainSetup类来检索这些信息。还可以使用这些类来检索设置信息包括程序集是否从全局程序集缓存加载、程序集的加载目录以及应用程序的影像复制文件位置等。
 
加载程序集到应用程序域。在某些情况下,用户需要创建自己的应用程序域并向其中加载程序集,例如,创建承载的应用程序时、创建通过编程来卸载的工具或代码时或创建能动态卸载或重新加载的可插接组件时。要动态加载程序集,可以使用 AppDomain类的Load方法或Assembly类的Load和LoadFrom方法。而在加载程序集后,还可以使用反射来实例化程序集中的对象。另外,还可以使用CreateInstance方法来对程序集中类的对象进行实例化。
 
卸载应用程序域。在创建应用程序域后,最终需要从内存中卸载该应用程序域以释放资源,此时可以使用AppDomain.Unload方法来实现此功能。当卸载应用程序域时,需要释放该应用程序域所使用的所有数据结构,并从该应用程序域移除所有加载的程序集。在卸载过程中,线程将无法访问应用程序域中的任何数据,并可能由于尝试卸载默认应用程序域,尝试卸载一个已卸载的应用程序域或此时有线程无法停止执行等原因而抛出CannotUnloadAppDomainException类型的异常。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhzuo/archive/2009/07/15/4351927.aspx

原创粉丝点击