让C#语言充当自身脚本!——.NET中的动态编译

原文:让C#语言充当自身脚本!——.NET中的动态编译

代码的动态编译并执行是.NET平台提供给我们的很强大的一个工具,用以灵活扩展(当然是面对内部开发人员)复杂而无法估算的逻辑,并通过一些额外的代码来扩展我们已有 的应用程序。这在很大程度上给我们提供了另外一种扩展的方式(当然这并不能算是严格意义上的扩展,但至少为我们提供了一种思路)。

动态代码执行可以应用在诸如模板生成,外加逻辑扩展等一些场合。一个简单的例子,为了网站那的响应速度,HTML静态页面往往是我们最好的选择,但基于数据驱动的网站往往又很难用静态页面实现,那么将动态页面生成html的工作或许就是一个很好的应用场合。另外,对于一些模板的套用,我们同样可以用它来做。另外这本身也是插件编写的方式。

最基本的动态编译

.Net为我们提供了很强大的支持来实现这一切我们可以去做的基础,主要应用的两个命名空间是:System.CodeDom.Compiler和Microsoft.CSharp或Microsoft.VisualBasic。另外还需要用到反射来动态执行你的代码。动态编译并执行代码的原理其实在于将提供的源代码交予CSharpCodeProvider来执行编译(其实和CSC没什么两样),如果没有任何编译错误,生成的IL代码会被编译成DLL存放于于内存并加载在某个应用程序域(默认为当前)内并通过反射的方式来调用其某个方法或者触发某个事件等。之所以说它是插件编写的一种方式也正是因为与此,我们可以通过预先定义好的借口来组织和扩展我们的程序并将其交还给主程序去触发。一个基本的动态编译并执行代码的步骤包括:

将要被编译和执行的代码读入并以字符串方式保存

声明CSharpCodeProvider对象实例

调用CSharpCodeProvider实例的CompileAssemblyFromSource方法编译

用反射生成被生成对象的实例(Assembly.CreateInstance)

调用其方法

以下代码片段包含了完整的编译和执行过程:

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 Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace DynamicCompileBase
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //get the code to compile
            string strSourceCode = this.txtSource.Text;

            // 1.Create a new CSharpCodePrivoder instance
            CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();

            // 2.Sets the runtime compiling parameters by crating a new CompilerParameters instance
            CompilerParameters objCompilerParameters = new CompilerParameters();
            objCompilerParameters.ReferencedAssemblies.Add("System.dll");
            objCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
            objCompilerParameters.GenerateInMemory = true;

            // 3.CompilerResults: Complile the code snippet by calling a method from the provider
            CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(objCompilerParameters, strSourceCode);

            if (cr.Errors.HasErrors)
            {
                string strErrorMsg = cr.Errors.Count.ToString() + " Errors:";

                for (int x = 0; x < cr.Errors.Count; x++)
                {
                    strErrorMsg = strErrorMsg + "/r/nLine: " +
                                 cr.Errors[x].Line.ToString() + " - " +
                                 cr.Errors[x].ErrorText;
                }

                this.txtResult.Text = strErrorMsg;
                MessageBox.Show("There were build erros, please modify your code.", "Compiling Error");
                return;
            }

            // 4. Invoke the method by using Reflection
            Assembly objAssembly = cr.CompiledAssembly;
            object objClass = objAssembly.CreateInstance("Dynamicly.HelloWorld");

            if (objClass == null)
            {
                this.txtResult.Text = "Error: " + "Couldn‘t load class.";
                return;
            }

            object[] objCodeParms = new object[1];
            objCodeParms[0] = "Allan.";

            string strResult = (string)objClass.GetType().InvokeMember(
                       "GetTime", BindingFlags.InvokeMethod, null, objClass, objCodeParms);
            this.txtResult.Text = strResult;
        }
    }
}

需要解释的是,这里我们在传递编译参数时设置了GenerateInMemory为true,这表明生成的DLL会被加载在内存中(随后被默认引用入当前应用程序域)。在调用GetTime方法时我们需要加入参数,传递object类型的数组并通过Reflection的InvokeMember来调用。在创建生成的Assembly中的对象实例时,需要注意用到的命名空间是你输入代码的真实命名空间。以下是我们输入的测试代码(为了方便,所有的代码都在外部输入,动态执行时不做调整):

using System;

namespace Dynamicly
{
    public class HelloWorld
    {
        public string GetTime(string strName)
        {
            return  "Welcome " + strName + ", Check in at " + System.DateTime.Now.ToString();
        }
    }
}

运行附件中提供的程序,可以很容易得到以下结果:

改进的执行过程

现在一切看起来很好,我们可以编译代码并把代码加载到当前应用程序域中来参与我们的活动,但你是否想过去卸载掉这段程序呢?更好的去控制程序呢?另外,当你运行这个程序很多遍的时候,你会发现占用内存很大,而且每次执行都会增大内存使用。是否需要来解决这个问题呢?当然需要,否则你会发现这个东西根本没用,我需要执行的一些大的应用会让我的服务器crzay,不堪重负而疯掉的。

