9.2           插件的雏形betvictor1946-抽象类… 3,程序集的独到之处

目       录

程序集

betvictor1946 1
怎么着是先后集?

  • 1.程序集(assembly)是三个及2个上述托管模块,以及部分财富文件的逻辑组合。
  • 2.程序集是组件复用,以及履行安全策略和版本策略的细卡片飞机地方。
  • 3.程序集是含有一个只怕八个类型定义文件和财富文件的集合。在先后集带有的有所文件中,有3个文本用于保存清单。(清单是元数据部分中一组数据表的汇聚,在那之中含有了程序集中有个别文本的称谓,描述了先后集的版本,语言文化,公布者,共有导出类型,以及构成该程序集的保有文件)。
  • 四 、在编写翻译应用程序中,所创建的CIL代码存款和储蓄在一个程序集中,程序集包蕴可举办的应用程序文件(.exe扩展名文件)和其余应用程序使用的库(.dll扩张名文件)。
  • 不难易行的说在.NET生成的dll和exe都是程序集,不过c++生成的就不是了。程序集带有财富文件,类型元数据(描述在代码中定义的每一种类和分子,二进制形式)、IL代码(那一个都被装在exe或dll中),各类程序集都有和好的称呼、版本等新闻。这一个消息方可由此AssemblyInfo.cs文件来协调定义。

