关于在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;
        }
    }
}
View Code

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;
        }
    }  
}
View Code

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;
        }
    }
}
View Code

 

执行步骤:

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中。但由于水平有限,希望有大神可以指教

posted @ 2013-05-21 17:48  Tsong Chen  阅读(1619)  评论(5编辑  收藏  举报