一直在CSDN汲取养分,学到了很多东西,抽个时间也反哺一下。
最近在做一个物联网服务器的cs版,由于业务的可变性太大,每次去更新正在运行的服务器不仅会带来一定的隐患,而且还麻烦。所以就考虑到做一个插件,可以动态的拓展业务,而不会对以前的功能造成影响。所以就想到了动态编译,但是论坛里关于动态编译的帖子太少,完成不了自己的需求,索性抽点时间自己造个轮子。水平有限,欢迎指教。
class DynamicCompiler
{
///
/// 设定要编译的代码及要引用的外部dll
///
/// 依赖的包名
/// CompilerParameters
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;
}
///
/// 编译
///
/// 编译依赖参数
/// 源码
/// 是否写入内存
/// 输出编译文件名称(dll)
///
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);
}
///
/// 多文件编译
///
///
/// 文件地址列表
///
///
///
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);
}
///
/// 通过Dll创建运行示例,该示例就是用户编译代码的类的实例,
/// 创建成功后,会将该实例加入到用户指定程序域运行
///
/// 程序区
/// 编译文件名称
/// 类名
/// 类构造函数参数
///
public static object creatRunningObj( CDomain domain,string sourceName,string className, object[] constructArgs)
{
RemoteLoaderFactory factory = (RemoteLoaderFactory)domain.MAppDomain.CreateInstance("RemoteAccess", "RemoteAccess.RemoteLoaderFactory").Unwrap();
// with help of factory, create a real 'LiveClass' instance
return factory.Create(sourceName, className, constructArgs);
}
///
/// 通过CompilerResults创建运行实例
///
///
///
///
public static object creatRunningObj(CompilerResults cr, string className)
{
if (cr.Errors.HasErrors)
throw new DynamicCompile.exceptions.DynamicComplierNullObjectException("传入了一个错误的CompilerResults");
return cr.CompiledAssembly.CreateInstance(className);
}
///
/// 获取程序集内所有的类
///
///
///
public static Type[]getClasses(CompilerResults cr)
{
if (cr.Errors.HasErrors)
throw new DynamicCompile.exceptions.DynamicComplierNullObjectException("传入了一个错误的CompilerResults");
return cr.CompiledAssembly.GetTypes();
}
}
作为一个工具,代码比较简单,研究过c#动态编译的基本上拿过来就能用。
/***********************************************************************************************************
* name:AbstractController
* 描述:抽象编译控制器,功能:指定文件路径脚本编译、指定依赖路径、指定输出路径,过滤已编译的文件,扫描程序集所有可用类并生成实例
* 作者:che
* 时间:2017-05-25
* *********************************************************************************************************/
using DynamicCompile.exceptions;
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;
namespace DynamicCompile.controller
{
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 _abscScriptDic; //className/*-hashCode*/ - AbstractCScript脚本实例
protected Dictionary _otherScriptDic ; //className/*-hashCode*/ - 其他脚本实例
protected Dictionary _compiledFiles ; // files_fullName.hashCode-filesFullName 已编译的文件
# endregion
public abstract void Initialize();
public AbstractController()
{
Initialize();
}
#region Method
///
/// 关闭并释放资源
///
public virtual void shutDown()
{
this.deleteAllOutDll(); //清空所有输出文件
this._abscScriptDic.Clear(); //清空所有执行实例
this._otherScriptDic.Clear();
this._compiledFiles.Clear(); //清除所有编译文件记录
this._mCDomain.shutDowm(); //关闭程序域
this.Dispose();
}
///
/// 删除所有输出文件
///
protected virtual void deleteAllOutDll()
{
while ((this._outDllCount--) > 0) //输出文件命名规则和删除规则相同
DynamicCompile.helper.AbstractFileUtils.delete(_outPutName + _outDllCount + ".dll");
}
///
/// 增加编译文件记录
///
///
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);
}
}
///
/// 扫描并编译指定文件下的脚本
///
public virtual void scanAndCompile()
{
string[] files = AbstractFileUtils.filterFiles(AbstractFileUtils.getFilesName(this._scriptPath, this.ScriptSuffix), this._compiledFiles.Keys.ToArray());//加载所有指定目录的脚本,并过滤掉已编译的文件
string[] dlls = helper.AbstractFileUtils.getFilesName(this._dllsPath, "*.dll");
if (files.Length == 0)
return;
CompilerParameters param = DynamicCompiler.setCompilerParameters(new helper.ProvideDLL(dlls)); //加载所有DLL
CompilerResults cr = DynamicCompiler.compile(param, files, false, getOutDllName()); //采用dll输出方式,方便程序管理
string txt = stringFormartUtils.format(cr); //失败则输出编译结果
if (txt.Length != 0)
throw new DynamicCompile.exceptions.DynamicCompileException(txt);
AbstractLogUtil.GetLogger().LogInfo("complie sucess,outPut:" + cr.CompiledAssembly.CodeBase + "\n class:" + DynamicCompiler.getClasses(cr).ToString());
addCompileFilesRecord(files);
Type[] classes = DynamicCompiler.getClasses(cr); //拿到程序集的所有类
foreach (Type item in classes)
{
compileClass(item, cr); //现在仅实现了对类的支持,后续可以支持接口、抽象类
compileAbstractt(item, cr);
compileAbstractt(item, cr);
}
}
///
/// 根据类名,在传入程序集寻找并创建类的实体记录
///
///
///
protected virtual void compileClass(Type item, CompilerResults cr)
{
if (item.IsClass != true) return;
if (this._abscScriptDic.ContainsKey(item.Name) || this._otherScriptDic.ContainsKey(item.Name))
return;
object ob = null;
try
{
ob = DynamicCompiler.creatRunningObj(this._mCDomain, cr.CompiledAssembly.CodeBase, item.FullName, null);
this._abscScriptDic.Add(item.Name, ob as IRemoteInterface); //加入到实体缓存列表方便重复使用
}
catch (Exception e)
{
ob = DynamicCompiler.creatRunningObj(cr, item.FullName); //使用cr创 建类时,可以加一下判定
this._otherScriptDic.Add(item.Name, ob);
DynamicCompile.helper.AbstractLogUtil.GetLogger().LogWarn("非继承AbstractCScript" + item.Name);
}
//object ob = DynamicCompiler.creatRunningObj(cr, item.FullName); //使用cr创 建类时,可以加一下判定
//AbstractCScript ab =ob as AbstractCScript;
//if (typeof(AbstractCScript).IsInstanceOfType(ob) != true) //如果不是继承了本实例的类
// continue;
}
private void compileInterface(Type item, CompilerResults cr)
{ }
private void compileAbstractt(Type item, CompilerResults cr)
{ }
///
///输出文件名称获取接口,
///没有做文件检查,因为在删除的时候,即使多次调用该函数却没有产生实际输出,也不会造成什么影响。
///
///
protected virtual string getOutDllName()
{
return this._outPutName + (this._outDllCount++) + ".dll";
}
#endregion
#region IDisposable Support
private bool disposedValue = false; // 要检测冗余调用
public string ScriptSuffix { get => _scriptSuffix; set => _scriptSuffix = value; }
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)。
}
// TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
// TODO: 将大型字段设置为 null。
disposedValue = true;
}
}
// TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
// ~TestController() {
// // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
// Dispose(false);
// }
// 添加此代码以正确实现可处置模式。
public void Dispose()
{
// 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
Dispose(true);
// TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
// GC.SuppressFinalize(this);
}
#endregion
}
}
测试控制器
using DynamicCompile.helper;
using DynamicCompile.process;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RemoteAccess;
using DynamicCompile.exceptions;
using System.IO;
using System.Drawing;
namespace DynamicCompile.controller
{
class TestController:AbstractController,IDisposable
{
public override void Initialize()
{
_scriptPath = @"..\..\DynamicCompile\files"; //该控制器要加载的脚本路径
_dllsPath = @"..\..\DynamicCompile\dlls"; //依赖dll所在路径
ScriptSuffix = "*.cs"; //脚本文件后缀,现指定为cs文件
_mCDomain = new CDomain("testDomain"); //用户定义程序域
_outPutName = @"..\..\DynamicCompile\dlls\OutDll_"; //编译输出dll名称
_outDllCount=0; //输出DLL计数
_abscScriptDic = new Dictionary(); //className/*-hashCode*/ - AbstractCScript脚本实例
_otherScriptDic = new Dictionary(); //className/*-hashCode*/ - 其他脚本实例
_compiledFiles = new Dictionary(); // files_fullName.hashCode-filesFullName 已编译的文件
AbstractLogUtil.SetLogger(new DefaultLogger(), LogLevel.Info);
}
public void compTest()
{
this.scanAndCompile();
}
public string test()
{
StringBuilder msg = new StringBuilder();
string classname = "class1";
if (this._abscScriptDic.ContainsKey(classname))
{
CSObj obj = new CSObj(_abscScriptDic[classname],"say", new object[1] { "testController" }, true);
Object robj= DynamicCompile.engine.AbstractEngine.creatEngine(engine.ENGINE_TYPE.CS).run(obj);
msg.AppendLine(robj as string);
Console.WriteLine(robj);
}
classname = "class3";
CSObj ob = new CSObj(_otherScriptDic[classname], "say", new object[1] { "testController" }, false);
Object rob = DynamicCompile.engine.AbstractEngine.creatEngine(engine.ENGINE_TYPE.CS).run(ob);
Console.WriteLine(rob);
msg.AppendLine(rob as string);
classname = "class3_son1";
ob = new CSObj(_otherScriptDic[classname], "say", new object[1] { "testController" }, false);
rob = DynamicCompile.engine.AbstractEngine.creatEngine(engine.ENGINE_TYPE.CS).run(ob);
Console.WriteLine(rob);
msg.AppendLine(rob as string);
return msg.ToString();
}
///
///输出文件名称获取接口,
///没有做文件检查,因为在删除的时候,即使多次调用该函数却没有产生实际输出,也不会造成什么影响。
///
///
private string getOutDllName()
{
return this._outPutName + (this._outDllCount++) + ".dll";
}
}
}
三个测试class;
using DynamicCompile.helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CompileTest.DynamicCompile.files
{
class class1:AbstractCScript
{
public class2 class2 = new class2();
public string say(string hello)
{
return new class3().say("class1") + "\n" + "class1: " + hello;
}
}
}
namespace CompileTest.DynamicCompile.files
{
class class3
{
public int age = 456;
public string say(string he)
{
return "class3:hello " + he;
}
public class class3_son1
{
public int age = 123;
public string say(string he)
{
return "class3_son1:hello " + he;
}
}
}
}
TestController contrl = new TestController();
private void button3_Click(object sender, EventArgs e)
{
contrl.scanAndCompile();
}
private void button4_Click(object sender, EventArgs e)
{
string msg= contrl.test();
txtResult.Text =DateTime.Now+"\n"+ msg;
}