betvictor1946 2betvictor1946 3

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 有关程序集的常规信息通过以下
// 特性集控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("AssemblyDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AssemblyDemo")]
[assembly: AssemblyCopyright("Copyright ©  2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 将 ComVisible 设置为 false 使此程序集中的类型
// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型,
// 则将该类型上的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]

// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("e7da9959-0c97-444c-aa40-6d9bbf728068")]

// 程序集的版本信息由下面四个值组成:
//
//      主版本
//      次版本 
//      内部版本号
//      修订号
//
// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,
// 方法是按如下所示使用“*”:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

AssemblyInfo.cs

View Code

 

betvictor1946 4
次第集的长处:

  • 不留余地版本控制难题,程序只需求使用需要的程序集,收缩了编码量(例如log4net.dll),程序的尺寸
  • 杀鸡取卵dll争辨(Windows历史上著名的 dll鬼世界)
  • 以在程序集中封装一些代码,提供必需的接口,供引用该程序集的项目选取

程序集常用的法子
Assembly.Load()方法,Assembly.LoadFrom()方法,Assembly.LoadFile()方法的界别!
1,Assembly.Load()
那一个主意通进度序集的长名称(包罗程序集名,版本音讯,语言文化,公钥标记)来加载程序集的,会加载此程序集引用的别样程序集,一般情状下都应该事先利用
这些方法,他的实施效能比LoadFrom要高很多,而且不会招致重复加载的题材(原因在第壹点上证实)
动用那么些艺术的时候,
CLOdyssey会应用一定的方针来查找程序集,实际上CLPAJERO按如下的依次来恒定程序集:
⑴假设程序集有强名称,在率先在全局程序集缓(GAC)中检索程序集。
⑵假如程序集的强名称没有科学钦点或GAC中找不到,那么通过安插文件中的成分钦命的ULANDL来探寻
⑶若是没有点名强名称或然在GAC中找不到,CL奔驰G级会探测特定的公文夹:
假若你的应用程序目录是C:\AppDir,成分中的privatePath内定了一个门路Path1,你要定点的顺序集是AssemblyName.dll则CL奇骏将根据如下顺序定位程序集

C:\AppDir\AssemblyName.dll 
C:\AppDir\AssemblyName\AssemblyName.dll 
C:\AppDir\Path1\AssemblyName.dll 
C:\AppDir\Path1\AssemblyName\AssemblyName.dll 

 

若是以上办法无法找到程序集,会爆发编写翻译错误,假若是动态加载程序集,会在运维时抛出很是!
2,Assembly.LoadFrom()
其一措施从钦点的路子来加载程序集,实际上那么些格局被调用的时候,CL汉兰达会打开那些文件,获取在那之中的先后集版本,语言文化,公钥标记等新闻,把她们传递给
Load方法,接着,Load方法应用地点的国策来搜寻程序集。固然找到了程序集,会和LoadFrom方法中钦点的门道做比较,倘使路径相同,该程序集
会被认为是应用程序的一部分,要是路径分化或Load方法没有找到程序集,那该程序集只是被视作二个“数据文件”来加载,不会被认为是应用程序的一有的。
那正是在第叁点中提到的Load方法比LoadFrom方法的实行作用高的缘由。其它,由于大概把程序集作为“数据文件”来加载,所以使用
LoadFrom从分化途径加载相同程序集的时候会招致重复加载。当然那些方法会加载此程序集引用的别的程序集。
3,Assembly.LoadFile()
这几个主意是从钦点的文件来加载程序集,和上边方法的分歧之处是其一措施不会加载此程序集引用的别的程序集!
结论:一般大家应该先行选项Load方法来加载程序集,假使赶上须求选用LoadFrom方法的时候,最好改变安排而用Load方法来顶替!
另:Assembly.LoadFile 与 Assembly.LoadFrom的区别
壹 、Assembly.LoadFile只载入相应的dll文件,比如Assembly.LoadFile(“abc.dll”),则载入abc.dll,假诺abc.dll中引用了def.dll的话,def.dll并不会被载入。
Assembly.LoadFrom则不平等,它会载入dll文件及其引用的别的dll,比如上边的例证,def.dll也会被载入。
贰 、用Assembly.LoadFrom载入叁个Assembly时,会先反省后面是否业已载入过一样名字的Assembly,比如abc.dll有八个本子(版本1在目录1下,版本2放在目录2下),程序一开端时载入了本子1,当使用Assembly.LoadFrom(“2\abc.dll”)载入版本2时,不可能载入,而是再次来到版本1。Assembly.LoadFile的话则不会做如此的自小编批评,比如下面的事例换成Assembly.LoadFile的话,则能正确载入版本2。
LoadFile:加载钦点路线上的先后集文件的始末。LoadFrom:
根据程序集的文本名加载程序集文件的内容。
区别:
LoadFile
方法用来来加载和反省有着同样标识但放在分歧途径中的程序集.但不会加载程序的依赖项。
LoadFrom 不能够用于加载标识相同但路径不一样的顺序集。
简单的说:假诺动态引用其余厂家的dll,能够用LoadFrom,因为厂家的dll还以引用其余的dll,假诺协调写的小dll,没有正视项,则用LoadFile,可是如此很少用。最新版的只辅助LoadFrom了。

第⑨章           插件引擎设计… 2

反射

反射正是动态获取程序集中的元数据(提供程序集的类型消息)的效应。也正是动态获取程序集中的元数据来操作类型的。比如大家使用的vs中的智能提示,就是经过反射获取类的法子、属性的。程序集带有模块,而模块蕴含类型,类型又富含成员。
反射则提供了包装程序集、模块和类型的指标。
您能够应用反射动态地创立项目标实例,将项目绑定到存活对象,或从现有对象中收获项目。
然后,能够调用类型的措施或访问其字段和总体性。
表示项目申明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。
betvictor1946 5
能够经过以下二种办法赢得Type:
1.由此类获得Type: Type t=typeof(Person);
Assembly中对type的类别的收获
调用Assembly的GetExportedTypes方法可以获取Assembly中定义的保有的public类型。
调用Assembly的GetTypes()方法可以取得Assembly中定义的富有的档次。
调用Assembly的GetType(name)方法能够赢得Assembly中定义的全名为name的类型新闻。如:
Type type = assembly.GetType( ” MyAssembly.Person ” );
动态创立对像
Activator.CreateInstance(Type
t)会动态调用类的无参构造函数创设三个指标,重临值正是开创的目的,假使类没有无参构造函数就会报错。
GetConstructor(参数列表);//这一个是找到带参数的构造函数。例如: object o =
Activator.CreateInstance(type, ” wolf ” , 22 , ” 未知 ” ); // 实例化
Type函数介绍
属性:
•type.Assembly:获取type所在的程序集对象
•type.FullName:获取type对象对应的类的全名称
•type.Name: 获取type对象对应类的 名称
•type.IsArray: 判断type是或不是为一个数组类
•type.IsEnum: 判断type是或不是为三个枚举类
方法:
•type.IsAssignableFrom(Type i):判断type是还是不是落到实处了接口i
•type.IsSubclassOf(Type father):判断type是不是持续了father
•type.IsInstanceOfType(objecto):判断o是不是为type类的实例
•type.GetFiled(“gender”):获取type中名为gender的字段对象
•type.GetMethod(“SayHi”):获取type中名为SayHi的章程对象
•type.GetProperty(“Age”):获取type中名为Age的性格对象

betvictor1946 6betvictor1946 7

//动态加载一个程序集
 Assembly assembly = Assembly.LoadFile(@"C:\02TestDll.dll");
//2.获取刚刚加载的程序集中的所有的类型
//assembly.GetType()  等价于  typeof(Assembly),不能获取某个程序集中国你的所有类型那个
//GetTypes()获取了所有的类型
 Type[] types = assembly.GetTypes();
////只获取那些public的类型
Type[] types = assembly.GetExportedTypes();
 //加入程序集中有多个类,只获取Person类的Type
//GetType()方法有重载,选择第二个重载,参数表示是要获取的类型的“完全限定名称”,即:命名空间.类名
//这里拿到了Type,其实就等价于typeof(Person)或者是:p.GetType();
 Type personType = assembly.GetType("_02TestDll.Person");
//获取所有的方法:personType.GetMethods();
////调用一个无参数,无返回值的方法
 MethodInfo method = personType.GetMethod("SayHi");            Console.WriteLine(method.Name);
//通过反射来创建一个Person类型的对象{其实就是通过Person的Type来创建一个Person对象}
object objPerson = Activator.CreateInstance(personType);
//调用这个方法
method.Invoke(objPerson, null);
//调用带参数,带返回值的方法
//1>找到对应的方法
 MethodInfo method = personType.GetMethod("Add");
object obj = Activator.CreateInstance(personType);
 //2>调用
 object result = method.Invoke(obj, new object[] { 102, 203 });
//调用重载的方法
 //1>找到对应的方法
MethodInfo method = personType.GetMethod("Add", new Type[] { typeof(int), typeof(int), typeof(int) });
 object obj = Activator.CreateInstance(personType);
//2>调用
int r = (int)method.Invoke(obj, new object[] { 1, 2, 3 });
 #region  通过反射获取类的属性,并赋值
//1.获取Name属性
PropertyInfo property = personType.GetProperty("Name");
object obj = Activator.CreateInstance(personType);
//2.为属性赋值
            property.SetValue(obj, "闫刘盘", null);
 //3.获取属性
 string name = property.GetValue(obj, null).ToString();
 #endregion

//#region 手动查找类型的构造函数,并且调用该构造函数来创建类型的对象
//查找到了对应的构造函数,但是还没有调用
ConstructorInfo ctor = personType.GetConstructor(new Type[] { typeof(string), typeof(int), typeof(string) });
//开始调用构造函数
object obj = ctor.Invoke(new object[] { "hpp", 16, "hpp@yahoo.com" });
bool IsAssignableFrom(Type c):(直译:是否可以从c赋值)判断当前的类型的变量是不是可以接受c类型变量的赋值。表示可以将Student类型赋值给Person类型,因为Student类型继承自Person类
bool IsInstanceOfType(object o):判断对象o是否是当前类的实例(当前类可以是o的类、父类、接口)
bool IsSubclassOf(Type c):判断当前类是否是类c的子类。只验证类与类之间的父子类关系,接口不包含。
IsAbstract   判断是否为抽象的,含接口

View Code

 

betvictor1946 8

版权证明:本文为博主原创作品,未经博主允许不得转发。

9.1           框架的契约-接口… 2

9.2           插件的雏形-抽象类… 3

9.3           完成接口… 4

9.4           反射机制… 5

9.5           反射工具类… 8

9.6           小结… 9

 

第8章     插件引擎布署

在介绍《第玖章
宿主程序详细安排》从前对接口和插件的连带内容开始展览一下全部介绍,在设计宿主程序的时候会用到那几个文化,也是宿主程序与插件之间相互的宗旨内容。

9.1    框架的契约-接口

    
插件式框架的宿主程序运转后,它首先会加载相应的布置文件(例如:设备驱动配置文件等),找到相应的插件程序集,这么些程序集以DLL文件格式存在,框架的宿主程序会找到钦定的插件类型,由插件引擎遵照插件类型(例如:IRunDevice)生成对象实例,由框架的宿主程序的管理器对插件实例进行管制和调度。

   
一个插件程序集或许包蕴八个插件类型,那么框架宿主程序是如何识别那么些品种是或不是为要加载的插件呢?各样插件对象都有一个地位标识-接口,这一个标识在框架设计中被称呼“通信契约”。接口能够被当作是一种概念了必不可少的主意、属性和事件的聚集,因而宿主程序就足以由此那种契约来生成现实的实例对象,并对其余零件或接口公开可操作的对象。

   
插件式框架当作2个高聚合低耦合的阳台,它的职能定义与效果达成之间是分离的。只要符合插件规范的二遍开发组件都能够挂载到框架平武汉,而它并不并心这几个组件的切实成效。当然,框架平台提供了有个别必不可少的音信、机制来保障那个零件能够正常达成3回开发的成效。

   
在具有多少个逻辑层次的结构划设想计中,各层之间的通讯大多种经营过接口来贯彻,接口不会轻易改变,如若四个层的职能产生变化,不会潜移默化其余层;只要健康落成了接口的组件作用,那么程序的运作就从未有过难题。那种做法使得各层之间的互相影响下跌到最低,显而易见,接口在多工作层级中能够更好的解耦。

    在大部功用性的编制程序和筹划工作中,很少必要考虑“接口(interface)
”的场所,借使大家只有满足通过控件的方法在IDE上编制程序和使用.NET
Framework中一般的类库,恐怕永远不会在程序中运用到接口,即便在C#等面向对象语言的语法书中读者会过多次看到过那几个词,也只是实现平日的效应,并未掌握面向对象编制程序的核心思想。

    
接口是一般作为的概念和契约。如猫和狗等动物,只必要将普通的、公共性的性质、动作等概念在接口里,例如:有眼睛、能够吃东西等。即使差别动物之间存在相当大差别,不过接口并不考虑它们分其余风味或效益的歧异,例如:什么颜色的眼睛、吃什么东西等。它只关切这个项目都必须兑现接口定义的兼具机能,而落到实处了那些接口就能够被看作是一种动物。

    由此,接口的三个第①的功用是:

n  定义八个类型都亟待的公家措施、属性。

n  作为一种不可实例化的种类存在。

一而再接口达成定义的章程、属性等,实际上是贯彻了一种政策。

9.2    插件的雏形-抽象类

接口与抽象类卓殊相像,例如两者都无法new三个实例对象,却都足以作

为一种契约和概念被选拔。可是接口和抽象类有实质的不比,那几个不一致蕴含:

n  接口没有其余完结部分,但是抽象类能够一而再接口后有的达成代码。

n  接口没有字段,然则抽象类能够分包字段。

n  接口能够被组织(Struct)继承,不过抽象类不行。

n  抽象类有构造函数和析构函数。

n  接口仅能连续自接口,而抽象类能够三番五次自其他类和接口。

n  接口支持多再而三,抽象类仅帮助单根继承。

在MSDN的有关内容中,给出了之类关于接口与抽象类的提议:


假若预测要创造组件的七个版本,则创制抽象类。抽象类提供简单易行的方法来决定组件版本。通过立异基类,全数继承类都随更改自动更新。另一方面,接口一旦成立就不能改变,假设要更新接口的版本,必须创制一个崭新的接口。


假如创立的坚守将在大范围的全异对象间接选举取,则接纳接口。抽象类应重点用来关系密切的靶子,而接口最契合为不相干的类提供通用的效应。


假使要规划小而精炼的功用模块,应该选拔接口。如若要统一筹划大的成效单元,则应该使用抽象类。


如果要在组件的拥有完结间提供通用的已兑现效益,应该利用抽象类。抽象类允许有的落成类,而接口不含有其余成员的落到实处。

9.3    达成接口

接口和抽象类都得以看成“通讯契约”,为子类提供正规。下边定义二个接口和抽象类。

//定义一个接口
public interface IMyInterface
{
       void Action(int type);
       string Method(int para);
}

//定义一个抽象类
public abstract class BaseAbstract:IMyInterface

{
       public abstract void Action(int type); //继承此类抽象类时必须实现这个方法。

       public string Method(int para)         //实现这个方法
       {
              return para.ToString();
       }
}

接轨接口的话,要求贯彻全体定义的方法或性质,如下代码:

public class MyClass1:IMyInterface
{
       public void Action(int type)
       {
              Console.WriteLine(type.ToString());
       }

       public string Method(int para)        
       {
              return para.ToString();
       }
}

继承抽象类的话,只需求完成抽象类没有兑现的主意或性质,一般为架空方法或品质,如下代码:

public class MyClass2:BaseAbstract
{
       public void Action(int type)   //继承抽象类,只需要实现这个函数。
       {
              Console.WriteLine(type.ToString());
       }
}

9.4    反射机制

   
有了设施驱动或插件,还无法挂载到框架平台的宿主程序中。大家考虑的题目是:已经有了自由多少个品种插件程序集,框架平台怎么样从程序集中依据类型定义在内部存款和储蓄器中生成插件对象?

  
回看普通情形下程序引用别的程序集组件的历程。首先,须求动用“添加引用”对话框加载程序集。然后,通过using关键字引用命名空间。最终,在命令空间下找到呼应的类,并new出来叁个实例。那是一种静态加载程序集的主意。

betvictor1946,  
在插件式应用框架中,那种形式并不适合。宿主程序在编译时并不知道它将要处理哪些程序集,更不曾章程静态的将插件类型通过using关键字引入,那些都以在运作时才能赢得的音讯。在那样的处境下,也无力回天利用静态方法和new关键字来生成3个项目实例。而是须求在运作时得到相关消息动态加载程序集,那几个进程被称之为反射。

  
反射是动态发现类型新闻的一种力量,它相仿早先时期绑定,补助开发人士在程序运维时选用程序集新闻动态使用项目,那个新闻在编译时是不解的,反射还协理更尖端的作为,如能在运转时动态创立新品类,并调用那一个品种的章程等。

   
JIT编写翻译器在将IL代码编写翻译开支地代码时,会查看IL代码中引用了那多少个类型。在运作时,JIT编译器利用程序集的TypeRef和AssemblyRef元数据表的记录项来规定哪1个程序集定义了引用的连串。在
AssemblyRef元数据记录项中记录了程序集强名称的种种部分—包涵名称,版本,公钥标记和言语文化。那多个部分构成了二个字符串标识。JIT编写翻译器尝试将与那么些标识匹配的主次集加载到当前的AppDomain中。借使程序集是弱命名的,标识上校只含盛名称。

   .NET
Framework中,为了达成动态加载,必要熟识Assembly、Type和Activator等工具类的艺术。框架平台重要运用了Assembly工具类,那几个类中回顾Load、LoadFrom和LoadFile。

1.      Assembly的Load方法

  
在中间CLRAV4使用Assembly的Load方法来加载那个程序集,那一个措施与Win32的LoadLibray等价。在里头,Load导致CL劲客对程序集应用多个本子重定向策略。并在GAC中检索程序集,借使没有找到,就去应用程序的基目录,私有路径目录和codebase钦定的地方查找。即使是3个弱命名程序集,Load不会向程序集应用重定向策略,也不会去GAC中摸索程序集。如若找到将重临三个Assembly的引用,若是没有找到则抛出FileNotFoundException至极。注意:Load方法假若已经加载三个同等标识的先后集只会简单的回到那一个顺序集的引用,而不会去创设多少个新的次序集。

大部动态可增加应用程序中,Assembly的Load方法是先后集加载到AppDomain的首要选择办法。那种办法亟待内定程序集的标识字符串。对于弱命名程序集只用钦命三个名字。

2.Assembly的LoadFrom方法

   
当大家知晓程序集的路径的场馆,能够利用LoadFrom方法,它同意传入多少个Path字符串,在里边,LoadFrom首先调用AssemblyName的静态方法GetAssemblyName。那个主意打开钦点的文件,通过AssemblyRef元数据表提取程序集的标识,然后关门文件。随后,LoadFrom在个中调用Assembly的Load方法寻找程序集。到此处,他的一举一动和Load方法是均等的。唯一不相同的是,假如按Load的情势没有找到程序集,LoadFrom会加载Path路径钦赐的次第集。别的,Path能够是USportageL。

3.Assembly的LoadFile方法

   
这么些主意初中一年级看和LoadFrom方法很像。但LoadFile方法不会在中间调用Assembly的Load方法。它只会加载钦点帕特h的程序集,并且这几个方式可以从随机路径加载程序集,同一程序集倘使在差异的途径下,它同意被反复加载,等于八个同名的主次集加载到了AppDomain中,那一点和方面的五个措施完全差别。可是,LoadFile并不会加载程序集的注重项,也正是不会加载程序集引用的别样程序集,那会招致运营时找不到其它参照DLL的要命。要消除这一个标题,需求向AppDomain的AssemblyResolve事件登记,在回调方法中呈现加载引用的次序集。类似于那样:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
       if (args.Name != null)
       {
              return Assembly.LoadFrom(string.Format("{0}\\plugin\\{1}.dll", Application.StartupPath, new AssemblyName(args.Name).Name));
       }
       return null;
}

     
特别注意:要测试LoadFile有没有加载引用的DLL,切不可将DLL拷贝到应用程序的根目录下测试,因为该目录是CL福睿斯加载程序集的暗中认可目录,在这几个目录中一旦存在引用的DLL,它会被加载,造成LoadFile会加载引用DLL的假象。能够在根目录下新建一个子目录如plugin,把引用的dll拷贝到那中间举办测试。

    
反射机制也有它的老毛病:安全性和天性方面。但是,框架平台在开发银行的时候、以及扩展新装置驱动(插件)的时候需求动用反射,一旦加载到宿主程序中,与静态引用程序集并未本质差别,都以寄存在内存中。

9.5    反射工具类

插件式框架平台采纳反射挂载设备驱动,在宿主程序中运作,须要2个专用的工具类来形成相关功能。代码定义如下:

/// <summary>
/// 一个轻便的 IObjectBuilder 实现
/// </summary>
public class TypeCreator : IObjectBuilder
{
       public T BuildUp<T>() where T : new()
       {
              return Activator.CreateInstance<T>();
       }

       public T BuildUp<T>(string typeName)
       {
              return (T)Activator.CreateInstance(Type.GetType(typeName));
       }

       public T BuildUp<T>(object[] args)
       {
              object result = Activator.CreateInstance(typeof(T),args);
              return (T)result;
       }

       /// <summary>
       /// 框架平台主要使用了这个函数。
       /// </summary>
       /// <typeparam name="T"></typeparam>
       /// <param name="assemblyname"></param>
       /// <param name="instancename"></param>
       /// <returns></returns>
       public T BuildUp<T>(string assemblyname, string instancename)
       {
              if (!System.IO.File.Exists(assemblyname))
              {
                     throw new FileNotFoundException(assemblyname + " 不存在");
              }
              System.Reflection.Assembly assmble = System.Reflection.Assembly.LoadFrom (assemblyname);
              object tmpobj = assmble.CreateInstance(instancename);
              return (T)tmpobj;
       }

       public T BuildUp<T>(string typeName, object[] args)
       {
              object result = Activator.CreateInstance(Type.GetType(typeName), args);
              return (T)result;
       }
}

9.6    小结

   
下一章节介绍宿主程序详细规划,需求对反射机制有早晚的询问,并且会采纳到地点的工具类,并在此基础上进行扩大。

    框架平台就要完全了,只需求一小步了。

 

笔者:唯笑志在

Email:504547114@qq.com

QQ:504547114

.NET开发技术联盟:54256083

文书档案下载:http://pan.baidu.com/s/1pJ7lZWf

法定网址:http://www.bmpj.net

相关文章