在C#中设置模块化程序的最佳方法

时间:2009-12-24 20:49:31

标签: c# .net module

我的朋友和我正在编写一个IRC C#bot,我们正在寻找一种方法来包含一个模块系统,以便用户可以编写自定义模块来扩展功能。

机器人使用Regex从服务器中分离所有原始数据,然后使用数据触发相应的事件。例如,典型的事件处理程序可能如下所示:

OnChannelMessage(object sender, ChannelMessageEventArgs e)
{
}

ChannelMessageEventArgs中将是频道名称,发送者的昵称,消息等......

我想拥有一个插件系统,以便人们可以随意构建模块并加载/卸载它们,当僵尸程序加载或运行时。

理想情况下,我希望能够动态编译.cs文件,而在plugin.cs文件中,可能会有以下几点:

  1. 要捕获的事件,例如OnChannelMessage和OnChannelEventArgs中的Channeldata
  2. 在提供此信息时该怎么做
  3. 一个帮助文本(我可以从主机器人内部调用..所以说一个字符串,帮助=“这是这个插件的帮助”,可以随时返回,而无需实际调用插件)
  4. 插件名称是什么等
  5. 感谢任何有关在编程方面比较陌生的人从哪里开始的想法。

5 个答案:

答案 0 :(得分:34)

我之前在项目中使用过这样的东西,但已经有一段时间了。可能有框架为你做这种事情。

要编写自己的插件架构,基本上您要为要实现的所有模块定义一个接口,并将其放在程序和模块共享的程序集中:

public interface IModule
{
     //functions/properties/events of a module
} 

然后,您的实现者将其模块编码到此程序集,最好使用默认构造函数。

public class SomeModule : IModule {} ///stuff 

在你的程序中(假设你的模块将被编译成它们自己的程序集)你加载一个包含模块的程序集的引用,找到实现模块接口的类型,并实例化它们:

var moduleAssembly = System.Reflection.Assembly.LoadFrom("assembly file");
var moduleTypes = moduleAssembly.GetTypes().Where(t => 
   t.GetInterfaces().Contains(typeof(IModule)));

var modules = moduleTypes.Select( type =>
            {   
               return  (IModule) Activator.CreateInstance(type);
            });

如果你想动态编译代码:你创建一个编译器对象,告诉它要引用哪些程序集(系统和包含IModule的程序集,以及所需的任何其他引用),告诉它将源文件编译成集会。从那里,您获得导出的类型,过滤保留实现IModule的那些,并实例化它们。

//I made up an IModule in namespace IMod, with string property S
string dynamicCS = @"using System; namespace DYN 
             { public class Mod : IMod.IModule { public string S 
                 { get { return \"Im a module\"; } } } }";

var compiler = new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
var options = new System.CodeDom.Compiler.CompilerParameters(
       new string[]{"System.dll", "IMod.dll"});

var dynamicAssembly= compiler.CompileAssemblyFromSource(options, dynamicCS);
//you need to check it for errors here

var dynamicModuleTypes = dynamicAssembly.CompiledAssembly.GetExportedTypes()
    .Where(t => t.GetInterfaces().Contains(typeof(IMod.IModule)));
var dynamicModules = dynModType.Select(t => (IMod.IModule)Activator.CreateInstance(t));

查看有关插件架构的教程并加载动态assmeblies,以便更好地了解这类内容。这只是开始。一旦你掌握了基础知识,你就可以开始做一些非常酷的事情了。

至于处理元数据(模块X名为YYY,应处理事件A,B和C): 尝试将其应用到您的类型系统中。您可以为不同的函数/事件组成不同的接口,或者您可以将所有函数放在一个接口上,并将属性(您在共享程序集中声明这些)放在模块类上,使用属性声明哪些事件该模块应订阅。基本上你想让人们为你的系统编写模块尽可能简单。

 enum ModuleTypes { ChannelMessage, AnotherEvent, .... }

 [Shared.Handles(ModuleTypes.ChannelMessage)]
 [Shared.Handles(ModuleTypes.AnotherEvent)]
 class SomeModule : IModule { ... }

 //this is a finer-grained way of doing it
 class ChannelMessageLogger : IChannelMessage {}
 class PrivateMessageAutoReply : IPrivateMessage {} 

玩得开心!

答案 1 :(得分:18)

Managed Extensibility Framework (MEF)提供了您正在寻找的内容,除非他们称之为插件架构。您可以提供用户可以构建的通用接口,然后MEF可以扫描目录中的dll并动态加载任何导出。

例如,您的插件作者可以创建类似

的内容
[Export(typeof(IModule))]
public class MyModule : IModule
{
   void HandleMessage(ChannelEventArgs e) {}
}

然后你的代码看起来像这样:

void OnChannelMessage(ChannelEventArgs e)
{
  foreach(IModule module in Modules)
  {
     module.HandleMessage(e);
  }
}

[Import]
public IEnumerable<IModule> Modules { get; set; }

答案 2 :(得分:3)

请查看微软的托管可扩展性框架(MEF) - 它建议您做一些您想要的事情,并提供许多其他功能:

Google results for 'managed extensibility framework'

答案 3 :(得分:3)

.NET 3.5的另一个选项是Managed AddIn Framework(MAF)。请查看以下链接。

示例和工具:http://clraddins.codeplex.com/

MAF博客:http://blogs.msdn.com/clraddins/

一篇好文章:http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/08/13/Build-AddIns-with-System-AddIn.aspx

答案 4 :(得分:2)

MEF - Managed Extensibility Framework现在是流行语 - 我认为这是最合适的。

如果你没有做到这一点,你也可以查看Prism,他们有另一种方法来实现模块。