动态加载动态库(C#)反射

来源:互联网 发布:中国大数据研究院英语 编辑:程序博客网 时间:2024/05/17 21:15

动态加载动态库(C#)反射

使用反射加载动态库C#             Assembly ass = Assembly.LoadFrom("Lib1.dll"); //动态库的名称
Type t = ass.GetType("Lib1.Class1");  //namespace.classname
object obj1 = Activator.CreateInstance(t); //创建实例
MethodInfo mi = t.GetMethod("say");  //获得方法
object[] parm = new object[1] {"xuetao" };//  构造参数
object ret = mi.Invoke(obj1, parm);  //调用方法    反射通常具有以下用途:① 使用Assembly定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例;② 使用Module了解如下的类似信息,如模块的程序集以及模块中的类等:③ 使用CoustructorInfo了解如下的类似信息,如构造函数的名称、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtua1)等;④ 使用MethodInfo来了解如下的类似信息,如方法的名称、返回类型、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtua1)等;⑤ 使用FieldInfo来了解如下的类似信息,如字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值;⑥ 使用EventInfo来了解如下的类似信息,如事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,并添加或移除事件处理程序:⑦ 使用PropertyInfo来了解如下的类似信息,如属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,并获取或设置属性值;⑧ 使用ParameterInfo来了解如下的类似信息,如参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。

 3.总体设计思路

   插件是一种遵循一定规范的应用程序接口编写出来的程序模块。当应用程序已经部署,但业务发生了变化,这样可以通过读取插件配置信息,载入新的应用构件,实现变化的业务。

  对于应用系统的框架而言,扩展点是框架中预先定义的一些“点”。 在框架复用中应用构件的组装需要基于扩展点进行。构造性和演化性是软件的两个本质特征,作为一类重要的可复用软件制品。而基于扩展点可以组装不同的应用构 件以适应领域的变化性。则体现了框架对于软件演化特征的支持[3]。

   本文涉及到几个概念,插件配置定义,接口定义,方法定义和调用参数定义和返回参数定义。在本插件平台中,配置文件描述插件配置定义,接口定义,方法定义。对于调用参数定义和返回参数定义则采用通用对象和动态对象组[4]来实现传入和返回参数。

   插件平台的实现过程如图1所示。当平台运行初始化时,通过读取XML配置信息,装载DLL,通过C#的反射机制分析DLL里的全部实现类和方法。外部构件可以在平台容器中被实例化,并执行插件点的方法。实现的算法不再是编码硬绑定。

采用C#反射机制和动态参数实现可插件业务平台

  图1 PlugPlatform整个过程图

   这样,应用程序在运行过程中动态绑定要实现的外部业务,当业务发生变化,也只是替换这些外部的动态库,不用重新对应用程序进行修改和编译,实现了耦合绑定。

  4.具体实现

   PlugPlatform平台包括四个部分:① 配置文件的获取和解析;② 通用参数和动态参数组处理;③ 插件平台装载DLL并执行外部方法;④ 异常处理。

  4.1 配置文件的获取和解析

   配置文件以XML Schema为基础,分为两种类型,一种是类配置文件,主要描述关于外部DLL中的类以及方法的内容。第二种配置文件是接口配置文件,主要描述关于外部DLL中的接口以及方法的内容。

  类配置文件的XSD如图2所示。

采用C#反射机制和动态参数实现可插件业务平台

  图片看不清楚?请点击这里查看原图(大图)。

  图2 类配置文件的schema图

  按照此XSD形成的配置XML如图3所示。

采用C#反射机制和动态参数实现可插件业务平台

  图3  类配置文件的XML树

   同理可以接口配置文件的XSD内容(如图4)和XML树(如图5)。

采用C#反射机制和动态参数实现可插件业务平台

  图片看不清楚?请点击这里查看原图(大图)。

  图4  接口配置文件的schema图

采用C#反射机制和动态参数实现可插件业务平台

  图5  接口配置文件的XML树

   在实现将XML树状结构的数据转换为二维MethodObject哈希表,MethodObject哈希表是一key/value的键值对,其中key通常可用来快速查找,value用于存储对应于key的值。MethodObject类数据结构如下:

public class MethodObject {
private ClassObject classobject = null;
private InterfaceObject interfaceobject = null;
private DllFileObject dllfileobject = null;
private string name = string.Empty;
private string simplename = string.Empty;
private string implementname = string.Empty;

public string MethodName {get { return this.name; } set { this.name = value; } }
public string SimpleName { get { return this.simplename; }set { this.simplename = value; } }
public string ImplementName {get { return this.implementname; }set { this.implementname = value; }}
public ClassObject ClassObject {get { return this.classobject; }set { this.classobject = value; }}
public InterfaceObject InterfaceObject {get {return this.interfaceobject; }set {this.interfaceobject = value; }}
public DllFileObject DLLFileObject{ get { return this.dllfileobject; } set { this.dllfileobject = value; }}
}

   MethodObject哈希表中key为保证内容的唯一性而采用方法的全名。可以,这样形成的主键可以进行快速查找。value用来存储 MethodObject对象。同时MethodObject对象与ClassObject对象,InterfaceObject对象和DLLFile对 象都是多对一的关系,所以,一旦获得了MethodObject对象,就可以反推出ClassObject对象,InterfaceObject对象和 DLLFile对象。

采用C#反射机制和动态参数实现可插件业务平台

  图6  配置XML转MethodObject哈希表

   根据XML Schema可以构建XML文档树,对XML文档树的节点进行分层遍历,然后采用递归算法,依次把XML文档树上最边上的叶子转化为方法对象哈希表,实现方式如图6所示。

  4.2通用参数和动态参数组处理

   对于外部的方法,要传入参数,同时也获得结果。这些都要用一些通用的数据结构来描述。参数必须可以支持任何类型,是一个通用性的参数。通过创建一个数据的通用类,可以保证支持任何数据类型。

   由于传入和传出的参数有多有少,这就要求参数组能实现随意的自动增长和减少。通过设计一个动态自增长的参数数组就可以实现。设计模型如图7表示。

采用C#反射机制和动态参数实现可插件业务平台

  图7  DataValueObject类和DynamicArrayObject类的设计模型

   动态参数组的增加数组对象方法如下:

public DynamicArrayObject addObject(DataValueObject obj) {
if (Objects == null)   {
Objects = new DataValueObject[1];
Objects[0] = obj;
return this;   }
else {
DataValueObject[] objectList = new DataValueObject[Length + 1];
for (int i = 0; i < Length; i++) { objectList[i] = Objects[i];  }
objectList[Length] = obj;
Objects = objectList;
return this;}
}

   动态参数组的删除数组对象方法如下:

public DynamicArrayObject deleteObject(int idx) {
if (Objects == null)   return this;
else {
if (idx >= 0 && idx < Length && Length > 1) {
DataValueObject[] objectList = new DataValueObject[Length - 1];
for (int i = 0; i < idx; i++) {objectList[i] = Objects[i];}
for (int i = idx + 1; i < Length; i++) {objectList[i - 1] = Objects[i]; }
Objects = objectList;
return this; }
else {
if (idx == 0 && Length <= 1) {
Objects = null;
return this; }
else return this; } }
}

  4.3插件平台动态装载DLL并执行

   PlugFramework是整个PlugPlatform平台的核心内容。主要实现检查和装载DLL文件,动态创建实例化对象,验证并执行外部方法。

采用C#反射机制和动态参数实现可插件业务平台

  图片看不清楚?请点击这里查看原图(大图)。

  图8  PlugPlatform动态装载的全过程

   PlugPlatform依据配置文件可以了解装载的外部DLL文件和要求执行的类或接口方法。整个装载和的调用过程如图8说明。

   PlugFramework包含有类和接口,这些类和接口之间有继承、实现、关联关系。其类图如图9所示。

采用C#反射机制和动态参数实现可插件业务平台

  图9  PlugPlatform类图

   PlugFramework各个类的具体详细描述如下:

 

 

  序号

 

 

  名称

 

 

  实现功能

 

 

  备注

 

 

  1

 

 

  Plus.PlusConfig

 

 

  插件平台的配置信息类,可创建工厂类

 

 

  类

 

 

  2

 

 

  Plus.PlugFactory

 

 

  插件平台的工厂类,可以创建方法实现类

 

 

  类

 

 

  3

 

 

  Plus.IAction

 

 

  方法实现的接口

 

 

  接口

 

 

  4

 

 

  Plus.Framework.PlugAction

 

 

  方法实现的抽象祖先类

 

 

  类

 

 

  5

 

 

  Plus.Framework.ClassAction

 

 

  类对象的实现类

 

 

  类

 

 

  6

 

 

  Plus.Framework.InterfaceAction

 

 

  接口对象的实现类

 

 

  类

 

 

  7

 

 

  Plus.Framework.AssemblyManager

 

 

  Assembly的管理类,生成Assembly。

 

 

  类

 

 

  8

 

 

  Plus.Framework.TypeManager

 

 

  Type的管理类,可实现对Type、Object的生成和检查。包括动态方法的调用。

 

 

  类

 

 

   动态调用外部方法的核心代码如下所示:

public object InvokeClassMethod(String className, object[] objectArgs, String methodName, object[] methodArgs) {
Object[] newArgs = new Object[methodArgs.Length];
Object thisObject = new Object();
Type type = CreateType(className);
MethodInfo[] methods = type.GetMethods();
Object instance = CreateObject(type, objectArgs);
foreach (MethodInfo m in methods) {
if (m.Name == methodName) {
newArgs = ConvertArgsType(m, methodArgs);
try {
if (!m.IsStatic) thisObject = m.Invoke(instance, newArgs); //非静态方法,使用类实例调用
else thisObject = m.Invoke(null, newArgs);
return thisObject;}
catch (Exception e){throw new PlusException("不能动态调用方法,原因:" + e.Message, e); }}
}
return thisObject;
}

   图10表示PlugPlatform实现全过程。下面分别对每个步骤做一个详细描述:

  ① 外部应用请求动态调用。

  ② PlusConfig类根据配置文件创建PlugFactory对象。

  ③ PlugFactory对象创建一个Action对象。

  ④ Action对象获得MethodObject对象组,逆向产生DllFileObject对象。

  ⑤ 根据DllFileObject对象中的DLL文件信息,Action对象通过AssemblyManager类获得Assembly对象。

  ⑥ Action对象使用Assembly对象创建TypeManager对象。

  ⑦ Action对象传递MethodObject对象给TypeManager对象。

  ⑧ TypeManager对象可依据MethodObject对象获得ClassObject对象。并使用ClassObject对象信息动态创建一个外部ClassObject对象的实例instance。

  ⑨ TypeManager对象使用instance和MethodObject对象信息调用instance的动态方法。instance把执行结果返回给Action对象。

  ⑩ Action对象把执行结果返回给外部应用。

采用C#反射机制和动态参数实现可插件业务平台

  图片看不清楚?请点击这里查看原图(大图)。

  图10  PlugPlatform实现的顺序图

   其中Plus工厂模式采用了Factory模式。对于Assembly的生成采用了Singleton模式。

  4.4 异常处理

   由于应用程序中有很多不可预料的问题,本平台在很多地方都有可能出现人为错误,如找不到配置文件;配置文件的格式不对,不能解析配置文件;类或接口名称写 错了,不能实例化类;方法名称写错了,不能执行方法等等。增加异常处理主要是增强其容错性,在这里就不做更多的说明。

  5.应用实例

   本例子程序主要有三个方面组成:XML配置文件、外部DLL文件和PlusPlatform调用代码。

  5.1 XML配置文件

   采用的XML配置文件有两个,一个是针对类对象的XML配置文件,一个是针对接口对象的配置文件。

  其中类对象的XML配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<PlugPlatformResource>
<DllFile name="UserLibrary.dll" filepath ="/" objectType ="class">
<classobject name="UserLibrary.UserTest1" >
<Methodobject>testAction01</Methodobject>
<Methodobject>testAction02</Methodobject>
<Methodobject>testAction03</Methodobject>
</classobject>
<classobject name="UserLibrary.UserTest2" >
<Methodobject>testAction01</Methodobject>
</classobject>   
</DllFile>
</PlugPlatformResource>

   接口对象的配置文件与类对象配置文件基本相同,只不过配置信息中由类换成了接口:

<?xml version="1.0" encoding="utf-8" ?>
<PlugPlatformResource>
<DllFile name="UserLibrary.dll" filepath ="/" objectType ="interface">   
<interfaceobject name="InterfaceTest1"  implement="UserLibrary.UserTest2" >
<Methodobject>testAction01</Methodobject>
</interfaceobject>
</DllFile>
</PlugPlatformResource>

  5.2 DLL文件内容

   其编译的DLL文件为UserLibrary.dll,该dll文件包括两个类和一个接口,其内部代码为: public class UserTest1 {
public DynamicArrayObject testAction01(DynamicArrayObject outObject) {
DynamicArrayObject thisObject = new DynamicArrayObject();
//分解DynamicArrayObject
DataValueObject do1 = null;
string ls = null;
for (int i = 0; i < outObject.Length; i++) {
do1 = outObject.getObject(i);
ls += (String)do1.getDataValue(); }

DataValueObject do2 = new DataValueObject();
do2.setDataType(do1.getDataType()).setDataValue(ls);

//组装DynamicArrayObject,返回DynamicArrayObject
thisObject.addObject(do2);
return thisObject;
}

public DynamicArrayObject testAction02(DynamicArrayObject outObject) {
return outObject; }
}

public class UserTest2 : InterfaceTest1 {
public DynamicArrayObject testAction01(DynamicArrayObject outObject) {
DynamicArrayObject thisObject = new DynamicArrayObject();          
//分解DynamicArrayObject
DataValueObject do1 = null;
string ls = null;
for (int i = 0; i < outObject.Length; i++) {
do1 = outObject.getObject(i);
ls += (String)do1.getDataValue(); }

DataValueObject do2 = new DataValueObject();
do2.setDataType(do1.getDataType()).setDataValue(ls);

//组装DynamicArrayObject,返回DynamicArrayObject
thisObject.addObject(do2);
return thisObject;
}

public DynamicArrayObject testAction02(DynamicArrayObject outObject){
return outObject;}
}

public interface InterfaceTest1 {
DynamicArrayObject testAction01(DynamicArrayObject outObject);
}

  5.3 调用插件平台代码

   调用代码也分为两类,一类是针对类对象处理的,代码如下:

    DynamicArrayObject thisObject = new DynamicArrayObject();
DataValueObject do1 = new DataValueObject();
DataValueObject do2 = new DataValueObject();
do1.setDataType("string").setDataValue("类测试:第一个对象值.");            
do2.setDataType("string").setDataValue("第二个对象值.");
thisObject.addObject(do1).addObject(do2);
string dllFile = Application.StartupPath + "\DllClassFile.xml";

PlugFactory factory = PlusConfig.BuildFactory(dllFile);
IAction action = factory.CreatAction();
DynamicArrayObject outputObject = action.Execute("UserLibrary.UserTest1.testAction01", thisObject);

   另一类是针对接口对象处理,代码如下:

DynamicArrayObject thisObject = new DynamicArrayObject();
DataValueObject do1 = new DataValueObject();
DataValueObject do2 = new DataValueObject();
do1.setDataType("string").setDataValue("接口测试:第一个对象值.");           
do2.setDataType("string").setDataValue("第二个对象值.");
thisObject.addObject(do1).addObject(do2);
string dllFile = Application.StartupPath + "\DllInterfaceFile.xml";

PlugFactory factory = PlusConfig.BuildFactory(dllFile);
IAction action = factory.CreatAction();
DynamicArrayObject outputObject = action.Execute("InterfaceTest1.testAction01", thisObject);

  可以对返回的DynamicArrayObject做分解查看,满足设计要求。

  6.结束语

   反射机制结合动态数组很好地解决了应用软件的后期维护和升级。对于应用软件的变化,可不改动任何现有的程序,只要修改XML配置文件的相应对象名称和加载 新的对象即可,程序不需要任何的重编、重启和硬性改动,并保证了原应用系统的可复用性从而实现降低耦合度,实现复用的目标。

  本模型在层与层之间借助类调用和接口实现,利用反射机制把调用者与实现者在编译期分离。运行期通过读配置文件动态加载实现类,并通过接口将实现 者强制转型,使其为调用者所用,完成调用者和实现者的解耦。但是,这个功能并不是完全完善,对于插件平台也有很多的改进性,如果能对类和接口配置文件更加 丰富,把插件平台升级为一个框架容器,该容器能把对象之间的依赖关系先行剥离,然后在适当时候由容器负责产生具体的实例再注射到调用者中,即控制权由应用 代码中转到了外部容器,控制权发生了转移。即所谓的控制反转模式,这种模式在java中已经有比较成熟的框架,如Spring等。相信凭借 Microsoft.Net庞大的技术框架平台,在C#上也会有这样的控制反转框架出现。

  参考文献

  [1](美)Karli Watson Christian Nagel等.康博,译.C#入门经典.北京:清华大学出版社,2006.

  [2] MSDN Library .NET Framework开发员指南:在运行时了解类型信息.2003.

  [3] 刘瑜 张世琨 王立福 杨芙清. 基于构件的软件框架与角色扩展形态研究. 软件学报,2004.14(8):1364-1370

  [4] 段春笋 杜立新. C#动态数组设计原理. 电脑编程技巧与维护,2005.(7):24-25

  [5] 何文海 谢建刚. 基于.NET平台的插件式应用框架开发. 电脑知识与技术:学术交流,2007.(8):755-756

  [6] 冷山述 陆倜 武装. 用C#构造可复用软件体系结构. 航空计算技术,2003.(4):88-90,93

  [7] 殷凯 谢文威. 在工厂方法模式中.NET反射技术应用的研究. 常州工学院学报,2006.(4):28-34

  [8] 姚明 李家兰. 基于.NET的通用软件开发平台的研究与实现. 电脑知识与技术:学术交流,2007.(8):797-798

原创粉丝点击