科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件.NET下的动态代码编译探索

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

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

不能确定动态代码编译在什么地方是有意义的?一个普通情况就应该可以帮助阐明这个问题

作者:小刀人编译 来源:VCKBASE 2007年11月3日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
在内存中编译

  最后一步是获得生成的源代码并将它编译到一个当前的程序集中去。对于这个例子,你是将这个例子装入内存而不是一个物理文件。通过特定的编程语言提供者执行当前编译动作,在这个例程中就是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”方法实体成为任何你喜欢的代码并试用之。你可以改变它,以使得所有关于方法实体的代码被存储在一个数据库中并在运行时重新获得。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章