要解决这个问题我们需要来了解一下应用程序域。.NET Application Domain是.NET提供的运行和承载一个活动的进程(Process)的容器,它将这个进程运行所需的代码和数据,隔离到一个小的范围内,称为Application Domain。当一个应用程序运行时,Application Domains将所有的程序集/组件集加载到当前的应用程序域中,并根据需要来调用。而对于动态生成的代码/程序集,我们看起来好像并没有办法去管理它。其实不然,我们可以用Application Domain提供的管理程序集的办法来动态加载和移除Assemblies来达到我们的提高性能的目的。具体怎么做呢,在前边的基础上增加以下步骤:

创建另外一个Application Domain

动态创建(编译)代码并保存到磁盘

创建一个公共的远程调用接口

创建远程调用接口的实例。并通过这个接口来访问其方法。

换句话来讲就是将对象加载到另外一个AppDomain中并通过远程调用的方法来调用。所谓远程调用其实也就是跨应用程序域调用,所以这个对象(动态代码)必须继承于MarshalByRefObject类。为了复用,这个接口被单独提到一个工程中,并提供一个工厂来简化每次的调用操作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace RemoteAccess
{
    /// <summary>
    /// Interface that can be run over the remote AppDomain boundary.
    /// </summary>
    public interface IRemoteInterface
    {
        object Invoke(string lcMethod, object[] Parameters);
    }

    /// <summary>
    /// Factory class to create objects exposing IRemoteInterface
    /// </summary>
    public class RemoteLoaderFactory : MarshalByRefObject
    {
        private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance;
        public RemoteLoaderFactory() { }
        public IRemoteInterface Create(string assemblyFile, string typeName, object[] constructArgs)
        {
            return (IRemoteInterface)Activator.CreateInstanceFrom(
                     assemblyFile, typeName, false, bfi, null, constructArgs,
                     null, null, null).Unwrap();
        }
    }
}

接下来在原来基础上需要修改的是:

将编译成的DLL保存到磁盘中。

创建另外的AppDomain。

获得IRemoteInterface接口的引用。(将生成的DLL加载到额外的AppDomain)

调用InvokeMethod方法来远程调用。

可以通过AppDomain.Unload()方法卸载程序集。

以下是完整的代码,演示了如何应用这一方案。

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 Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using RemoteAccess;

namespace DynamicCompileAppDomain
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // get the code to compile
            string strSourceCode = this.txtSource.Text;

            // 0. Create an addtional AppDomain
            AppDomainSetup objSetup = new AppDomainSetup();
            objSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            AppDomain objAppDomain = AppDomain.CreateDomain("MyAppDomain", null, objSetup);

            // 1.Create a new CSharpCodePrivoder instance
            CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();

            // 2.Sets the runtime compiling parameters by crating a new CompilerParameters instance
            CompilerParameters objCompilerParameters = new CompilerParameters();
            objCompilerParameters.ReferencedAssemblies.Add("System.dll");
            objCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");

            // Load the remote loader interface
            objCompilerParameters.ReferencedAssemblies.Add("RemoteAccess.dll");

            // Load the resulting assembly into memory
            objCompilerParameters.GenerateInMemory = false;
            objCompilerParameters.OutputAssembly = "DynamicalCode.dll";

            // 3.CompilerResults: Complile the code snippet by calling a method from the provider
            CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(objCompilerParameters, strSourceCode);

            if (cr.Errors.HasErrors)
            {
                string strErrorMsg = cr.Errors.Count.ToString() + " Errors:";

                for (int x = 0; x < cr.Errors.Count; x++)
                {
                    strErrorMsg = strErrorMsg + "/r/nLine: " +
                                 cr.Errors[x].Line.ToString() + " - " +
                                 cr.Errors[x].ErrorText;
                }

                this.txtResult.Text = strErrorMsg;
                MessageBox.Show("There were build erros, please modify your code.", "Compiling Error");
                return;
            }

            // 4. Invoke the method by using Reflection
            RemoteLoaderFactory factory = (RemoteLoaderFactory)objAppDomain.CreateInstance("RemoteAccess", "RemoteAccess.RemoteLoaderFactory").Unwrap();

            // with help of factory, create a real ‘LiveClass‘ instance
            object objObject = factory.Create("DynamicalCode.dll", "Dynamicly.HelloWorld", null);

            if (objObject == null)
            {
                this.txtResult.Text = "Error: " + "Couldn‘t load class.";
                return;
            }

            // *** Cast object to remote interface, avoid loading type info
            IRemoteInterface objRemote = (IRemoteInterface)objObject;

            object[] objCodeParms = new object[1];
            objCodeParms[0] = "Allan.";
            string strResult = (string)objRemote.Invoke("GetTime", objCodeParms);
            this.txtResult.Text = strResult;

            //Dispose the objects and unload the generated DLLs.
            objRemote = null;
            AppDomain.Unload(objAppDomain);
            System.IO.File.Delete("DynamicalCode.dll");
        }
    }
}

