我和我的 friend 正在编写一个 IRC C# 机器人,我们正在寻找一种包含模块系统的方法,以便用户可以编写自定义模块来扩展功能。
机器人使用 Regex 拆分来自服务器的所有原始数据,然后用数据触发适当的事件。例如,典型的事件处理程序可能如下所示:
OnChannelMessage(object sender, ChannelMessageEventArgs e)
{
}
在 ChannelMessageEventArgs
中将是 channel 名称、发件人的昵称、消息等...
我想要一个插件系统,以便人们可以在机器人加载或运行时随意构建模块并加载/卸载它们。
理想情况下,我希望能够即时编译 .cs
文件,并且在 plugin.cs 文件中,有几件事:
- 捕获什么事件,比如OnChannelMessage,OnChannelEventArgs中的Channeldata
- 收到此信息后该怎么办,
- 一个帮助文本(我可以从主机器人内部调用..所以说一个字符串,help =“这是这个插件的帮助”,可以在任何时候返回而不实际调用插件)<
- 插件名称等
对于编程方面相对较新的人,感谢您提供关于从哪里开始的任何想法。
我以前在项目中使用过类似的东西,但已经有一段时间了。可能有一些框架可以为您做这类事情。
要编写您自己的插件架构,基本上您需要为所有要实现的模块定义一个接口(interface),并将其放在您的程序和模块共享的程序集中:
public interface IModule
{
//functions/properties/events of a module
}
然后您的实现者会将他们的模块编码到这个程序集中,最好使用默认构造函数。
public class SomeModule : IModule {} ///stuff
在你的程序中(假设你的模块将被编译成它们自己的程序集)你加载一个包含模块的程序集的引用,找到实现模块接口(interface)的类型,并实例化它们:
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));
查看有关插件架构和加载动态组件的教程,以更好地了解执行此类操作。这只是开始。一旦掌握了基础知识,您就可以开始做一些非常酷的事情了。
至于处理元数据(模块 X 名为 YYY,应该处理事件 A、B 和 C):
尝试将其应用到您的类型系统中。您可以为不同的功能/事件组成不同的接口(interface),或者您可以将所有功能放在一个接口(interface)上,并将属性(您将在共享程序集中声明这些)放在模块类上,使用属性来声明哪些事件该模块应该订阅。基本上,您想让人们为您的系统编写模块尽可能简单。
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 {}
玩得开心!