一步一步学习开发BPM工作流系统--------(三)开发WinForm的应用平台-1
前面介绍过BPM有很多模块,这些模块都是C/S的,需要有一个应用平台来管理这些模块,如果你想自己开发B/S的,可以略过该篇。
我们先来看一下要开发的应用平台都有哪些功能?
首先要有一个主程序,是一个可执行的exe文件,指定统一的调用接口,可以调用实现该接口的Dll,配置信息写入数据库。有了这个应用平台,你可以任意的配置应用模块,用户需要那个给他们配置那个,方便管理和升级。
废话不多说,先看一下我们要做的应用平台的主界面。如下图:
左侧是导航树,右侧是工作区,整个主界面采用MDI的方式。主要用到的技术:反射动态调用dll.
本节内容包括:
一、反射技术的应用,将深入介绍程序集、类和方法的获取方法,以及如何过滤掉无用的类和方法名。
二、应用模块开发,通过一个demo介绍如何开发应用模块。
一、反射技术的应用
反射是一个比较高级的用法,它可以在运行期加载程序集,增加了程序的灵活性,关于反射的原理和相关知识这里不多讲,只讲几个用到的方法。
如何加载一个程序集?程序集是一个Dll文件也可以是一个EXE文件,总之只要是.Net编写的就可以。反射调用的原理是首先加载程序集文件到内存中,然后从内存中取到程序集中的类,并实例化类,然后取到类中的方法,就可以调用这个方法了,调用的时候,参数必须保持与程序集中的参数位置个数一致。在应用平台中,我们需要动态调用情况包括:动态调用一个窗体,动态调用一个方法,窗体又包括MDI类型和SDI类型,所以我们需要提供几个动态调用的方法。下面的代码动态调用一个MDI窗体。
/// <summary>
/// MDI方式动态调用dll中的窗体,只使用于winform.
/// </summary>
public System.Windows.Forms.Form CallMDIWindows() { if (_DllFileName.Trim().Length==0||_DllFileName==null) throw new Exception("CallMDIWindows调用失败,DllName 不允许为空!"); if (_DllClassName.Trim().Length==0||_DllClassName==null) throw new Exception("CallMDIWindows调用失败,DllClassName 不允许为空!"); if (_MainForm==null)throw new Exception("CallMDIWindows调用失败,MainForm没有指定!"); if (!File.Exists(_DllFileName)) throw new Exception("CallMDIWindows调用失败,[" _DllFileName "]不存在。"); try { System.Windows.Forms.Form fromCtrl = null; Assembly assembly = Assembly.LoadFile(_DllFileName);//从文件中加载一个程序集 Type tp=assembly.GetType(_DllClassName);//获取到要使用的类名,这里是一个窗体类 if (_ObjArray==null)//如果窗体类的构造函数不需要参数,这里要与窗体类保持一致 fromCtrl = ( System.Windows.Forms.Form)Activator.CreateInstance(tp);//实例化这个类,得到一个窗体的实例 else//窗体类的构造函数需要参数,这里要与窗体类保持一致 fromCtrl = (System.Windows.Forms.Form)Activator.CreateInstance(tp, _ObjArray);//实例化这个类,得到一个窗体的实例 fromCtrl.MdiParent=_MainForm;//MDI的主窗体 fromCtrl.Show();//打开这个窗体 return fromCtrl;//返回该窗体的引用 } catch(Exception ex) { throw ex; } }
下面的代码调用一个SDI窗体
/// <summary> /// SDI方式动态调用dll中的窗体,只使用于winform. /// </summary> public System.Windows.Forms.Form CallSDIWindows() { if (_DllFileName.Trim().Length==0||_DllFileName==null) throw new Exception("CallSDIWindows调用失败,DllName 不允许为空!"); if (_DllClassName.Trim().Length==0||_DllClassName==null) throw new Exception("CallSDIWindows调用失败,_DllClassName 不允许为空!"); if (!File.Exists(_DllFileName)) throw new Exception("CallSDIWindows调用失败,[" _DllFileName "]不存在。"); try { System.Windows.Forms.Form fromCtrl = null; Assembly assembly = Assembly.LoadFile(_DllFileName);//从文件中加载一个程序集 Type tp = assembly.GetType(_DllClassName);//获取到要使用的类名,这里是一个窗体类 if (_ObjArray == null)//如果窗体类的构造函数不需要参数,这里要与窗体类保持一致 fromCtrl = ( System.Windows.Forms.Form)Activator.CreateInstance(tp); else//窗体类的构造函数需要参数,这里要与窗体类保持一致 fromCtrl = ( System.Windows.Forms.Form)Activator.CreateInstance(tp,_ObjArray); fromCtrl.ShowDialog(); return fromCtrl; } catch (Exception ex) { throw ex; } }
下面的代码调用一个方法
/// <summary> /// 动态调用dll类中的方法 /// </summary> /// <returns></returns> public object CallMethod() { if (_DllFileName.Trim().Length==0||_DllFileName==null) throw new Exception("CallMethod调用失败,DllName 不允许为空!"); if (_DllClassName.Trim().Length==0||_DllClassName==null) throw new Exception("CallMethod调用失败,_DllClassName 不允许为空!"); if (_DllMethodName.Trim().Length==0||_DllMethodName==null) throw new Exception("CallMethod调用失败,DllMethodName 不允许为空!"); if (!File.Exists(_DllFileName)) throw new Exception("CallMethod调用失败,[" _DllFileName "]不存在。"); object obj=null; try { Assembly assembly = Assembly.LoadFile(_DllFileName);//从文件中加载一个程序集 Type tp = assembly.GetType(_DllClassName);//获取到要使用的类名,这里是一个窗体类 MethodInfo mi=tp.GetMethod(_DllMethodName);//从类中获取要调用的方法名 if (_ObjArray==null)//类的实例化不需要参数 obj = (object)Activator.CreateInstance(tp); else //类的实例化需要参数 obj = (object)Activator.CreateInstance(tp,_ObjArray); return mi.Invoke(obj,_ObjMethodArray);//这里默认方法都需要参数 } catch (Exception ex) { throw ex; } }
这里需要注意的几点:
1、Dll在加载前可以覆盖和删除,一旦加载即处于使用状态无法删除,所以要替换Dll的时候需要先关闭主程序。
2、获取类名的时候,类的名称必须带有完整的命名空间
我们先来学习动态调用,具体的开发过程如下:
第一步:我们先来做一个主应用程序,如下图:
第二步:我们开发一个测试的TestDll,界面如下图:
该窗体提供一个带参数的构造函数和一个Add方法。代码如下:
public partial class Form1 : Form { string _userName = ""; public Form1() { InitializeComponent(); } public Form1(string userName) { InitializeComponent(); _userName = userName; label1.Text = _userName; } private void button1_Click(object sender, EventArgs e) { MessageBox.Show(_userName ",调用Ok了!"); } public int Add(int a, int b) { return a +b; } }
第三步:编译TestDll,把TestDll.dll文件拷贝到第一步创建的主应用程序目录下。
第四步:在主应用程序三个按钮分别写如下代码:
private void toolStripButton2_Click(object sender, EventArgs e) { //打开一个dll中的MDI窗体 DynamicLibrary dyl = new DynamicLibrary(); Object[] objArray = new object[1];//dll中类构造函数必须具有的参数数组 objArray[0] = "云飞扬"; dyl.DllFileName =Application.StartupPath "\\TestDll.dll"; dyl.DllClassName = "TestDll.Form1"; dyl.ObjArray = objArray; dyl.MainForm = this;//主窗体 dyl.CallMDIWindows(); } private void toolStripButton3_Click(object sender, EventArgs e) { //打开一个Dll中的SDI窗体 DynamicLibrary dyl = new DynamicLibrary(); Object[] objArray = new object[1];//dll中类构造函数必须具有的参数数组 objArray[0] = "云飞扬"; dyl.DllFileName = Application.StartupPath "\\TestDll.dll"; dyl.DllClassName = "TestDll.Form1"; dyl.ObjArray = objArray; dyl.CallSDIWindows(); } private void toolStripButton1_Click(object sender, EventArgs e) { //执行Dll中的一个方法 DynamicLibrary dyl = new DynamicLibrary(); Object[] objArray = new object[1];//dll中类构造函数必须具有的参数数组 objArray[0] = "云飞扬"; Object[] objArrayM = new object[2];//dll中Add方法用到的参数数组 objArrayM[0] = 3; objArrayM[1] = 4; dyl.DllFileName = Application.StartupPath "\\TestDll.dll"; dyl.DllClassName = "TestDll.Form1"; dyl.ObjArray = objArray; dyl.ObjMethodArray = objArrayM; dyl.DllMethodName="Add"; object result= dyl.CallMethod(); MessageBox.Show(result.ToString()); }
第五步:运行主应用程序,分别执行三个按钮,分别出现下面的三种效果,说明执行正确。
调用Dll中的一个MDI窗体
调用Dll中的SDI窗体
执行一个Dll中的方法
我们成功的实现了动态调用的几种情况。窗体可以是任意复杂的窗体,方法可以是任意复杂的方法。这个Demo是学习一下利用反射实现动态调用,后面会有源码的下载。到这里我们只是学习了反射,WinForm应用平台的核心已经实现了,但要做成一个应用平台还有很多工作要做,比如dll如何配置,配置信息如何存放,打开的窗体如何判断是否已经存在,避免重复开启,还需要一个登录窗体,还需要一个扑捉错误的统一方法,可以破获dll模块中的所有错误,避免因模块出错整个平台崩掉,由于篇幅限制这些内容下一篇来介绍。
本篇源码下载地址:https://files.cnblogs.com/legweifang/WinAppDynamicDemo.rar