浅谈.net插件式编程

一、动态加载控件
动态加载,最基本用到的就是反射机制。在System.Reflection的namespace下有一系列的关于获取Assembly信息、类(型)信息的类、接口、结构等。可能上面的话对急切想实现动态加载控件的朋友来说可能一点用也没有,那么就看下面的代码吧,也许可以使你马上实现你想要的:

//加载控件
Assembly assembly = Assembly.LoadFrom(@"C:\Controls.dll");
//获得类(型)
Type type = assembly.GetType("Controls.UserControl",false,true);
//设置筛选标志
BindingFlags bflags = BindingFlags.DeclaredOnly | BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance;
//调用构造函数并获得对象
Object obj = type.InvokeMember("UserControl", bflags |
BindingFlags.CreateInstance, null, null, null);
//将对象转换类型
System.Windows.Forms.Control c = (Control)obj;
//将控件添加到窗体
this.Controls.Add(c);


下面对上面程序段用到的一些变量、方法做一点说明
1、BindingFlags,枚举类型
BindingFlags.Instance : 对象实例
BindingFlags.Static : 静态成员
BindingFlags.Public : 指可在搜索中包含公共成员
BindingFlags.NonPublic : 指可在搜索中包含非公共成员(即私有成员和受保护的成员)
BindingFlags.FlattenHierarchy : 指可包含层次结构上的静态成员
BindingFlags.IgnoreCase : 表示忽略 name 的大小写
BindingFlags.DeclaredOnly : 仅搜索 Type 上声明的成员,而不搜索被简单继承的成员
BindingFlags.CreateInstance : 表示调用构造函数。忽略 name。对其他调用标志无效

2、Type.InvokeMember
public object InvokeMember(
string name,
BindingFlags invokeAttr,
Binder binder,
object target,
object[] args
);
参数
name
String,它包含要调用的构造函数、方法、属性或字段成员的名称。
- 或 -
空字符串 (""),表示调用默认成员。
invokeAttr
一个位屏蔽,由一个或多个指定搜索执行方式的 BindingFlags 组成。 访问可以是 BindingFlags 之一,如Public、 NonPublic、Private、 InvokeMethod 和 GetField 等。不需要指定查找类型。如果省略查找类型, 则将应用 BindingFlags.Public | BindingFlags.Instance。
binder
一个 Binder 对象,该对象定义一组属性并启用绑定,而绑定可能涉及选择重载方法、 强制参数类型和通过反射调用成 员。 - 或 - 若为空引用(Visual Basic 中为 Nothing),则使用 DefaultBinder。
target
要在其上调用指定成员的 Object。
args
包含传递给要调用的成员的参数的数组。
返回值
表示被调用成员的返回值的 Object。

二、插件编程
通过上面代码段,我们基本实现动态加载控件。由此我想到了现在网上提到很多的插件式的开发方法。通过动态加载控件,我们不是能很方便的为软件扩充功能吗?我不知道Eclipse这种插件是怎么实现的,但至少这种动态加载控件的方法实现插件编程的一个变通的方法。不是吗?我把一个功能模块做成一个控件,然后在程序启动是扫描目录,即可获得所有的控件,当点击菜单是,将控件加载到窗体就行了。我在母体程序里,我们所要做的只不过要一个容器窗口类来加载控件。当然,事先要有些约定,比如说,控件有哪些可供调用的方法等等。

 

 

反射(Reflection): 就是能够在运行时刻查询到类型信息的进程。他有以下的各个部分,可以根据你应用的需要选择其中的一个或者某些来使用:
1. Assembly:使用它来定义和加载一些Assembly, 加载存在于Assembly 中的modules,并且可以得到这个Assembly的类型,同时创建他的实例MSN原文:Use Assembly to define and load assemblies, load modules that are listed in the assembly manifest, and locate a type from this assembly and create an instance of it.
 
