.NET 動態編譯_實用技巧

來源:互聯網
上載者:User
這在很大程度上給我們提供了另外一種擴充的方式(當然這並不能算是嚴格意義上的擴充,但至少為我們提供了一種思路)。

動態代碼執行可以應用在諸如模板產生,外加邏輯擴充等一些場合。一個簡單的例子,為了網站那的響應速度,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://xiazai.jb51.net/200905/yuanma/DynamicCompiler.rar

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.