怎么是程序集,9.1           框架的契约-接口… 2

目       录

程序集

皇冠现金app 1
何以是先后集?

  • 1.程序集(assembly)是三个及一个之上托管模块,以及一些能源文件的逻辑组合。
  • 2.程序集是组件复用,以及执行安全策略和本子策略的小不点儿单位。
  • 3.程序集是包罗多个照旧多少个类型定义文件和财富文件的聚合。在程序集带有的具备文件中,有多个文书用于保存清单。(清单是元数据部分中一组数据表的聚集,其中饱含了程序集中某些文书的称谓,描述了先后集的版本,语言文化,发布者,共有导出类型,以及构成该程序集的全数文件)。
  • ④ 、在编译应用程序中,所开创的CIL代码存储在二个顺序集中,程序集包蕴可实施的应用程序文件(.exe扩张名文件)和别的应用程序使用的库(.dll增加名文件)。
  • 大约的说在.NET生成的dll和exe都以程序集,不过c++生成的就不是了。次第集带有财富文件,类型元数据(描述在代码中定义的每一类型和成员,二进制方式)、IL代码(那几个都被装在exe或dll中),每一种程序集都有投机的名号、版本等消息。那些新闻可以通过AssemblyInfo.cs文件来协调定义。

皇冠现金app 2皇冠现金app 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

 

皇冠现金app 4
程序集的独到之处:

  • 消除版本控制难题,程序只需求采纳须要的程序集,裁减了编码量(例如log4net.dll),程序的尺寸
  • 缓解dll抵触(Windows历史上盛名的 dll地狱)
  • 以在程序集中封装一些代码,提供必需的接口,供引用该程序集的品类应用

程序集常用的形式
Assembly.Load()方法,Assembly.LoadFrom()方法,Assembly.LoadFile()方法的分别!
1,Assembly.Load()
以此点子通进度序集的长名称(包含程序集名,版本音讯,语言文化,公钥标记)来加载程序集的,会加载此程序集引用的任何程序集,一般景观下都应该先行使用
这几个方法,他的履行成效比LoadFrom要高很多,而且不会促成重复加载的题材(原因在第三点上印证)
使用这些方法的时候,
CL科雷傲会应用一定的策略来探寻程序集,实际上CL福特Explorer按如下的顺序来定位程序集:
⑴即使程序集有强名称,在首先在大局程序集缓(GAC)中追寻程序集。
⑵如若程序集的强名称没有正确内定或GAC中找不到,那么通过安顿文件中的成分钦定的UCRUISERL来搜寻
⑶若是没有点名强名称只怕在GAC中找不到,CL卡宴会探测特定的公文夹:
假使你的应用程序目录是C:\AppDir,成分中的private帕特h指定了1个门道Path1,你要定点的顺序集是AssemblyName.dll则CLQX56将如约如下顺序定位程序集

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

 

若果以上办法不大概找到程序集,会时有爆发编译错误,即便是动态加载程序集,会在运转时抛出万分!
2,Assembly.LoadFrom()
以此办法从内定的门路来加载程序集,实际上那么些主意被调用的时候,CLKoleos会打开这些文件,获取其中的主次集版本,语言文化,公钥标记等音讯,把她们传递给
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中的智能指示,就是通过反射获取类的不二法门、属性的。程序集带有模块,而模块包罗类型,类型又富含成员。
反射则提供了打包程序集、模块和类型的靶子。
您可以应用反射动态地开创项目标实例,将项目绑定到现有对象,或从现有对象中拿走项目。
然后,可以调用类型的措施或访问其字段和属性。
代表项目讲明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。
皇冠现金app 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)会动态调用类的无参构造函数制造3个对象,再次来到值就是创建的对象,假设类没有无参构造函数就会报错。
GetConstructor(参数列表);//这些是找到带参数的构造函数。例如: object o =
Activator.CreateInstance(type, ” wolf ” , 22 , ” 未知 ” ); // 实例化
Type函数介绍
属性:
•type.Assembly:获取type所在的程序集对象
•type.FullName:获取type对象对应的类的全名称
•type.Name: 获取type对象对应类的 名称
•type.IsArray: 判断type是或不是为三个数组类
•type.IsEnum: 判断type是不是为1个枚举类
方法:
•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的品质对象