2. Module: 可以使用它来查找到包含该moduleAssembly信息,并且可以得到该module中的各种类;同时你不单单可以得到所有的全局函数和其他变量,也可以得到定义在该module中的非全局方法MSN原文:Use Module to discover information such as the assembly that contains the module and the classes in the module. You can also get all global methods or other specific, nonglobal methods defined on the module.
 
3. ConstructorInfo: 使用它你可以查找到一个构造函数的名字,参数,访问权限和实现的细节(比如说它是抽象的,还是虚拟的);使用某个类型的GetConstructors GetContructor方法来运行他的构造函数MSN原文:Use ConstructorInfo to discover information such as the name, parameters, access modifiers (such as public or private), and implementation details (such as abstract or virtual) of a constructor. Use the GetConstructors or GetConstructor method of a Type to invoke a specific constructor.
 
4. MethodInfo: 通过他您可以查找到某个方法的名字,返回类型,参数,访问权限和实现的细节(比如说它是抽象的,还是虚拟的?);它也提供两个方法来运行制定的方法,他们分别是:GetMethodsGetMethod.Use MSN原文:MethodInfo to discover information such as the name, return type, parameters, access modifiers (such as public or private), and implementation details (such as abstract or virtual) of a method. Use the GetMethods or GetMethod method of a Type to invoke a specific method.
 
5. FieldInfo: 使用它你可以查找到某个字段的名字,返回类型,参数,访问权限和实现的细节(比如说是否是静态的?);它提供了get set方法来访问字段。MSN原文:Use FieldInfo to discover information such as the name, access modifiers (such as public or private) and implementation details (such as static) of a field, and to get or set field values.
 
6. EventInfo:使用它你将可以查找到某一个事件的名字,事件句柄的数据类型,自定义的属性,声明的类型和相关的类型等等的信息,当然你可以增加或者去除一个事件。MSN原文:Use EventInfo to discover information such as the name, event-handler data type, custom attributes, declaring type, and reflected type of an event, and to add or remove event handlers.
 
7. PropertyInfo: 使用它你可以查找到某个属性的名字, 数据类型, 声明的类型,关联的类型 和只读或者可写状态等一些信息; 当然你可以得到和设置他的值。MSN原文:Use PropertyInfo to discover information such as the name, data type, declaring type, reflected type, and read-only or writable status of a property, and to get or set property values.
 
8. MSN原文:Use ParameterInfo to discover information such as a parameter's name, data type, whether a parameter is an input or output parameter, and the position of the parameter in a method signature.
 
9. MSN原文:Use CustomAttributeData to discover information about custom attributes when you are working in the reflection-only context of an application domain. CustomAttributeData allows you to examine attributes without creating instances of them.
看了上面的那么多字,你可以能还不知道怎么应用,不过没关系,下面将会提供一些例子,在看例子之前,我们先看看Type类。
 
