首页 > 代码库 > C# 动态编译

C# 动态编译

支持.NET 2.0

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

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

·         声明CSharpCodeProvider对象实例

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

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

·         调用其方法

//Imports the namespace for compilingusing System.CodeDom.Compiler;using Microsoft.CSharp;using System.Reflection;//get the code to compilestring strSourceCode = this.txtSource.Text;// 1.Create a new CSharpCodePrivoder instanceCSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); // 2.Sets the runtime compiling parameters by crating a new CompilerParameters instanceCompilerParameters 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 providerCompilerResults 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 ReflectionAssembly 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;

 

改进的执行过程

 

现在一切看起来很好,我们可以编译代码并把代码加载到当前应用程序域中来参与我们的活动,但你是否想过去卸载掉这段程序呢?更好的去控制程序呢?另外,当你运行这个程序很多遍的时候,你会发现占用内存很大,而且每次执行都会增大内存使用。是否需要来解决这个问题呢?当然需要,否则你会发现这个东西根本没用,我需要执行的一些大的应用会让我的服务器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 compilestring strSourceCode = this.txtSource.Text; //1. Create an addtional AppDomainAppDomainSetup objSetup = new AppDomainSetup();objSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;AppDomain objAppDomain = AppDomain.CreateDomain("MyAppDomain", null, objSetup); // 1.Create a new CSharpCodePrivoder instanceCSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); // 2.Sets the runtime compiling parameters by crating a new CompilerParameters instanceCompilerParameters objCompilerParameters = new CompilerParameters();objCompilerParameters.ReferencedAssemblies.Add("System.dll");objCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); // Load the remote loader interfaceobjCompilerParameters.ReferencedAssemblies.Add("RemoteAccess.dll"); // Load the resulting assembly into memoryobjCompilerParameters.GenerateInMemory = false;objCompilerParameters.OutputAssembly = "DynamicalCode.dll"; // 3.CompilerResults: Complile the code snippet by calling a method from the providerCompilerResults 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 ReflectionRemoteLoaderFactory factory = (RemoteLoaderFactory)objAppDomain.CreateInstance("RemoteAccess","RemoteAccess.RemoteLoaderFactory").Unwrap(); // with help of factory, create a real ‘LiveClass‘ instanceobject 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 infoIRemoteInterface 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

 

有几点使用体会(采用方法2,就是优化后的方法):

1、若在源程序中若显式的引用了objRemote,比如强制类型转换为某个对象,则可能在卸载的时候发现卸载不掉,文件也删除不掉,所以如果非要操作对象,比如用Invoke不能传递ref参数,则一定要使用接口编程(即将函数写到接口,然后用户类来实现,调用的时候还是接口调用)。

2、动态生成的类可以直接用继承,而避免写很多代码在string里。

 

 

当然还有其他方法,请参考原帖

本帖转自:

http://www.cnblogs.com/westernsky/archive/2011/12/27/2303064.html

C# 动态编译