皇冠现金app 6皇冠现金app 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

 

皇冠现金app 8

版权评释:本文为博主原创文章,未经博主允许不得转发。

9.1           框架的契约-接口… 2

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

9.3           完毕接口… 4

9.4           反射机制… 5

9.5           反射工具类… 8

9.6           小结… 9

 

第天问     插件引擎布置

在介绍《第十章
宿主程序详细规划》此前对接口和插件的连锁内容举行一下完好无损介绍,在筹划宿主程序的时候会用到这么些文化,也是宿主程序与插件之间相互的大旨内容。

9.1    框架的契约-接口

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

   
三个插件程序集恐怕包罗三个插件类型,那么框架宿主程序是怎么辨别这几个品种是不是为要加载的插件呢?每种插件对象都有2个身价标识-接口,那些标识在框架设计中被称呼“通信契约”。接口可以被作为是一种概念了不可或缺的章程、属性和事件的联谊,因而宿主程序就足以经过那种契约来生成现实的实例对象,并对别的零件或接口公开可操作的目的。

   
插件式框架作为二个高聚合低耦合的阳台,它的功效定义与功能落成之间是分手的。只要符合插件规范的2遍开发组件都足以挂载到框架平巴尔的摩,而它并不并心那些零件的实际效果。当然,框架平台提供了一部分少不了的音讯、机制来保管这几个零件可以健康达成一遍开发的功用。

   
在具备七个逻辑层次的结构设计中,各层之间的通讯大多经过接口来促成,接口不会自由改变,倘若贰个层的效率爆发变化,不会影响别的层;只要健康已毕了接口的机件功用,那么程序的运转就平昔不难题。那种做法使得各层之间的相互影响降低到最低,不言而喻,接口在多事情层级中可以更好的解耦。

    在半数以上效率性的编程和统筹工作中,很少必要考虑“接口(interface)
”的状态,假使大家仅仅满足通过控件的章程在IDE上编程和使用.NET
Framework中一般的类库,恐怕永远不会在程序中运用到接口,固然在C#等面向对象语言的语法书中读者会过数拾一回探望过那个词,也只是达成日常的意义,并未领悟面向对象编程的核心思想。

    
接口是形似作为的定义和契约。如猫和狗等动物,只须要将普通的、公共性的习性、动作等概念在接口里,例如:有眼睛、可以吃东西等。固然差距动物之间存在很大差异,然而接口并不考虑它们分其余特点或效益的出入,例如:什么颜色的双眼、吃什么东西等。它只关怀这几个品种都必须贯彻接口定义的具有机能,而完结了这些接口就足以被看做是一种动物。

    由此,接口的三个非常紧要的成效是:

n  定义三个项目都亟需的公家措施、属性。

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

继承接口落成定义的法子、属性等,实际上是促成了一种政策。

9.2    插件的雏形-抽象类

接口与抽象类极度相像,例如两者都不能new2个实例对象,却都足以作

为一种契约和定义被利用。但是接口和抽象类有精神的不比,那些不一致蕴涵:

n  接口没有其余已毕部分,不过抽象类可以几次三番接口后有个别落到实处代码。

n  接口没有字段,不过抽象类可以分包字段。

n  接口可以被社团(Struct)继承,不过抽象类不行。

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

n  接口仅能连续自接口,而抽象类可以一连自其余类和接口。

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

在MSDN的连锁内容中,给出了如下关于接口与抽象类的提议:


若是预测要创设组件的多个版本,则开创抽象类。抽象类提供不难易行的章程来支配组件版本。通过立异基类,全部继承类都随更改自动更新。另一方面,接口一旦创设就无法改变,倘使要翻新接口的本子,必须创制1个全新的接口。


假如创设的机能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的目的,而接口最契合为不相干的类提供通用的效率。


如若要设计小而简易的作用模块,应该运用接口。如若要统筹大的效益单元,则应该利用抽象类。


假若要在组件的保有完成间提供通用的已已毕效益,应该采用抽象类。抽象类允许部分完结类,而接口不带有其余成员的贯彻。

