當我們打算建立一個.net程式時(包括傳統型程式或者Web應用程式),如果能使用其他語言來擴充這個.net程式的功能的話那肯定會相當有實用價值。
比如某些使用者可以寫一個簡單指令碼來設定這個程式的一些設定,或者在程式中修改資料是如何持久化儲存的,或者為這個.net程式寫一個簡單的外掛程式。在這篇文章,我們來看看如何讓php作為.net程式的指令碼語言
顯然這樣做有很多的好處:
1,很多程式員都會寫一些基本的PHP代碼,甚至一個初級程式員都能為你的應用寫一個簡單的PHP指令碼代碼
2, PHP是非常容易使用的,網路上已經有了一大堆現成的php程式碼片段可以拿來複製後直接使用
3,歸功於Phalanger庫( http://phalanger.codeplex.com/), PHP代碼能夠很容易地擷取任何.net庫以及調用幾乎所有.net程式提供的服務
上面描述的情境僅僅只是使用Phalanger from C#(或者其他程式設計語言)在運行時產生PHP代碼的一小部分案例,打個比方,你能想象一下一個web網路架構使用C#來寫網域名稱模組然後使用PHP去搭建使用者介面會是什麼樣子. 所以本文將展示如何在C#的程式中運行PHP代碼,與怎麼使用全域變數作為參數傳遞到PHP代碼,以及如何讀取標準.net流。
Phalanger 是一個將PHP指令碼編譯成.net位元組碼的編譯器,它本身就被設計用來允許無縫地讓.net與其他語言進行雙向的互通性。
這就意味著你能在php代碼中調用.net方法以及使用.net的類(http://wiki.phpcompiler.net/.NET_interoperability),同時你也能在C#或者F#中調用php的方法以及使用php的類.( http://wiki.phpcompiler.net/Code_Samples/Standard_mode_interoperability)
同時本文展示了另外一種使用Phalanger的方式:通過.net程式來運行php代碼.尤其當被啟動並執行代碼是動態擷取的或者無法被先行編譯為程式集時(例如當代碼是後來被使用者所寫的這種情況).當啟動並執行的php代碼沒有任何改變時,一般你應該使用先行編譯的指令碼庫( http://wiki.phpcompiler.net/Code_Samples/Standard_mode_interoperability),這樣能夠得到更高的效率因為在運行時它們不會參與編譯。
配置
在ASP.NET 4.0 C#的網站程式中我已經測試過這個技術了,當然,在.net控制台程式或者winforms這樣的傳統型應用程式中也是可行的。但要記住你的.net程式必須是使用.net 4.0(full profile)作為目標.net架構,以及必須引用至少一個Phalanger的程式集:“PhpNetCore, Version=2.1.0.0, Culture=neutral, PublicKeyToken=0A8E8C4C76728C71". Phalanger必須在你的應用程式中正確配置。雖然它一樣可以被手動設定(http://www.php-compiler.net/blog/2011/installation-free-phalanger-web),但最簡單的方式就是使用安裝器了。
源碼
不可思議的是運行PHP代碼的核心就是PHP.Core.DynamicCode.Eval這個方法, 它在PhpNetCore.dll程式集中,唯一有些麻煩的可能就是方法所需的大量參數了。首先我們需要一個可用的PHP.Core.ScriptContext執行個體, 這就是Phalanger的運行php代碼的執行執行個體。你能從當前線程上擷取一個這樣的執行個體.特別注意PHP不是多線程的,所以ScriptContext只是僅僅與一個線程緊密關聯
1var context = PHP.Core.ScriptContext.CurrentContext;
然後我們將設定ScriptContext的輸出方式,這樣PHP指令碼才能轉換出我們所需要的流。這裡我們將設定兩個輸出方式 - 位元組流以及文字資料流。注意在最後你必須銷毀這些流,以至於所有的資料將會被正確的重新整理
1context.OutputStream = output;
2using (context.Output = new System.IO.StreamWriter(output)) {
我們也能在ScriptContext中設定全域變數,這樣我們也能很方便的傳輸一些參數到啟動並執行PHP代碼中。
1Operators.SetVariable(context, null, "X", "Hello World!");
最終我們將使用的Eval方法來運行PHP代碼. 而這個方法實際上被Phalanger內部用來處理PHP的 eval() 運算式.所以這就是為什麼這個方法有如此多參數的原因。
01// evaluate our code:
02return DynamicCode.Eval(
03 code,
04 false,/*phalanger internal stuff*/
05 context,
06 null,/*local variables*/
07 null,/*reference to "$this"*/
08 null,/*current class context*/
09 "Default.aspx.cs",/*file name, used for debug and cache key*/
10 1,1,/*position in the file used for debug and cache key*/
11 -1,/*something internal*/
12 null/*current namespace, used in CLR mode*/
13);
如果運行代碼錶現得和全域php代碼一樣時,大部分參數看上去就沒什麼特別之處了。最重要的參數就是code.該參數是一個包含你的php代碼的字串。Phalanger將先轉譯然後再編譯這段代碼。轉換出的.net位元組碼被將被作為暫存程序集被儲存在記憶體中(我們也稱它為瞬時程式集)
。注意整個轉譯以及編譯的過程很快,因為瞬時程式集也會被緩衝起來加速的運行相同PHP代碼。
如你所見,你也能在參數file name以及 postion中提供檔案名稱以及檔案所在位置;所以當你調試代碼然後單步調試進入運算式時,它將會剛好跳到position參數指定的位置。
注意被緩衝的瞬時程式集是否被更新將依賴於ScriptContext前面執行的PHP代碼(比如定義好的類以及方法),只有前後兩次產生的PHP代碼一致時,瞬時程式集才能被緩衝下來。這就是為什麼Eval方法中的參數code,file name以及position與前面的的匹配時才能緩衝後被重用。
那麼我們要記住,當隨後要運行更多的PHP程式碼片段時你應該首先考慮這個問題。
最後如果你打算在web應用程式中使用Phalanger時,你應該首先就初始化PHP.Core.RequestContext, 然後在php指令碼結束時銷毀它。
1using (var request_context = RequestContext.Initialize(
2 ApplicationContext.Default,
3 HttpContext.Current))
4{ /* all the stuff above */ }
總結:
總共就是這些。 因為後面執行的的PHP代碼中也包含了已經定義好的PHP方法,變數以及類,所以你也能在.net代碼中使用它們。
.net應用程式功能的語言。你也能用這個技術去建立一個使用c#建立網域名稱模組和PHP搭建使用者介面的web應用程式。