对于客户端的输入程序,我们需要继承于MarshalByRefObject类和IRemoteInterface接口,并添加对RemoteAccess程序集的引用。以下为输入:

using System;
using System.Reflection;
using RemoteAccess;

namespace Dynamicly
{
    public class HelloWorld : MarshalByRefObject,IRemoteInterface
    {
        public object Invoke(string strMethod,object[] Parameters)
        {
            return this.GetType().InvokeMember(strMethod, BindingFlags.InvokeMethod,null,this,Parameters);
        }

        public string GetTime(string strName)
        {
            return  "Welcome " + strName + ", Check in at " + System.DateTime.Now.ToString();
        }
    }
}

这样,你可以通过适时的编译,加载和卸载程序集来保证你的程序始终处于一个可控消耗的过程,并且达到了动态编译的目的,而且因为在不同的应用程序域中,让你的本身的程序更加安全和健壮。

最后附上示例程序源代码:

http://pan.baidu.com/s/1skSPQ3b

示例程序共有4个项目:

DynamicCompile是http://blog.csdn.net/clb929/article/details/51371363这篇文章的练习程序

DynamicCompileBase是本文无应用程序域动态编译的例子(无法释放内存)

RemoteAccess是远程调用应用程序域的库

DynamicCompileAppDomain是远程调用应用程序域动态编译的示例程序(能够将C#代码编译为临时DLL,动态加载并执行,然后释放,最后删除临时DLL)

原文地址:https://www.cnblogs.com/lonelyxmas/p/10226905.html

时间: 2024-10-10 11:30:22

让C#语言充当自身脚本!——.NET中的动态编译的相关文章

C语言引用连接脚本lds中的符号——清除bss段,c实现方式

之前我们的启动文件清除bss和拷贝都是通过汇编的方式的实现,但是,我们能够使用C语言,就不使用汇编: 先看连接脚本: SECTIONS { . = 0x30000000; __code_start = .; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss :

.NET中的动态编译

http://www.cnblogs.com/zlgcool/archive/2008/10/12/1309616.html 在开始之前,先熟悉几个类及部分属性.方法:CSharpCodeProvider.ICodeCompiler.CompilerParameters.CompilerResults.Assembly. 一.CSharpCodeProvider 提供对C#代码生成器和代码编译器的实例的访问.如果要动态生成VB代码,可以使用VBCodeProvider. CreateCompil

【转】Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)

http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题. 事件函数的执行顺序 先说一下执行顺序吧. 官方给出的脚本中事件函数的执行顺序如下图:  我们可以做一个小实验来测试一下: 在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示

(转)Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)

自:http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题. 事件函数的执行顺序 先说一下执行顺序吧. 官方给出的脚本中事件函数的执行顺序如下图:  我们可以做一个小实验来测试一下: 在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图

【转】Unity3D中脚本的执行顺序和编译顺序

支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题. 事件函数的执行顺序 先说一下执行顺序吧. 官方给出的脚本中事件函数的执行顺序如下图:  我们可以做一个小实验来测试一下: 在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示,然后按照顺序将脚本绑定到对

Linux Bash脚本编程语言中的美学与哲学

我承认,我再一次地当了标题党.但是不可否认,这一定是一篇精华随笔.在这一篇中,我将探讨Bash脚本语言中的美学与哲学. 这不是一篇Bash脚本编程的教程,但是却能让人更加深入地了解Bash脚本编程,更加快速地学习Bash脚本编程. 阅读这篇随笔,不需要你有Bash编程的经验,但一定要和我一样热衷于探索各种编程语言的本质,感悟它们的魅力. 其实早就想写关于Bash的东西了. 我们平时喜欢对编程语言进行分类,比如面向过程的编程语言.面向对象的编程语言.函数式编程语言等等.在我心中,我认为Bash就是

Java语言中反射动态代理接口的解释与演示

Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

C#语言基础知识(2):C#中多态

我的理解是:通过继承实现的不同对象调用相同的方法,表现出不同的行为,称之为多态. 1: OverRide 实现多态 1 public class Animal 2 { 3 public virtual void Eat() 4 { 5 Console.WriteLine("Animal eat"); 6 } 7 } 8 public class Dog : Animal 9 { 10 public override void Eat() 11 { 12 Console.WriteLin

C#语言基础知识(1):C#中关于重载和重写

Overload:重载就是在同一个类中,方法名相同,参数列表不同.参数列表不同包括:参数的个数不同,参数类型不同. 1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace OverLoading 6 { 7 class Program 8 { 9 public static int max(int i, int j) //静态方法 10 { 11 if (i > j) 12 re