㈠ 詳解.NET中的動態編譯技術
代碼的動態編譯並執行是一個 NET平台提供給我們的很強大的工具用以靈活擴展(當然是面對內部開發人員)復雜而無法估算的邏輯 並通過一些額外的代碼來擴展我們已有 的應用程序 這在很大程度上給我們提供了另外一種擴展的方式(當然這並不能算是嚴格意義上的擴展 但至少為我們提供了一種思路) 動態代碼執行可以應用在諸如模板生成 外加邏輯擴展等一些場合 一個簡單的例子 為了網站那的響應速度 HTML靜態頁面往往是我們最好的選擇 但基於數據驅動的網站往往又很難用靜態頁面實現 那麼將動態頁面生成的工作或許就是一個很好的應用場合 另外 對於一些模板的套用 我們同樣可以用它來做 另外這本身也是插件編寫的方式
最基本的動態編譯
Net為我們提供了很強大的支持來實現這一切我們可以去做的基礎 主要應用的兩個命名空間是 System CodeDom Compiler和Microsoft CSharp或Microsoft VisualBasic 另外還需要用到反射來動態執行你的代碼 動態編譯並執行代碼的原理其實在於將提供的源代碼交予CSharpCodeProvider來執行編譯(其實和CSC沒什麼兩樣) 如果沒有任何編譯錯誤 生成的IL代碼會被編譯成DLL存放於於內存並載入在某個應用程序域(默認為當前)內並通過反射的方式來調用其某個方法或者觸發某個事件等 之所以說它是插件編寫的一種方式也正是因為與此 我們可以通過預先定義好的借口來組織和擴展我們的程序並將其交還給主程序去觸發 一個基本的動態編譯並執行代碼的步驟包括
· 將要被編譯和執行的代碼讀入並以字元串方式保存
· 聲明CSharpCodeProvider對象實例
· 調用CSharpCodeProvider實例的CompileAssemblyFromSource方法編譯
· 用反射生成被生成對象的實例(Assembly CreateInstance)
· 調用其方法
以下代碼片段包含了完整的編譯和執行過程
//get the code to pile string strSourceCode = this txtSource Text; // Create a new CSharpCodePrivoder instance CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); // Sets the runtime piling 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; // 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 = ; x < cr Errors Count; x++) { strErrorMsg = strErrorMsg + Line: + 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; } // 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[ ]; objCodeParms[ ] = 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 Wele + strName +
Check in at + System DateTime Now ToString(); } } }
運行附件中提供的程序 可以很容易得到一下結果
㈡ 如何擴展Visual Studio 的編譯時功能
很多時候會想在vs編譯的時候自定義一些事情或者動作,
例如:
拷貝生成的文件到特定的目錄。
部署程序到測試目錄或者環境,例如注冊到windows服務,更新GAC等。
根據編譯環境生成特定的配置文件(例如web.config) PS: 身在一個復雜環境, 這是我最想要的功能。
自動執行外部exe。
同步DLL和其他資源文件。
1.最簡單的自然是用Visual Studio自帶的編譯事件,這東西使用方便,又是Visual Studio自帶的功能,就是功能弱了一點(好吧 其實是很弱)
將項目生成的DLL文件拷貝到特定目錄,(如果你想拷貝一整個文件夾 用x; 當然,熟悉命令行的人可以弄出更多的玩法)
如下圖所示
2.另外一種比較推薦的方式是自定義編譯擴展(可以執行C#代碼...功能強大多了), 看下面這個項目文件的最後幾句 (項目文件就是 項目名.csproj)
<Import Project="....BuildTasksBuild.tasks" />
<Target Name="BeforeBuild">
<Message Text="Start Automatic Generate Configuration File, Source File: $(ProjectDir)web.config" Importance="high">
</Message>
<ConfigurationEnvironmentTask TemplateFile="$(ProjectDir)web.template.config" TargetFile="$(ProjectDir)web.config" DataSource="$(EnvironmentName).config" />
</Target>
</Project>
這幾句話的意思是
1. 包含一個task文件 (該文件包含了ConfigurationEnvironmentTask 的定義,這是一個自定開發的類,其主要作用是根據環境生成web.config文件)
2.輸出一行提示信息 Start Automatic..... (該信息將顯示在Output Window)
3. 調用ConfigurationEnvironmentTask 並傳入一些參數(TemplateFile 等都是自己定義的參數)
Build.tasks的文件內容其實很簡單,主要就是說明了這個task定義在哪裡 (Build.dll)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="Build.dll" TaskName="Build.ConfigurationEnvironmentTask"/>
</Project>