9.3    完结接口

接口和抽象类都可以看作“通讯契约”,为子类提供标准。下边定义1个接口和抽象类。

//定义一个接口
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出来两个实例。那是一种静态加载程序集的办法。

  
在插件式应用框架中,那种格局并不相符。宿主程序在编译时并不知道它将要处理哪些程序集,更不曾艺术静态的将插件类型通过using关键字引入,这个都以在运作时才能获取的音信。在那样的情景下,也无力回天运用静态方法和new关键字来生成壹个档次实例。而是要求在运营时取得相关新闻动态加载程序集,这些进度被叫作反射。

  
反射是动态发现类型音信的一种能力,它好像中期绑定,扶助开发人士在程序运转时利用程序集新闻动态使用项目,这个音讯在编译时是不解的,反射还协助更尖端的行事,如能在运作时动态创造新类型,并调用这个项目标方法等。

   
JIT编译器在将IL代码编译花费地代码时,会查看IL代码中引用了那个类型。在运作时,JIT编译器利用程序集的TypeRef和AssemblyRef元数据表的记录项来规定哪三个顺序集定义了引用的类型。在
AssemblyRef元数据记录项中著录了先后集强名称的依次部分—包括名称,版本,公钥标记和言语文化。那多少个部分组成了一个字符串标识。JIT编译
器尝试将与这些标识匹配的次第集加载到目前的AppDomain中。若是程序集是弱命名的,标识旅长只含出名称。

   .NET
Framework中,为了贯彻动态加载,要求纯熟Assembly、Type和Activator等工具类的方式。框架平台主要利用了Assembly工具类,那些类中包罗Load、LoadFrom和LoadFile。

1.      Assembly的Load方法

  
在内部CLOdyssey使用Assembly的Load方法来加载那么些程序集,这一个措施与Win32的LoadLibray等价。在其间,Load导致CL牧马人对先后集应用二个版本重定向策略。并在GAC中找寻程序集,假使没有找到,就去应用程序的基目录,私有路径目录和codebase钦命的义务查找。若是是2个弱命名程序集,Load不会向程序集应用重定向策略,也不会去GAC中搜寻程序集。假若找到将赶回贰个Assembly的引用,假诺没有找到则抛出FileNotFoundException万分。注意:Load方法假使已经加载一个如出一辙标识的次第集只会不难的回到那个顺序集的引用,而不会去创制贰个新的先后集。

大部动态可扩展应用程序中,Assembly的Load方法是先后集加载到AppDomain的首选办法。那种措施亟待指定程序集的标识字符串。对于弱命名程序集只用钦点二个名字。

2.Assembly的LoadFrom方法

   
当大家领略程序集的途径的场面,可以应用LoadFrom方法,它同意传入1个帕特h字符串,在里头,LoadFrom首先调用AssemblyName的静态方法GetAssemblyName。这几个点子打开内定的公文,通过AssemblyRef元数据表提取程序集的标识,然后关门文件。随后,LoadFrom在其中调用Assembly的Load方法寻找程序集。到此处,他的作为和Load方法是一模一样的。唯一区其他是,即使按Load的办法没有找到程序集,LoadFrom会加载Path路径内定的主次集。此外,Path可以是UKoleosL。

皇冠现金app,3.Assembly的LoadFile方法

   
那个艺术初一看和LoadFrom方法很像。但LoadFile方法不会在里头调用Assembly的Load方法。它只会加载钦赐Path的程序集,并且那一个法子能够从随机路径加载程序集,同一程序集若是在差其他门路下,它同意被频仍加载,等于八个同名的主次集加载到了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奥迪Q3加载程序集的专断认同目录,在这些目录中如果存在引用的DLL,它会被加载,造成LoadFile会加载引用DLL的假象。能够在根目录下新建三个子目录如plugin,把引用的dll拷贝到那个中进行测试。

    
反射机制也有它的症结:安全性和属性方面。不过,框架平台在开行的时候、以及增加新设备驱动(插件)的时候需求接纳反射,一旦加载到宿主程序中,与静态引用程序集并未本质分化,都以寄存在内存中。

9.5    反射工具类

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

/// <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

相关文章