c#动态编译,⾃⼰做了个c#脚本管理框架,可以作为其他应⽤的插件使⽤。增强程序的拓展性
⼀直在CSDN汲取养分,学到了很多东西,抽个时间也反哺⼀下。
c#动态编译
1、初衷
最近在做⼀个物联⽹服务器的cs版,由于业务的可变性太⼤,每次去更新正在运⾏的服务器不仅会带来⼀定的隐患,⽽且还⿇烦。所以就考虑到做⼀个插件,可以动态的拓展业务,⽽不会对以前的功能造成影响。所以就想到了动态编译,但是论坛⾥关于动态编译的帖⼦太少,完成不了⾃⼰的需求,索性抽点时间⾃⼰造个轮⼦。⽔平有限,欢迎指教。
2、效果展⽰
插件框架⽬录结构
3、详细
1>、c#编译器
c#提供了⼀套动态编译的库 CSharpCodeProvider,具体编译过程我就不啰嗦了,⽹上⼀搜⼀⼤堆。我对其做了⼆次封装,⽅便调⽤,代码如下。(后⾯均是给予该封装进⾏编译操作)
class DynamicCompiler
{
/// <summary>
/// 设定要编译的代码及要引⽤的外部dll
/// </summary>
/// <param name="pdlls">依赖的包名</param>
/// <returns>CompilerParameters</returns>
public static CompilerParameters setCompilerParameters(helper.ProvideDLL pdlls)
public static CompilerParameters setCompilerParameters(helper.ProvideDLL pdlls)
{
// Sets the runtime compiling parameters by crating a new CompilerParameters instance
CompilerParameters objCompilerParameters = new CompilerParameters();
// objCompilerParameters.ReferencedAssemblies.Add("System.dll");
// Load the remote loader interface
foreach (string item in pdlls.Dlls)
objCompilerParameters.ReferencedAssemblies.Add(item);
// Load the resulting assembly into memory
//objCompilerParameters.GenerateInMemory = false;
return objCompilerParameters;
}
/// <summary>
/
// 编译
/// </summary>
/// <param name="param">编译依赖参数</param>
/// <param name="sourceCode">源码</param>
/// <param name="intoMemory">是否写⼊内存</param>
/// <param name="outPutName">输出编译⽂件名称(dll)</param>
/// <returns></returns>
public static CompilerResults compile(CompilerParameters param,string sourceCode,bool intoMemory,string outPutName=null)
{
CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
param.GenerateInMemory = intoMemory;
if(outPutName!=null)
{
param.GenerateExecutable = false;
param.OutputAssembly = outPutName;
}
return objCSharpCodePrivoder.CompileAssemblyFromSource(param, sourceCode);
}
/// <summary>
/// 多⽂件编译
/// </summary>
/// <param name="param"></param>
/
// <param name="files">⽂件地址列表</param>
/// <param name="intoMemory"></param>
/// <param name="outPutName"></param>
/// <returns></returns>
public static CompilerResults compile(CompilerParameters param, string []files, bool intoMemory, string outPutName = null)
{
CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
param.GenerateInMemory = intoMemory;
if (outPutName != null)
{
param.GenerateExecutable = false;
param.OutputAssembly = outPutName;
}
return objCSharpCodePrivoder.CompileAssemblyFromFile(param, files);
}
/// <summary>
/// 通过Dll创建运⾏⽰例,该⽰例就是⽤户编译代码的类的实例,
/// 创建成功后,会将该实例加⼊到⽤户指定程序域运⾏
/// </summary>
/// <param name="domain">程序区</param>
/// <param name="sourceName">编译⽂件名称</param>
/// <param name="className">类名</param>
/
// <param name="constructArgs">类构造函数参数</param>
/// <returns></returns>
public static object creatRunningObj( CDomain domain,string sourceName,string className, object[] constructArgs)
{
RemoteLoaderFactory factory = (RemoteLoaderFactory)domain.MAppDomain.CreateInstance("RemoteAccess", "RemoteAccess.RemoteLoaderFactory") // with help of factory, create a real 'LiveClass' instance
// with help of factory, create a real 'LiveClass' instance
return factory.Create(sourceName, className, constructArgs);
param name}
/// <summary>
/// 通过CompilerResults创建运⾏实例
/// </summary>
/// <param name="cr"></param>
/// <param name="className"></param>
/// <returns></returns>
public static object creatRunningObj(CompilerResults cr, string className)
{
if (cr.Errors.HasErrors)
throw ptions.DynamicComplierNullObjectException("传⼊了⼀个错误的CompilerResults");
return cr.CompiledAssembly.CreateInstance(className);
}
/// <summary>
/// 获取程序集内所有的类
/// </summary>
/// <param name="cr"></param>
/// <returns></returns>
public static Type[]getClasses(CompilerResults cr)
{
if (cr.Errors.HasErrors)
throw ptions.DynamicComplierNullObjectException("传⼊了⼀个错误的CompilerResults");
return cr.CompiledAssembly.GetTypes();
}
}
作为⼀个⼯具,代码⽐较简单,研究过c#动态编译的基本上拿过来就能⽤。
2>、插件框架设计思想
⼤致过程为: 脚本-编译器-控制器-执⾏引擎-执⾏结果。再对每层做抽象,个⼈习惯使⽤抽象类。在脚本层⽅⾯,因为脚本编译器是c#编译器,所以在脚本内部只能是c#语⾔,⾄于lua、Python等脚本,以后即使想要引⼊本框架,只要实现相应的引擎即可。引⼊⽅法可以是⽂件引⼊、串引⼊。后续。⼤致框架思想如下(跟实际实现会有出⼊,只是前期简单的画了下)
该图可对应上述框架⽬录。关于抽象控制器AbstractController和抽象脚本⽰例,我的想法是,控制器是要率先启动的,他并不知道后⾯还有多少脚本要加⼊运⾏。且在宿主进程运⾏过程中可以随时杀掉该附属脚本进程来保证宿主进程的稳定运⾏。所以中间抽象⼀层AbstractCScript,继承该类的所有脚本拥有本框架识别的同⼀出⼊⼝函数,在宿主进程中也可以提供相应的界⾯管理当前脚本系统已加载的所有脚本。未继承的脚本当然也会在控制器内出现,在主进程中也可以考虑把他们的管理加上。当然控制器的所有逻辑均根据个⼈需求实现,我在抽象控制器中实现了⼀些基本操作接⼝,例如加载和卸载管理,继承和⾮继承对象管理等,关键性接⼝均是可以复写的。我也写了个测试控制器如testController. 代码如下:
/***********************************************************************************************************
* name:AbstractController
* 描述:抽象编译控制器,功能:指定⽂件路径脚本编译、指定依赖路径、指定输出路径,过滤已编译的⽂件,扫描程序集所有可⽤类并⽣成实例
* 作者:che
* 时间:2017-05-25
* *********************************************************************************************************/
ptions;
using DynamicCompile.helper;
using DynamicCompile.process;
using RemoteAccess;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
ller
{
abstract class AbstractController
{
#region proteries
protected string _scriptPath ; //该控制器要加载的脚本路径
protected string _dllsPath; //依赖dll所在路径
protected string _scriptSuffix ; //脚本⽂件后缀,现指定为cs⽂件
protected CDomain _mCDomain; //⽤户定义程序域
protected string _outPutName ; //编译输出dll基础名称,匹配⾃定义命名规则
protected int _outDllCount; //输出DLL计数
protected Dictionary<string, IRemoteInterface> _abscScriptDic; //className/*-hashCode*/ - AbstractCScript脚本实例
protected Dictionary<string, object> _otherScriptDic ; //className/*-hashCode*/ - 其他脚本实例
protected Dictionary<int, string> _compiledFiles ; // files_fullName.hashCode-filesFullName 已编译的⽂件
# endregion
public abstract void Initialize();
public AbstractController()
{
Initialize();
}
#region Method
/// <summary>
/// 关闭并释放资源
/// </summary>
public virtual void shutDown()
{
this.deleteAllOutDll(); //清空所有输出⽂件
this._abscScriptDic.Clear(); //清空所有执⾏实例
this._otherScriptDic.Clear();
this._compiledFiles.Clear(); //清除所有编译⽂件记录
this._mCDomain.shutDowm(); //关闭程序域
this.Dispose();
}
/// <summary>
/// 删除所有输出⽂件
/// </summary>
protected virtual void deleteAllOutDll()
{
while ((this._outDllCount--) > 0) //输出⽂件命名规则和删除规则相同
DynamicCompile.helper.AbstractFileUtils.delete(_outPutName + _outDllCount + ".dll");
}
/// <summary>
/// 增加编译⽂件记录
/// </summary>
/// <param name="files"></param>
protected void addCompileFilesRecord(string[] files)
{
foreach (string item in files)
{
if (this._compiledFiles.ContainsKey(item.GetHashCode()))
throw new ControllerException("已编译过该⽂件!" + item);
this._compiledFiles.Add(item.GetHashCode(), item);
}
}
/// <summary>
/// 扫描并编译指定⽂件下的脚本
/// </summary>
public virtual void scanAndCompile()
{
string[] files = AbstractFileUtils.FilesName(this._scriptPath, this.ScriptSuffix), this._compiledFiles.Keys.ToArray());//加载所有指 string[] dlls = FilesName(this._dllsPath, "*.dll");
if (files.Length == 0)
return;
CompilerParameters param = DynamicCompiler.setCompilerParameters(new helper.ProvideDLL(dlls)); //加载所有DLL
CompilerResults cr = DynamicCompilerpile(param, files, false, getOutDllName()); //采⽤dll输出⽅式,⽅便程序管理
string txt = stringFormartUtils.format(cr); //失败则输出编译结果
if (txt.Length != 0)
throw ptions.DynamicCompileException(txt);
AbstractLogUtil.GetLogger().LogInfo("complie sucess,outPut:" + cr.CompiledAssembly.CodeBase + "\n class:" + Classes(cr).ToString addCompileFilesRecord(files);
Type[] classes = Classes(cr); //拿到程序集的所有类
foreach (Type item in classes)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论