這個是繼:使用ext_skel和phpize構建php5擴展 內容 (拆分出來) Zend_API:深刻_PHP_內核:http://cn2.php.net/manual/zh/internals2.ze1.php
咱們使用ext_skel建立擴展 hello_module,該模塊包含一個方法:hello_world。
php
使用ext_skel 生成的代碼都是PHP_開頭的宏, 而不是ZEND_開頭. 實際上這二者是同樣的。mysql
在源代碼src/main/PHP.h 中發現: PHP_FUNCTION 就是ZEND_FUNCTION的別名,即:sql
其實,不少」PHP_」開頭的宏都用對應的」ZEND_」開頭的宏,這個應該是爲兼容以前的版本吧。vim
php_hello_module.hapi
hello_module.c數組
全部的 PHP模塊一般都包含如下幾個部分:安全
· 包含頭文件(引入所須要的宏、API定義等);app
· 聲明導出函數(用於 Zend函數塊的聲明);函數
· 聲明 Zend函數塊;優化
· 聲明 Zend模塊;
· 實現get_module()函數;
· 實現導出函數。
模塊所必須包含的頭文件僅有一個 php.h,它位於 main目錄下。這個文件包含了構建模塊時所必需的各類宏和API定義。
小提示:專門爲模塊建立一個含有其特有信息的頭文件是一個很好的習慣。這個頭文件應該包含 php.h和全部導出函數的定義。若是你是使用 ext_skel來建立模塊的話,那麼你可能已經有了這個文件,由於這個文件會被 ext_skel自動生成。
爲了聲明導出函數(也就是讓其成爲能夠被 PHP腳本直接調用的原生函數),Zend提供了一個宏來幫助完成這樣一個聲明。代碼以下:
ZEND_FUNCTION 宏聲明瞭一個使用 Zend內部 API來編譯的新的C函數。這個 C函數是 void類型,以 INTERNAL_FUNCTION_PARAMETERS(這是另外一個宏)爲參數,並且函數名字以 zif_爲前綴。把上面這句聲明展開能夠獲得這樣的代碼:
接着再把 INTERNAL_FUNCTION_PARAMETERS展開就會獲得這樣一個結果, 即ZEND_FUNCTION最終擴展結果:
可見,ZEND_FUNCTION就是簡單的聲明瞭一個名爲zif_ first_module的C函數,zif多是」Zend Internal Function」的縮寫。
在解釋器(interpreter)和執行器(executor)被分離出PHP包後,這裏面(指的是解釋器和執行器)原有的一些 API定義及宏也漸漸演變成了一套新的 API系統:Zend API。現在的Zend API 已經承擔了不少原來(指的是分離以前)本屬於 PHP API的職責,大量的 PHP API被以別名的方式簡化爲對應的Zend API。咱們推薦您應該儘量地使用 Zend API,PHP API只是由於兼容性緣由才被保留下來。舉例來講,zval和 pval實際上是同一類型,只不過 zval定義在 Zend部分,而 pval定義在 PHP部分(實際上 pval根本就是 zval的一個別名)。但因爲INTERNAL_FUNCTION_PARAMETERS是一個 Zend宏,所以咱們在上面的聲明中使用了zval。在編寫代碼時,你也應該老是使用 zval以遵循新的 Zend API規範。
這個聲明中的參數列表很是重要,你應該牢記於心。
PHP調用函數的 Zend參數:
參數 |
說明 |
ht |
這個參數包含了Zend參數的個數。但你不該該直接訪問這個值,而是應該經過 ZEND_NUM_ARGS()宏來獲取參數的個數。 |
return_value |
這個參數用來保存函數向 PHP返回的值。訪問這個變量的最佳方式也是用一系列的宏。後面咱們會有詳細說明。 |
this_ptr |
根據這個參數你能夠訪問該函數所在的對象(換句話說,此時這個函數應該是一個類的「方法」)。推薦使用函數 getThis()來獲得這個值。 |
return_value_used |
這個值主要用來標識函數的返回值是否爲腳本所使用。0表示腳本不使用其返回值,而 1則相反。一般用於檢驗函數是否被正確調用以及速度優化方面,這是由於返回一個值是一種代價很昂貴的操做(能夠在 array.c裏面看一下是如何利用這一特性的)。 |
executor_globals |
這個變量指向 Zend Engine的全局設置,在建立新變量時這個這個值會頗有用。咱們也能夠函數中使用宏 TSRMLS_FETCH()來引用這個值。 |
如今你已經聲明瞭導出函數,但Zend並不知道如何調用,所以還必須得將其引入 Zend。這些函數的引入是經過一個包含有N個zend_function_entry結構的數組來完成的。數組的每一項都對應於一個外部可見的函數,每一項都包含了某個函數在 PHP中出現的名字以及在 C代碼中所定義的名字。zend_function_entry的內部聲明:
字段 |
說明 |
fname |
指定在 PHP裏所見到的函數名(好比:fopen、mysql_connect或者是咱們樣例中的hello_world)。 |
handler |
指向對應 C函數的句柄。樣例能夠參考前面使用宏INTERNAL_FUNCTION_PARAMETERS的函數聲明。 |
func_arg_types |
用來標識一些參數是否要強制性地按引用方式進行傳遞。一般應將其設定爲 NULL。 |
對於咱們要建立的模塊:hello_module.該模塊提供一個方法:hello_word,能夠這樣聲明:
這樣完成C函數到PHP函數的映射的語句。
你可能已經看到了,這個結構的最後一項是 {NULL, NULL, NULL}。事實上,這個結構的最後一項也必須始終是 {NULL, NULL, NULL},由於 Zend Engine須要靠它來確認這些導出函數的列表是否列舉完畢。
注意:
1 )你不該該使用一個預約義的宏來代替列表的結尾部分(即{NULL, NULL, NULL}),由於編譯器會盡可能尋找一個名爲 「NULL」 的函數的指針來代替 NULL!
2)宏 ZEND_FE(「Zend Function Entry」的簡寫)將簡單地展開爲一個zend_function_entry結構。不過須要注意,這些宏對函數採起了一種很特別的命名機制:把你的C函數前加上一個 zif_前綴。比方說,ZEND_FE(hello_word)實際上是指向了一個名爲zif_hello_word()的 C函數。若是你想把宏和一個手工編碼的函數名混合使用時(這並非一個好的習慣),請你務必注意這一點。
小提示:若是出現了一些引用某個名爲zif_*()函數的編譯錯誤,那十有八九與 ZEND_FE 所定義的函數有關。
用來定義函數的宏
宏 |
說明 |
ZEND_FE(name, arg_types) |
定義了一個zend_function_entry內字段name爲 「name」的函數。arg_types應該被設定爲 NULL。這個聲明須要有一個對應的 C函數,該這個函數的名稱將自動以 zif_爲前綴。舉例來講,ZEND_FE("first_module", NULL)就引入了一個名爲 first_module()的 PHP函數,並被關聯到一個名爲 zif_first_module()的C函數。這個聲明一般與ZEND_FUNCTION搭配使用。 |
ZEND_NAMED_FE(php_name, name, arg_types) |
定義了一個名爲 php_name的 PHP函數,而且被關聯到一個名爲 name的 C函數。arg_types應該被設定爲 NULL。若是你不想使用宏 ZEND_FE自動建立帶有 zif_前綴的函數名的話能夠用這個來代替。一般與ZEND_NAMED_FUNCTION搭配使用。 |
ZEND_FALIAS(name, alias, arg_types) |
爲 name建立一個名爲 alias的別名。arg_types應該被設定爲 NULL。這個聲明不須要有一個對應的 C函數,由於它僅僅是建立了一個用來代替 name的別名而已。 |
PHP_FE(name, arg_types) |
之前的 PHP API,等同於 ZEND_FE。僅爲兼容性而保留,請儘可能避免使用。 |
PHP_NAMED_FE(runtime_name, name, arg_types) |
之前的 PHP API,等同於ZEND_NAMED_FE。僅爲兼容性而保留,請儘可能避免使用。 |
注意:你不能將 ZEND_FE和 PHP_FUNCTION混合使用,也不能將PHP_FE和 ZEND_FUNCTION混合使用。可是將ZEND_FE+ ZEND_FUNCTION和 PHP_FE + PHP_FUNCTION一塊兒混合使用是沒有任何問題的。固然咱們並不推薦這樣的混合使用,而是建議你所有使用 ZEND_*系列的宏。
Zend模塊的信息被保存在一個名爲zend_module_entry的結構,它包含了全部須要向 Zend提供的模塊信息。zend_module_entry的內部聲明」中看到這個 Zend模塊的內部定義:
字段 |
說明 |
size, zend_api, zend_debug and zts |
一般用 "STANDARD_MODULE_HEADER"來填充,它指定了模塊的四個成員:標識整個模塊結構大小的 size,值爲 ZEND_MODULE_API_NO常量的 zend_api,標識是否爲調試版本(使用 ZEND_DEBUG進行編譯)的 zend_debug,還有一個用來標識是否啓用了 ZTS(Zend線程安全,使用 ZTS或USING_ZTS進行編譯)的 zts。 |
name |
模塊名稱 (像「File functions」、「Socket functions」、「Crypt」等等).這個名字就是使用phpinfo()函數後在「Additional Modules」部分所顯示的名稱。 |
functions |
Zend 函數塊的指針,這個咱們在前面已經討論過。 |
module_startup_func |
模塊啓動函數。這個函數僅在模塊初始化時被調用,一般用於一些與整個模塊相關初始化的工做(好比申請初始化的內存等等)。若是想代表模塊函數調用失敗或請求初始化失敗請返回 FAILURE,不然請返回 SUCCESS。能夠經過宏 ZEND_MINIT 來聲明一個模塊啓動函數。若是不想使用,請將其設定爲 NULL。 |
module_shutdown_func |
模塊關閉函數。這個函數僅在模塊卸載時被調用,一般用於一些與模塊相關的反初始化的工做(好比釋放已申請的內存等等)。這個函數和module_startup_func()相對應。若是想代表函數調用失敗或請求初始化失敗請返回 FAILURE,不然請返回 SUCCESS。能夠經過宏ZEND_MSHUTDOWN來聲明一個模塊關閉函數。若是不想使用,請將其設定爲 NULL。 |
request_startup_func |
請求啓動函數。這個函數在每次有頁面的請求時被調用,一般用於與該請求相關的的初始化工做。若是想代表函數調用失敗或請求初始化失敗請返回 FAILURE,不然請返回 SUCCESS。注意:若是該模塊是在一個頁面請求中被動態加載的,那麼這個模塊的請求啓動函數將晚於模塊啓動函數的調用(其實這兩個初始化事件是同時發生的)。可使用宏 ZEND_RINIT 來聲明一個請求啓動函數,若不想使用,請將其設定爲 NULL。 |
request_shutdown_func |
請求關閉函數。這個函數在每次頁面請求處理完畢後被調用,正好與request_startup_func()相對應。若是想代表函數調用失敗或請求初始化失敗請返回 FAILURE,不然請返回 SUCCESS。注意:當在頁面請求做爲動態模塊加載時,這個請求關閉函數先於模塊關閉函數的調用(其實這兩個反初始化事件是同時發生的)。可使用宏 ZEND_RSHUTDOWN來聲明這個函數,若不想使用,請將其設定爲NULL。 |
info_func |
模塊信息函數。當腳本調用phpinfo()函數時,Zend便會遍歷全部已加載的模塊,並調用它們的這個函數。每一個模塊都有機會輸出本身的信息。一般狀況下這個函數被用來顯示一些環境變量或靜態信息。可使用宏 ZEND_MINFO來聲明這個函數,若不想使用,請將其設定爲 NULL。 |
version |
模塊的版本號。若是你暫時還不想給某塊設置一個版本號的話,你能夠將其設定爲NO_VERSION_YET。但咱們仍是推薦您在此添加一個字符串做爲其版本號。版本號一般是相似這樣: "2.5-dev", "2.5RC1", "2.5" 或者 "2.5pl3"等等。 |
Remaining structure elements |
這些字段一般是在模塊內部使用的,一般使用宏STANDARD_MODULE_PROPERTIES來填充。並且你也不該該將他們設定別的值。STANDARD_MODULE_PROPERTIES_EX一般只會在你使用了全局啓動函數(ZEND_GINIT)和全局關閉函數(ZEND_GSHUTDOWN)時纔用到,通常狀況請直接使用 STANDARD_MODULE_PROPERTIES 。 |
在咱們的例子當中,這個結構被定義以下:
這基本上是你能夠設定最簡單、最小的一組值。該模塊名稱爲"hello_module」,而後是所引用的函數列表,其後全部的啓動和關閉函數都沒有使用,均被設定爲了。
做爲參考,你能夠在表 3 「全部可聲明模塊啓動和關閉函數的宏」中找到全部的可設置啓動與關閉函數的宏。這些宏暫時在咱們的例子中還還沒有用到,但稍後咱們將會示範其用法。你應該使用這些宏來聲明啓動和關閉函數,由於它們都須要引入一些特殊的變量(INIT_FUNC_ARGS和 SHUTDOWN_FUNC_ARGS),而這兩個參數宏將在你使用下面這些預約義宏時被自動引入(其實就是圖個方便)。若是你是手工聲明的函數或是對函數的參數列表做了一些必要的修改,那麼你就應該修改你的模塊相應的源代碼來保持兼容。
表3.3全部可聲明模塊啓動和關閉函數的宏
宏 |
描述 |
ZEND_MINIT(module) |
聲明一個模塊的啓動函數。函數名被自動設定爲zend_minit_<module> (好比:zend_minit_first_module)。一般與ZEND_MINIT_FUNCTION搭配使用。 |
ZEND_MSHUTDOWN(module) |
聲明一個模塊的關閉函數。函數名被自動設定爲zend_mshutdown_<module> (好比:zend_mshutdown_first_module)。一般與ZEND_MSHUTDOWN_FUNCTION搭配使用。 |
ZEND_RINIT(module) |
聲明一個請求的啓動函數。函數名被自動設定爲zend_rinit_<module> (好比:zend_rinit_first_module)。一般與ZEND_RINIT_FUNCTION搭配使用。 |
ZEND_RSHUTDOWN(module) |
聲明一個請求的關閉函數。函數名被自動設定爲zend_rshutdown_<module> (好比:zend_rshutdown_first_module)。一般與ZEND_RSHUTDOWN_FUNCTION搭配使用。 |
ZEND_MINFO(module) |
聲明一個輸出模塊信息的函數,用於phpinfo()。函數名被自動設定爲zend_info_<module> (好比:zend_info_first_module)。一般與ZEND_MINFO_FUNCTION搭配使用。 |
這個函數只用於動態可加載模塊。咱們先來看一下如何經過宏ZEND_GET_MODULE來建立這個函數:
這個函數的實現被一條件編譯語句所包圍。這是頗有必要的,由於get_module()函數僅僅在你的模塊想要編譯成動態模塊時纔會被調用。經過在編譯命令行指定編譯條件:COMPILE_DL_FIRSTMOD(也就是上面咱們設置的那個預約義)的打開與否,你就能夠決定是編譯成一個動態模塊仍是編譯成一個內建模塊。若是想要編譯成內建模塊的話,那麼這個get_module()將被移除。
get_module()函數在模塊加載時被 Zend所調用,你也能夠認爲是被你 PHP腳本中的dl()函數所調用。這個函數的做用就是把模塊的信息信息塊傳遞 Zend並通知 Zend 獲取這個模塊的相關內容。
若是你沒有在一個動態可加載模塊中實現get_module()函數,那麼當你在訪問它的時候 Zend 就會向你拋出一個錯誤信息。
導出函數的實現是咱們構建擴展的最後一步。在咱們的hello_module例子中,函數被實現以下:
這個函數是用宏 ZEND_FUNCTION來聲明的,和前面咱們討論的 Zend函數塊中的 ZEND_FE聲明相對應。在函數的聲明以後,咱們的代碼便開始檢查和接收這個函數的參數。在將參數進行轉換後將其值返回。
Zend容許模塊在加載和卸載時收到通知,以進行初始化和清除工做,咱們要作的就是把相應函數傳遞給Zend,它會在合適的時機自動調用。
Zend提供了以下四個宏,分別用於聲明對應的函數:
宏 |
意義 |
ZEND_MODULE_STARTUP_D(hello_module) |
在加載模塊時調用 |
ZEND_MODULE_SHUTDOWN_D(hello_module) |
在卸載模塊時調用 |
ZEND_MODULE_ACTIVATE_D(hello_module) |
一個頁面開始運行時調用 |
ZEND_MODULE_DEACTIVATE_D(hello_module) |
一個頁面運行完畢時調用 |
在聲明模塊信息裏面
1)STANDARD_MODULE_HEADER和STANDARD_MODULE_PROPERTIES宏填充了該結構的首尾部分,具體填充了什麼並非咱們須要關心的,而且爲了兼容後續版本也最好不要手工修改。
2)第2、三項是模塊名稱和導出函數,名稱能夠任意填寫,導出函數就是咱們在前面準備好的zend_function_entry數組。
3)後面的四個參數內容:
就是用於啓動和終止函數,他們都是指針函數。
4) 參數值 PHP_MINFO(hello_module),也是指針函數,用於配合phpinfo()來顯示模塊信息。
這些宏的用法和ZEND_FUNCTION宏同樣,展開後就是聲明瞭特定原型的函數,其參數module能夠是任意的,但最好使用模塊名稱。這些函數的參數中,對咱們有用的是int module_number,它是模塊號,全局惟一,後面會提到其用處。
在聲明和實現相應函數時,都應該使用這些宏。最後,須要把這些函數填寫到zend_module_entry裏,可按順序使用以下的宏,這些宏生成相應的函數名稱:
ZEND_MODULE_STARTUP_N(hello_module) |
ZEND_MODULE_SHUTDOWN_N(hello_module) |
ZEND_MODULE_ACTIVATE_N(hello_module) |
ZEND_MODULE_DEACTIVATE_N(hello_module) |
在咱們的hello_module中,對應的聲明(因爲ext_skel生成的代碼是PHP API ,和ZEND API本質上同樣)
對應的實現:
若是不想使用這個函數,對應的參數均可以設置爲null
那麼模塊聲明