System.Type
它是整个放射的中心,上面的各个部分都是围绕着他来进行的。他能够查询类型名字,类型中包含的模块和名称空间,以及判断该类型是值类型还是引用类型。
1. 得到类型名字:
float f = 1.0f;
Type t = f.GetType();
Console.WriteLine(t.Name);//这样就可以得到他的类型名字了。
2. 通过一个类型名字得到一个Type:
Type t = Type.GetType(“System.Int32”);
Console.WriteLine(t.Name);
3. 下面把查询类型的一部分列出来:
Type t = Type.GetType(typeName);
t.IsAbstract;
t.IsAnsiClass;
t.IsArray;
t.IsEnum;
t.IsClass
……
Assembly的应用
在这里就介绍一个称为“插件”技术的例子作为对这部分的总结吧!
现在使用一个现实中的例子,我在上海的某某地段开了一个超级市场,里面有各种各种牌子的店铺,你可以在超级市场新开设店铺,当然有一些店铺因为生意不好,不得不退出超级市场。那么在程序里面应该怎么样来实现这个过程呢?因为店铺的开张或倒闭不是固定的,是动态的, 这样我们就不能够在主程序中固化这些店铺,这个时候就需要我们动态的加载这些店铺了,下面请看如下实现:
1.     开设超级市场:
首先我们需要一个商铺管理类,用它来管理开店和关闭店铺,代码如下:
    public class ShopManager
    {
        protected static ShopManager m_ShopManager;
        protected ShopManager()
        {
        }
        public static ShopManager GetShopManager()
        {
            if (m_ShopManager == null)
            {
                m_ShopManager = new ShopManager();
            }
            return m_ShopManager;
        }
        public List<string> GetShops()
        {
            List<string> shops = new List<string>();
            string pluginFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");
            if (!System.IO.Directory.Exists(pluginFolder))
                return shops;
            foreach(string pluginFile in System.IO.Directory.GetFiles(pluginFolder, "*.dll"))
            {
                try
                {
                    Assembly assembly = Assembly.LoadFile(pluginFile);
                    foreach(Type type in assembly.GetTypes())
                    {
                        if(!type.IsClass || type.IsNotPublic) continue;
                        Type[] tempInterfaces = type.GetInterfaces();
                        if(((IList)tempInterfaces).Contains(typeof(IShop)))
                        {
                            IShop shop = (IShop)CreateInstance(type);
                            shops.Add(shop.GetShopName());
                        }
                    }
                }
                catch
                {
                }
            }
            return shops;
        }
 
        protected object CreateInstance(Type type, object[] args)
        {
            try
            {
                return Activator.CreateInstance(type, args);
            }
            catch (TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }
        protected object CreateInstance(Type type)
        {
            return CreateInstance(type, null);
        }
    }
接着定义个抽象的商铺类,我们的管理类只知道这个类,代码如下:
    public interface IShop
    {
        string GetShopName();
}
最后定义我们的超级市场类,请看代码:
    public class Market
    {
        protected static Market m_Market;
        protected List<string> m_Shops = new List<string>();
        protected Market()
        {
            //get all shops
            m_Shops = ShopManager.GetShopManager().GetShops();
        }
        public void ShowShops()
        {
            foreach (string shop in m_Shops)
            {
                Console.WriteLine(shop);
            }
        }
        public static Market GetMarket()
        {
            if (m_Market == null)
            {
                m_Market = new Market();
            }
            return m_Market;
        }
    }
这样整个市场就建立起来了。接着看看我们怎么开设店铺。
2 开设店铺:
首先创建一个店铺的接口类,并提供一个函数叫着:string GetShopName();
接着你就可以创建属于你的店铺了,这些具体的店铺都必须继承制店铺的接口类,实现他的接口函数,至于你要自己要为你开设的店铺创建什么功能,是你自己的事情,超级市场是不会关心的。下面是两个店铺的例子:
新建一个Class Library工程,命名为NikeShop,接下来是具体的代码:
using System;
using System.Collections.Generic;
using System.Text;
 
using SuperMarket;
namespace Shop
{
    public class NikeShop : IShop
    {
        public string GetShopName()
        {
            return "Nike";
        }
    }
}
同样再创建一个工程,叫做AdidasShop,代码如下:
using System;
using System.Collections.Generic;
using System.Text;
 
using SuperMarket;
namespace Shop
{
    public class AdidasShop : IShop
    {
        public string GetShopName()
        {
            return "Adidas";
        }
    }
}
最后把你生成的店铺的dll拷贝到plugin的文件夹里面就可以了。当你从新进入超级市场(启动主程序)的时候就可以看到该店铺了
3 关闭倒闭的店铺:
怎么样来关闭店铺呢? 很简单,我们只需要把这个店铺对应的dll文件从plugin里面删除就行,等你下次运行的时候,你将看不到该店铺了。
posted @ 2008-10-27 14:50  Zero.Li  阅读(1727)  评论(0编辑  收藏  举报