`
leonardleonard
  • 浏览: 778010 次
社区版块
存档分类
最新评论

.NET下的动态代码编译探索

阅读更多
不能确定动态代码编译在什么地方是有意义的?一个普通情况就应该可以帮助阐明这个问题。假如你不得不从一个数据库中取出数据并将它放入另一个数据库。你应该只需使用一个SQL语句从源数据库中选取数据并插入目标数据库中,这只是小菜一碟,对不对?如果你正在拷贝生产数据以生成测试数据并需要改变数据以确保目标数据在以后开发中使用是安全的又将如何?你可能会构建一个数据传输系统(DTS)或某个其它传输机制,但是如果你这样做超过足够多的数据,这就会变成你每次为拷贝数据建立数据-擦除(data-scrubbing)机制而消耗时间。你可以写一个应用程序来加工并生成测试数据,但是每次你在一个不同的应用程序上用它时你都将不得不改动(应用程序)并创建新的算法。

  走进动态代码编译。胜于不停地写一些一次性的代码,你可以创建一个有特定内部运作机制的应用程序来传送数据并在传送时运用代码段来改变数据。该代码段将代理每个你需要在数据上要做的动作。它们将被作为原始文本被储存在一个数据库中或某个它们可以很容易被修改的其它位置。代码段将被编译并在执行时同时应用到数据。这将允许你获得一个完全是不同的代码段的数据库,使得你可以很容易地恢复、修改并应用它而不用每次都要改变你的应用程序的根本。

  这是个相当复杂的情况,但是它应该帮助你理解一些可能性。现在,让我们看看如何实现它。

  CodeCompileUnit(代码编译单元)

  为了动态编译一个类,从System.CodeDom命名空间的一个CodeCompileUnit开始。CodeCompileUnit包含一个程序图形。为了构建代码,你要创建一些支撑对象并将它们添加到CodeCompileUnit实例中去。这些对象:代表应该已经在你的代码中的,就像你准备在设计时要创建它的普通对象。

  . CodeNamespace―代表指定的命名空间

  . CodeTypeDeclaration―代表类型声明

  . CodeMemberMethod―代表一个方法

  一个HelloWorld例子

  你可以使用下面的示例代码来生成包含一个接收单个参数并返回一个值的SayHello方法的代码。scriptBody方法的参数值成为SayHello方法的实体(body)。你将你的代码包含在接收影响结果的参数的一个static(静态)类中以创建CodeCompileUnit。

public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
string typeName,
string scriptBody)
{
 // 创建CodeCompileUnit以包含代码
 CodeCompileUnit ccu = new CodeCompileUnit(); // 分配需要的命名空间
 CodeNamespace cns = new CodeNamespace(typeNamespace);
 cns.Imports.Add(new CodeNamespaceImport("System"));
 ccu.Namespaces.Add(cns); // 创建新的类声明
 CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
 cns.Types.Add(parentClass); // 创建获得一个参数并返回一个字符串的SayHello方法
 CodeMemberMethod method = new CodeMemberMethod();
 method.Name = "SayHello";
 method.Attributes = MemberAttributes.Public;
 CodeParameterDeclarationExpression arg = new CodeParameterDeclarationExpression(typeof(string), "inputMessage");
 method.Parameters.Add(arg);
 method.ReturnType = new CodeTypeReference(typeof(string)); // 添加方法实体需要的代码
 CodeSnippetStatement methodBody =new CodeSnippetStatement(scriptBody);
 method.Statements.Add(methodBody);
 parentClass.Members.Add(method); return ccu;
}
CodeProvider(代码提供者)

  现在你已经创建了一个CodeCompileUnit包含你的代码段,使用它来生成被编译到你的动态程序集中去的全部源代码。下面的静态方法首先从前面的例子中调用方法并且同时使用CSharpCodeProvider生成全部代码:

public static string GenerateCode(string typeNamespace,
string typeName,
string scriptBody)
{
 // 调用我们前面的方法创建CodeCompileUnit
 CodeCompileUnit ccu = CreateExecutionClass(typeNamespace,
 typeName, scriptBody); CSharpCodeProvider provider = new CSharpCodeProvider();
 CodeGeneratorOptions options = new CodeGeneratorOptions();
 options.BlankLinesBetweenMembers = false;
 options.IndentString = "\t"; StringWriter sw = new StringWriter();
 try
 {
  provider.GenerateCodeFromCompileUnit(ccu, sw, options);
  sw.Flush();
 }
 finally
 {
  sw.Close();
 } return sw.GetStringBuilder().ToString();
}

  作为一个例子,用输入值:"CodeGuru.DynamicCode","ScriptType",和"return inputMessage;"调用GenerateCode方法得出以下输出:

//---------------------------------------------------------------
// <auto-generated>
// 该代码是由工具生成的。
// 运行时版本:2.0.50630.0
// 更改这个文件可能导致不正确的(程序)动作并且如果代码被再次生成时将会丢掉这些更改。
// </auto-generated>
//---------------------------------------------------------------
namespace CodeGuru.DynamicCode {
 using System;
 public class ScriptType {
  public virtual string SayHello(string inputMessage) {
   return inputMessage;
  }
 }
}
  在内存中编译

  最后一步是获得生成的源代码并将它编译到一个当前的程序集中去。对于这个例子,你是将这个例子装入内存而不是一个物理文件。通过特定的编程语言提供者执行当前编译动作,在这个例程中就是CSharpCodeProvider。你设定任何预定的编译选项并从源代码编译这个程序集。

  下面的示例代码从你已构建的代码中生成了一个程序集:

static Assembly CompileInMemory(string code)
{
 CSharpCodeProvider provider = new CSharpCodeProvider(); CompilerParameters options = new  CompilerParameters();
 options.IncludeDebugInformation = false;
 options.GenerateExecutable = false;
 options.GenerateInMemory = true;
 CompilerResults results =provider.CompileAssemblyFromSource(options, code);
 provider.Dispose();
 Assembly generatedAssembly = null;
 if (results.Errors.Count == 0)
 {
  generatedAssembly = results.CompiledAssembly;
 }
 return generatedAssembly;
}

  如Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName, "return inputMessage;"));的调用将会生成一个新的程序集。你可能会用任何你想要的方法实体代替"return inputMessage;"来创建预定的变量作些并发调用。

  创建一个实例

  你已经动态生成了一个程序集并将其编译到内存中。下一个任务就是从程序集中创建一个类的实例。这实际上比听起来更加复杂。你已经创建的程序集存在于内存中。对它的存在没有任何参考信息,因此你不能简单的创建一个新的实例,因为它们不会解决问题。创建一个类以拥有所有已编译程序集作为一个工作区。你将不顾类型决定事件,所以当一个类型需要时你可以使用你的类型中的一个。 ExecutionHost示例代码

  下面的代码定义了一个名为ExecutionHost的类,它追踪了你所有的动态编译程序集:

using System;
using System.Collections;
using System.Reflection;
namespace CodeGuru.CodeDomSample
{
 class ExecutionHost
 {
  private Hashtable assemblies = null;
  public ExecutionHost()
  {
   assemblies = new Hashtable();
   // 响应类型解析事件(the type resolution event)要求以截取它并找到我们类型
   AppDomain.CurrentDomain.TypeResolve += new ResolveEventHandler(CurrentDomain_TypeResolve);
  }
  private Assembly CurrentDomain_TypeResolve(object sender,ResolveEventArgs args)
  {
   // 为预定的类型找出我们程序集
   Assembly a = null;
   if (assemblies.ContainsKey(args.Name))
   {
    a = (Assembly)assemblies[args.Name];
   }
   return a;
  } public void AddAssembly(string fullTypeName, Assembly a)
  {
   assemblies.Add(fullTypeName, a);
  } public string Execute(string typeFullName, string msg)
  {
   // 尝试创建触发事件所需要的类型
   Type targetType = Type.GetType(typeFullName, true, true);
   object target =targetType.Assembly.CreateInstance(typeFullName);
   IExecutableModule m = (IExecutableModule)target; return m.SayHello(msg);
  }
 }
}
namespace CodeGuru.CodeDomSample
{
 public interface IExecutableModule
 {
  string SayHello(string inputMessage);
 }
}
public static CodeCompileUnit CreateExecutionClass(string typeNamespace,string typeName,string scriptBody)
{
 // 创建CodeCompileUnit以存放代码
 CodeCompileUnit ccu = new CodeCompileUnit(); // 分配给预期的命名空间
 CodeNamespace cns = new CodeNamespace(typeNamespace);
 cns.Imports.Add(new CodeNamespaceImport("System"));
 ccu.Namespaces.Add(cns);
 // 创建类
 CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
 cns.Types.Add(parentClass);
 // 新行-为IExecutableModule接口添加一个实现
 parentClass.BaseTypes.Add(typeof(CodeGuru.CodeDomSample.IExecutableModule));
 // 创建获得一个参数并返回一个字符串的SayHello方法
 CodeMemberMethod method = new CodeMemberMethod();
 method.Name = "SayHello";
 method.Attributes = MemberAttributes.Public;
 CodeParameterDeclarationExpression arg = new CodeParameterDeclarationExpression(typeof(string),
"inputMessage");
 method.Parameters.Add(arg);
 method.ReturnType = new CodeTypeReference(typeof(string));
 // 添加预期代码到方法实体
 CodeSnippetStatement methodBody = new CodeSnippetStatement(scriptBody);
 method.Statements.Add(methodBody);
 parentClass.Members.Add(method);
 return ccu;
}

  注意Execute方法。它用反射来创建预定类型的一个实例。这将触发_TypeResolve事件并允许你的程序集中的一个被返回,如果该被返回程序集通过AddAssembly方法已被添加到ExecutionHost中了。

  你也要注意在你的动态生成代码中添加的接口实现。没有它,你将不知道如何调用预期的方法。为了你的生成代码,IExecutableModule接口与作为一个基类添加的附加接口的CreateExecutionClass方法的一个最新副本一同被提供。

  另外,因为你增添了一个现在需要在CodeGuru.DynamicCode程序集内部使用的接口,你必须给含有IExecutableModule 声明的CodeGuru.CodeDomSample添加一个接口。请看下面最新的CompileInMemory副本:

static Assembly CompileInMemory(string code)
{
 CSharpCodeProvider provider = new CSharpCodeProvider();
 CompilerParameters options = new CompilerParameters();
 options.IncludeDebugInformation = false;
 options.GenerateExecutable = false;
 options.GenerateInMemory = true;
 // 新行-添加一个接口到需要的程序集
 options.ReferencedAssemblies.Add("CodeGuru.CodeDomSample.exe");
 CompilerResults results = provider.CompileAssemblyFromSource(options, code);
 provider.Dispose(); Assembly generatedAssembly = null;
 if (results.Errors.Count == 0)
 {
  generatedAssembly = results.CompiledAssembly;
 } return generatedAssembly;
}

  现在,你可以用下面的测试代码来测试动态生成一个程序集然后对方法做一个调用的端到端(end-to-end)过程:

string typeNamespace = "CodeGuru.DynamicCode";
string typeName = "ScriptType" + Guid.NewGuid().ToString("N");
Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName,"return inputMessage;"));
ExecutionHost host = new ExecutionHost();
string fullTypeName = typeNamespace + "." + typeName;
host.AddAssembly(fullTypeName, a);
string test = host.Execute(fullTypeName, "Hello World!");

  每次在你生成代码时使用Guid生成唯一对象名称。

  后记

  你已看完了一个非常基本的例子,它描述了一个复杂的主题及完成这个任务所需要的代码。在类型名称上添加Guid是为了确保其唯一性,因此你可以随心所欲地编译并使用各种不同的类型而不会在名称上发生冲突。你可以自由改变“return inputMessage”方法实体成为任何你喜欢的代码并试用之。你可以改变它,以使得所有关于方法实体的代码被存储在一个数据库中并在运行时重新获得。
分享到:
评论

相关推荐

    JustDecompile新的免费的.NET 浏览和反编译的开发者效率工具

    JustDecompile新的免费的.NET 浏览和反编译的开发者效率工具, JustDecompile基于Telerik的多年的代码分析和...JustDecompile让你轻松地探索和分析已经编译过的.NET程序集,你只需要简单地点击一个按钮就能反编译代码。

    ILSpy .Net框架反编译工具最新版本

    本资源提供ILSpy最新版本 5.0下载,是一个开源的.NET反编译工具,简洁强大易用是它的特征。在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。

    ILSpy .NET反编译工具

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 中文版与英文版相比,存在以下不同: 所有的对话框、菜单和提示均已中文化 预置了调试插件,支持在ILSpy中无源代码直接调试程序集 调整主程序...

    ILSpy 是一个开源的.NET反编译工具

    ILSpy 是一个开源的.NET反编译工具,简洁强大易用是它的特征。在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。

    .NET反编译工具ILSpy2.3.0 获得DLL EXE 项目的源代码 好用 免费 最新 最全

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 ILSpy .NET Decompiler Download Binaries Download Source View On GitHub ILSpy is the open-source .NET assembly browser and decompiler. ...

    asp.net知识库

    ASP.NET 2.0 中的代码隐藏和编译 ASP.NET 2.0 Language Swithcer and Theme Swicher 多语言转换和多样式主题转换 ASP.NET2.0 ObjectDataSource的使用详解(1) ASP.NET2.0 ObjectDataSource的使用详解(2) ...

    ILSpy_2.4 .NET反编译工具

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 如果你用Reflector感觉不爽,你可以有另一个选择,试试ILSpy,或许会带来一丝的惊喜。 就我个人的使用感觉,比Reflector更好用,小巧免费;而且...

    《Visual Studio .NET使用技巧手册》精选版

    全书内容分为精练而容易把握的几部分,收录的技巧从编辑代码,到编译和调试,以及VS.NET编译器深入了解等多个方面,对所有开发人员而言都是本必读的书籍,无论他们是否熟练,或者使用哪种开发语言。本书涵盖了Visual...

    C#语言教程:掌握.NET下的编程艺术

    类型安全:C#是一种强类型语言,这意味着在编译时就能检测出许多类型相关的错误,从而提高了代码的安全性和稳定性。 垃圾回收:C#内置了垃圾回收机制,可以自动管理内存,减轻了程序员的负担,并减少了内存泄漏和...

    ASP.NET源码包合集2.zip

    [CMS程序]NetFocus最新版源代码_netfocus.rar [CMS程序]posh v2.2.Beta_posh_2.2.beta.rar [CMS程序]Rainbow v1.4.0.1778e RC4汉化版_rainbow1401778erc4hh.rar [CMS程序]ROYcms !NT 1.0.0 源码版(重构)_roycms.rar ...

    ASP.NET源码包合集7.zip

    [博客空间]X3BLOG(ASP.NET开源多用户博客系统) 1.1.0 beta1 编译版_x3blog-bin.rar [博客空间]YetAnotherForum.Net + ScrewTurn Wiki 中文完美汉化增强版_5jbb.com_forum_wiki_v1.2.3.rar [博客空间]ZJ-BLOG v1.0 ...

    ILspy 反编译软件

    ILSpy 是一个开源的.NET反编译工具,简洁强大易用是它的特征。在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。

    ILSpy_2.4反编译工具

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 如果你用Reflector感觉不爽,你可以有另一个选择,试试ILSpy,或许会带来一丝的惊喜。 就我个人的使用感觉,比Reflector更好用,小巧免费;...

    SqlLinq:.NET IEnumerables 的动态 SQL 查询

    动态 .NET SQL 查询 这个项目最初是一个实验,它允许我编写类似 SQL 的动态内联查询,这样我就可以探索我的 MP3 文件集合的性质。 这个原始项目位于 CodeProject: : 。 原始代码被改进为 SQL 选择语句的基本实现...

    c# 反编译工具 ILSpy中文版

    ILSpy 是一个开源的.NET反编译工具,简洁强大易用是它的特征。在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索

    JustDecompile

    JustDecompile新的免费的.NET 浏览和反编译的开发者效率工具, JustDecompile基于Telerik的多年的代码分析和...JustDecompile让你轻松地探索和分析已经编译过的.NET程序集,你只需要简单地点击一个按钮就能反编译代码。

    ILSpy反编译工具

    ILSpy反编译工具,反编译查看源码,ILSpy 是一个开源的.Net程序集。简洁强大易用是它的特征。在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。通过此工具,可辅助分析Unity脚本中的内存分配

    IL Spy.zip

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 关于ILSpy为何物,这里不多做介绍。 中文版与英文版相比,存在以下不同: 所有的对话框、菜单和提示均已中文化 预置了调试插件,支持在ILSpy中...

    ILSpy_2.4 强大的反编译工具

    在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索。 如果你用Reflector感觉不爽,你可以有另一个选择,试试ILSpy,或许会带来一丝的惊喜。 就我个人的使用感觉,比Reflector更好用,小巧免费;...

Global site tag (gtag.js) - Google Analytics