让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)
让C#语言充当自身脚本!——.NET中的动态编译的更多相关文章
- C语言引用连接脚本lds中的符号——清除bss段,c实现方式
之前我们的启动文件清除bss和拷贝都是通过汇编的方式的实现,但是,我们能够使用C语言,就不使用汇编: 先看连接脚本: SECTIONS { . = 0x30000000; __code_start = ...
- .NET中的动态编译
代码的动态编译并执行是一个.NET平台提供给我们的很强大的工具用以灵活扩展(当然是面对内部开发人员)复杂而无法估算的逻辑,并通过一些额外的代码来扩展我们已有 的应用程序.这在很大程度上给我们提供了另外 ...
- Unity3D中脚本的执行顺序和编译顺序
http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与 ...
- 【转】Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)
http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与 ...
- Linux 桌面玩家指南:06. 优雅地使用命令行及 Bash 脚本编程语言中的美学与哲学
特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...
- (转)Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)
自:http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行 ...
- 【转】Unity3D中脚本的执行顺序和编译顺序
支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦 ...
- JDK1.7新特性(3):java语言动态性之脚本语言API
简要描述:其实在jdk1.6中就引入了支持脚本语言的API.这使得java能够很轻松的调用其他脚本语言.具体API的使用参考下面的代码: package com.rampage.jdk7.chapte ...
- Linux Bash脚本编程语言中的美学与哲学
我承认,我再一次地当了标题党.但是不可否认,这一定是一篇精华随笔.在这一篇中,我将探讨Bash脚本语言中的美学与哲学. 这不是一篇Bash脚本编程的教程,但是却能让人更加深入地了解Bash脚本编程,更 ...
随机推荐
- SSH进阶(2)——用Struts拦截器实现登陆限制
拦截器从字面意思来看就是限制.限制用户訪问某些网页.在Action提出请求之前用拦截器来做权限设置,让符合的用户跳入对应的界面中.近期做的一个商城项目中就用到了自己定义的拦截器,实现了一个简单的ses ...
- Python 实用第三方库
1. youtube 视频下载 使用:you-get pip install you-get you-get 'https://www.youtube.com/watch?v=jNQXAC9IVRw'
- Java设计模式——代理模式实现及原理
简介 Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术.生活中的方方面面都可以虚拟到代码中.代理模式所讲的就是现实生活中的这么一个概念:中介. 代理模式的定义:给某一个对象提 ...
- 软件——python,主函数
1;; 如何在spyder中运行python程序 如下图, 写入一个输出 ' hellow word '的程序 然后点击运行按钮就可以运行了.
- 算法 Tricks(三)—— 数组(序列)任意区间最小(大)值
序列(数组)的区间通过左右端点确定,这样首先设置一个最值变量用来记录最值,从左端点一步步移动到右端点,自然移动的过程中也可以计算整个区间的和,也即一次线性遍历下来,可同时获得多个有用信息. // 区间 ...
- 【Codeforces Round #435 (Div. 2) C】Mahmoud and Ehab and the xor
[链接]h在这里写链接 [题意] 让你组成一个n个数的集合,使得这n个数的异或和为x; x<=1e5 每个数最大1e6; [题解] 1e5<=2^17<=2^18<=1e6的 ...
- Springboot+shiro配置笔记+错误小结(转)
软件152 尹以操 springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对 ...
- mysql数据库各存储引擎比較
mysql数据库差别于其它数据库的最重要的一个特点是其插件式的表存储引擎,存储引擎是基于表的.而不是数据库 InnoDB存储引擎: 支持事务,其设计目标主要面向在线事务处理(OLTP)的 ...
- [React] Create a Virtualized List with Auto Sizing Cells using react-virtualized and CellMeasurer
In this lesson we'll use CellMeasurer and CellMeasurerCache to automatically calculate and cache the ...
- swift项目第九天:正则表达式的学习
import UIKit /* 练习1:匹配abc 练习2:包含一个a~z,后面必须是0~9 -->[a-z][0-9]或者[a-z]\d * [a-z] : a~z * [0-9]/\d : ...