本文版权归博客园Allan.所有!此处纯粹收藏,如有再转,请按如下方式显式标明文章作者及出处,以示尊重!!!

作者:Allan.

原文题目:.NET中的动态编译

原文出处:http://www.cnblogs.com/zlgcool/archive/2008/10/12/1309616.html

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

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

最基本的动态编译

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

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

·         声明CSharpCodeProvider对象实例

·         调用CSharpCodeProvider实例的CompileAssemblyFromSource方法编译

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

·         调用其方法

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

//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()方法卸载程序集。

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

//get the code to compile

string strSourceCode = this.txtSource.Text;

//1. 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://files.cnblogs.com/zlgcool/DynamicCompiler.rar

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

  1. 游戏c#脚本语言,C#作为脚本语言执行解密

    背景 我们知道Unity3d是通过C#脚本语言的形式来实现游戏的逻辑代码编写,同样SCOTT服务器也设置了通过C#脚本来实现游戏逻辑,但是本文并不是想真正分析解密他们的运行机制,只是想通过自己的一个需 ...

  2. C#发现之旅第十四讲 基于动态编译的VB.NET脚本引擎

    本章说明 在前面章节中,笔者使用了反射和动态编译技术实现了快速ORM框架,在本章中笔者将继续使用这些技术来实现一个VB.NET的脚本引擎,使得人们在开发中能实现类似MS Office那样实现VBA宏的 ...

  3. 在Linux中如何运行C语言写的脚本

    目录 1:Linux下如何运行C语言脚本 2:实例展示 1:Linux下如何运行C语言脚本 Linux别的系统我不知道是不是这个方法,我是用的ubuntu的,其他的我也没测试过,但是ubuntu应该是 ...

  4. JAVA的嵌入式脚本开发(中)

    本文将分以下几个小节来学习: Java的JavaScript脚本引擎 脚本开发中涉及到的类和接口简介 开发步骤 脚本与java数据传递 脚本执行上下文 作用域 方法的调用 编译脚本 脚本与java数据 ...

  5. c 语言中 %是什么运算符,C 语言基础----详解C中的运算符

    C语言中又有哪些运算符呢? 如下所示: ※ 算术运算符 ※ 赋值运算符 ※ 关系运算符 ※ 逻辑运算符 ※ 三目运算符 C语言基本算术运算符如下表: 除法运算中注意: 如果相除的两个数都是整数的话,则 ...

  6. Windows PowerShell 2.0语言开发之脚本块

    脚本块是重要的编程结构,是PowerShell重要的摘要和重用代码的机制,学习脚本块的最终目标是掌握各种重用代码的方法,如别名程序提供和脚本文件.这些技术都很重要,因为它们是逐步创建复杂脚本的基础. ...

  7. 何时在脚本标记中需要CDATA节?

    脚本标记中是否曾经需要CDATA标记?如果需要,何时? 换句话说,何时何地: <script type="text/javascript"> //<![CDATA ...

  8. 揭秘!用标准Go语言能写脚本吗?

    导语 | Go作为一种编译型语言,经常用于实现后台服务的开发.由于Go初始的开发大佬都是C的老牌使用者,因此Go中保留了不少C的编程习惯和思想,这对C/C++ 和PHP开发者来说非常有吸引力.作为编译 ...

  9. 汇编语言、高级语言(系统语言)、脚本语言的异同

    汇编语言.高级语言(系统语言).脚本语言的异同 高级语言主要指需要编译的语言.如C/C++,Java,C#.脚本语言主要指解释型语言,编写的代码无需编译器编译,直接使用解释器放入虚拟机执行. 高级语言 ...

最新文章

  1. springbatch的reader,如何使用mybatis?使用MyBatisCursorItemReader可以完美解决
  2. kafka消息存储原理及查询机制
  3. 南大cssci期刊目录_最新版CSSCI管理学和经济学来源期刊目录(20192020)及增减变化!【南大核心】...
  4. 第1次在Flash Builder中写程序
  5. 【目标检测】Anchor-Free算法--CenterNet详解
  6. 四款机型全面开售 海蓝色iPhone 12 Pro最受欢迎
  7. VS code中使用code runner遇到两例问题之解决:1.输出乱码,2运行无响应(输出)
  8. Python基础练习三超市存包柜模拟(优化)
  9. 算法学习--Day9
  10. Atitit 项目源码的架构,框架,配置与环境说明模板 规范 标准化
  11. 无盘网吧服务器比单机快吗,锐起无盘网吧系统打造比有盘更快的速度
  12. 计算机分盘介质受写入保护,硬盘介质受写入保护怎么办
  13. C - Write the program expr which evaluates a reverse Polish expression from the command line
  14. 什么是模型?什么是建模?
  15. 最佳阵容怎么找不到服务器,最佳阵容新服最佳782服开服时间表_最佳阵容新区开服预告_第一手游网手游开服表...
  16. R语言:第一部分 入门
  17. PostgreSQL构建通用标签系统
  18. cov函数 corrcoef函数【Matlab】
  19. 20200812 java 笔记
  20. 【PM】从产品经理的角度探索游戏产品

热门文章

  1. python 微服务 etcd_python etcd api
  2. 微信小程序自定义状态栏navigationBar样式组件,适配所有机型
  3. PCL——向PCD文件写入点云数据
  4. 【面经】记一次字节跳动后端面试经历
  5. 10分钟搞定kettle源码部署
  6. log4cpp 用法
  7. CSS 定位 (Positioning) 实例
  8. Linux Platform驱动模型(三) _platform+cdev
  9. [转] Hibernate一级缓存、二级缓存
  10. 数字后端基本概念介绍<Tap Cell>