Sun_Blue_Sky

菩提本无树,明镜亦非台,本来无一物,何处惹尘埃 寻求内心的平静
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

插件?脚本?

Posted on 2010-08-17 16:50  Sun_Blue_Sky  阅读(411)  评论(0)    收藏  举报
很多时候,我们需要通过插件(add-in)方式来载入不同的策略。举个例子,在垂直搜索引擎开发中,爬虫抓取回来的数据必须进行分析获取元数据后才能用于搜索。我们很难用通用文本特征抽取算法来获取不同网站的精确数据,此时我们必须为某些站点编写特定的 "算法"。由于目标种子站点在运营过程中随时都可能发生变化,从设计角度来说,我们自然不能把这些特殊策略全部放到核心类库里。可行的做法是 —— "外挂",将每个特定的策略做成独立插件,通过特定的手段动态载入并执行,以此来达到在不影响整体系统的情况下,进行扩充和变更的目的。

目前在 .NET 环境下,可选的 "插件模型" 包括:

1. 程序集方式
  • System.Addin : .NET 3.5 BCL 提供的一个插件扩展模型,功能十分强大,不过整体有些偏 "重",过于复杂。
  • System.Reflection : 使用反射过于繁琐了些。
  • AOP : 基本和反射类似,不过可以通过配置文件来简化操作。
程序集模式需要我们提供编译后的程序集,这对于频繁变化的系统来说不是个好选择。

2. 脚本方式
  • System.CodeDom : 这个不错,不过要处理的细节也不少,诸如编译过程,引用及参数设置等等。
  • IronPython、Ruby.NET : 很多网络游戏服务器,都采取动态脚本来编写 "剧情",服务器本身只是一个执行环境。只是选择 Python、Ruby 需要不同的语言整合,有点麻烦。
  • CS-Script : 这个东西基本上是对 CodeDom 的封装,它为我们完成了所有的细节处理。

经过对比,CS-Script 更符合我的需求。这样一来,我们只需要将策略写成文本代码文件,放到特定区域,然后由 "Shell" 载入执行即可。就算修改,也只需一个记事本就能完成,更何况使用的还是我们熟悉的 C# 语言。(记事本 + 命令行编译器 + 命令行调试器 ~~~~ 不要学我们这的某个同事,要在服务器上装一整套 VS2005。 ~~~~)

看一个例子。
Server.exe

 

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;

namespace Server
{
    
// 服务器上下文环境
    public class MyContent
    {
        
public int I { getset; }
    }

    
// 插件接口
    public interface IAddin 
    {
        
void Execute(MyContent content);
    }

    
class Program
    {
        
static void Main(string[] args)
        {
            
// 载入策略脚本
            var assembly = CSScript.Load("TestAddin.cs");
            
            
// 查找实现了插件接口的类型
            var q = (from type in assembly.GetTypes() 
                     
where type.GetInterface("Server.IAddin"true!= null select type).SingleOrDefault();

            
// 创建环境上下文
            var content = new MyContent { I = 12345 };
            
            
// 创建插件对象 (当然,我们可以缓存该对象,没必要每次执行都创建一个实例)
            var addin = new AsmHelper(assembly).CreateObject(q.Name) as IAddin;
            
            
// 调用插件方法
            addin.Execute(content);
        }
    }
}

 

TestAddin.cs

//css_ref Server.exe;
using System;
using Learn.CUI;

public class TestAddin : IAddin
{
    
public void Execute(MyContent content)
    {
        Console.WriteLine(content.I);
    }
}

 

当然,我们还可以在另一个应用程序域中执行插件,这样就可以 Unload,从而在不关闭宿主的情况下修改插件脚本了。
Server.cs


代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSScriptLibrary;

namespace Server
{
    
// 服务器上下文环境,注意 MarshalByRefObject。
    public class MyContent : MarshalByRefObject
    {
        
public int I { getset; }
    }

    
// 插件接口
    public interface IAddin
    {
        
void Execute(MyContent content);
    }

    
class Program
    {
        
static void Main(string[] args)
        {
            var content 
= new MyContent { I = 12345 };

            var asmFile 
= CSScript.Compile("TestAddin.cs");
            
using (var helper = new AsmHelper(asmFile, "AddinDomain"true))
            {
                
// 注意设置依赖程序集搜索路径
                helper.ProbingDirs = new[] { AppDomain.CurrentDomain.BaseDirectory };

                
// 创建代理对象
                var addin = helper.CreateObject("TestAddin"as IAddin;
                
                
// 调用插件方法
                addin.Execute(content);
            }
        }
    }
}

 

TestAddin.cs

代码
//css_ref Server.exe;
using System;
using Server;

// 插件,注意 MarshalByRefObject。
public class TestAddin : MarshalByRefObject, IAddin
{
    
public void Test()
    {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
    }

    
public void Execute(MyContent content)
    {
        Console.WriteLine(content.I);
    }
}

 

 由于要跨域传递,因此某些地方需要添加 MarshalByRefObject。另外,注意设置 helper.ProbingDirs,否则找不到插件依赖的程序集,抛出异常。雨痕偷懒没有把公用类型放到一个单独的程序集中,正式开发时,建议将其放到一个独立的类库项目中。

CS-Script 的功能很多,用它替代 CodeDom,可以省不少力气。更多细节,请参考其帮助文件。