关于在AppDomain中动态编译的解决方法
看着博文之前,希望大家能看看 Allan. 的这篇文章。
http://www.cnblogs.com/zlgcool/archive/2008/10/12/1309616.html
之前逛园子时偶尔看到 Allan.这篇文章中提供了两种动态编译的方案,其中第一种方法由于卸载不太方便的确会导致内存方面的问题。而改良的第二种方法会将dll保存至bin目录,并通过加载这个dll,再调用dll中的方法,最后还有个删除dll的操作。这一系列操作势必会影响效率和浪费资源。我当时就想能否不简化这一系列的操作。所以才有了这篇博文,在这里首先感谢 Fish Li ,峰哥利用自己时间为园子里面的同志们解决问题,这种做法值得大家学习。
好了,开始说正文:
程序集的确是不能单独卸载,但程序域可以卸载,Allan. 的第二种方法可以借鉴修改。
先看看解决方案
autoCompiled:提供对源代码动态编译的功能
AppDomainTest:调用程序
Form1.cs:新建AppDomain、加载autoCompiled
RemoteLoader.cs:提供远程访问调用
Winform中引用autoCompiled项目。
大致的思路就是将整个代码的编译过程(Compiled.cs类)全部拿到AppDomain中执行,RemoteLoader.cs提供远程访问。
winform代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using System.Reflection; namespace AppDomainTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string strSourceCode = @"using System; namespace Test { public class HelloWorld { public string GetTime(string strName) { return ""你好: "" + strName + "", 现在是北京时间: "" + System.DateTime.Now.ToString(); } } }"; object obj = CreateAppDomainExec(strSourceCode, "Test.HelloWorld", "GetTime"); MessageBox.Show(obj.ToString()); } /// <summary> /// 编译代码 /// </summary> /// <param name="strSourceCode">源代码</param> /// <param name="typeName">typeName,需要根据这个创建编译后的实例 namespace+className</param> /// <param name="FunctionName">调用的方法名</param> /// <returns>返回值</returns> public object CreateAppDomainExec(string strSourceCode, string typeName, string FunctionName) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationName = "TestAppDomain"; setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; AppDomain appDomain = AppDomain.CreateDomain("CreateDomain", null, setup); RemoteLoader remoteLoader = (RemoteLoader)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().GetName().FullName, typeof(RemoteLoader).FullName);//创建访问对象 remoteLoader.LoadAssembly("autoCompiled");//加载编译代码的程序集 object result = remoteLoader.CompiledExec(strSourceCode, typeName, FunctionName);//动态编译并执行 //卸载程序集 AppDomain.Unload(appDomain); appDomain = null; return result; } } }
RemoteLoader.cs类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace AppDomainTest { public class RemoteLoader : MarshalByRefObject { private Assembly assembly; /// <summary> /// 通过namespace读取 /// </summary> /// <param name="fullName"></param> public void LoadAssembly(string fullName) { assembly = Assembly.Load(fullName); } /// <summary> /// 通过路径读取 /// </summary> /// <param name="fullName"></param> public void LoadFromAssembly(string fullName) { assembly = Assembly.LoadFrom(fullName); } /// <summary> /// 编译代码 /// </summary> /// <param name="strSourceCode">源代码</param> /// <param name="typeName">typeName,需要根据这个创建编译后的实例 namespace+className</param> /// <param name="FunctionName">调用的方法名</param> /// <returns>返回值</returns> public object CompiledExec(string SourceCode, string typeName, string FunctionName) { object objClass = assembly.CreateInstance("autoCompiled.Compiled");//获取编译方法 object strResult = objClass.GetType().InvokeMember("GetCompiledByString", BindingFlags.InvokeMethod, null, objClass, new object[] { SourceCode, typeName, FunctionName }); return strResult; } } }
Compiled.cs类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.CodeDom.Compiler; using Microsoft.CSharp; using System.Reflection; namespace autoCompiled { public class Compiled { /// <summary> /// 编译代码 /// </summary> /// <param name="strSourceCode">源代码</param> /// <param name="typeName">typeName,需要根据这个创建编译后的实例 namespace+className</param> /// <param name="FunctionName">调用的方法名</param> /// <returns>返回值</returns> public object GetCompiledByString(string strSourceCode,string typeName,string FunctionName) { CompilerParameters objCompilerParameters = new CompilerParameters(); objCompilerParameters.ReferencedAssemblies.Add("System.dll"); objCompilerParameters.GenerateInMemory = true; CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(objCompilerParameters, strSourceCode); Assembly objAssembly = cr.CompiledAssembly; object objClass = objAssembly.CreateInstance(typeName);//获取方法 namespace.className if (objClass == null) return "获取类失败"; //反射调用方法 object objResult = objClass.GetType().InvokeMember(FunctionName, BindingFlags.InvokeMethod, null, objClass, new object[] { "Tsong" }); return objResult; } } }
执行步骤:
1:点击按钮之后,代码会新建一个程序域AppDomain
2:在新建立的ApppDomain中程序域加载程序集【remoteLoader.LoadAssembly("autoCompiled");】
3:获取程序集中的需要调用的类 【object objClass = assembly.CreateInstance("autoCompiled.Compiled");】
4:调用类方法进行编译并返回执行结果【GetCompiledByString】
5:卸载AppDomain
在AppDomain.Unload之后再次调用,会发现程序报错。说明程序集的确已随着AppDomain被卸载。
这种方法的确可以避免Allan.第二种方法繁琐的步骤,但是本质上还是通过加载编译后的引用。每次都会通过2次反射调用方法
第一次:调用引用的方法
第二次:调用自动编译代码中的方法
本人理想化的只是将动态编译之后的结果 Assembly 添加至AppDomain中,而不是将代码编译的过程都添加至AppDomain中。但由于水平有限,希望有大神可以指教