網上關於 PHP 的資料多如牛毛,關於其核心 Zend Engine 的卻少之又少。PHP 中文手冊出現已 N 年,但 Zend API 的翻譯卻仍然不見動靜,小弟自覺對 Zend Engine 略有小窺,而且翻譯也有助於強迫本身對文章的進一步理解,因而嘗試翻譯此章,英文很差,恭請方家指點校覈。轉載請註明來自撫琴居(譯者主頁):http://www.yAnbiN.org php
知者不言,言者不知。 ――老子《道德經》五十六章 html
有時候,單純依靠 PHP 「自己」是不行的。儘管普通用戶不多遇到這種狀況,但一些專業性的應用則常常須要將 PHP 的性能發揮到極致(這裏的性能是指速度或功能)。因爲受到 PHP 語言自己的限制,同時還可能不得不把龐大的庫文件包含到每一個腳本當中。所以,某些新功能並非總能被順利實現,因此咱們必須另外尋找一些方法來克服 PHP 的這些缺點。 mysql
瞭解到了這一點,咱們就應該接觸一下 PHP 的心臟並探究一下它的內核-能夠編譯成 PHP 並讓之工做的 C 代碼-的時候了。 sql
「擴展 PHP」提及來容易作起來難。PHP 如今已經發展成了一個具備數兆字節源代碼的很是成熟的系統。要想深刻這樣的一個系統,有不少東西須要學習和考慮。在寫這一章節的時候,咱們最終決定採用「邊學邊作」的方式。這也許並非最科學和專業的方式,但卻應該是最有趣和最有效的一種方式。在下面的小節裏,你首先會很是快速的學習到如何寫一個雖然很基礎但卻能當即運行的擴展,而後將會學習到有關 Zend API 的高級功能。另一個選擇就是將其做爲一個總體,一次性的講述全部的這些操做、設計、技巧和訣竅等,而且可讓咱們在實際動手前就能夠獲得一副完整的願景。這看起來彷佛是一個更好的方法,也沒有死角,但它卻枯燥無味、費時費力,很容易讓人感到氣餒。這就是咱們爲何要採用很是直接的講法的緣由。 數據庫
注意,儘管這一章會盡量多講述一些關於 PHP 內部工做機制的知識,但要想真的給出一份在任什麼時候間任何狀況下的PHP 擴展指南,那簡直是不可能的。PHP 是如此龐大和複雜,以至於只有你親自動手實踐一下才有可能真正理解它的內部工做機制,所以咱們強烈推薦你隨時參考它的源代碼來進行工做。 apache
Zend 指的是語言引擎,PHP 指的是咱們從外面看到的一套完整的系統。這聽起來有點糊塗,但其實並不複雜(見圖3-1 PHP 內部結構圖)。爲了實現一個 WEB 腳本的解釋器,你須要完成如下三個部分的工做: 編程
解釋器部分:負責對輸入代碼的分析、翻譯和執行; api
功能性部分:負責具體實現語言的各類功能(好比它的函數等等); 數組
接口部分:負責同 WEB 服務器的會話等功能。 瀏覽器
Zend 包括了第一部分的所有和第二部分的局部,PHP 包括了第二部分的局部和第三部分的所有。他們合起來稱之爲 PHP 包。Zend 構成了語言的核心,同時也包含了一些最基本的 PHP 預約義函數的實現。PHP 則包含了全部創造出語言自己各類顯著特性的模塊。
|
圖3-1 PHP 內部結構圖 |
下面將要討論PHP 容許在哪裏擴展以及如何擴展。
正如上圖(圖3-1 PHP 內部結構圖)所示,PHP 主要以三種方式來進行擴展:外部模塊,內建模塊和 Zend 引擎。下面咱們將分別討論這些方式:
外部模塊能夠在腳本運行時使用 dl() 函數載入。這個函數從磁盤載入一個共享對象並將它的功能與調用該函數的腳本進行綁定並使之生效。腳本終止後,這個外部模塊將在內存中被丟棄。這種方式有利有弊,以下表所示:
優勢 | 缺點 |
---|---|
外部模塊不須要從新對 PHP 進行編譯。 | 共享對象在每次腳本調用時都須要對其進行加載,速度較慢。 |
PHP經過「外包」方式來讓自身的體積保持很小。 | 附加的外部模塊文件會讓磁盤變得比較散亂。 |
每一個想使用該模塊功能的腳本都必須使用dl() 函數手動加載,或者在 php.ini 文件當中添加一些擴展標籤(這並不老是一個恰當的解決方案)。 |
綜上所述,外部模塊很是適合開發第三方產品,較少使用的附加的小功能或者僅僅是調試等這些用途。爲了迅速開發一些附加功能,外部模塊是最佳方式。但對於一些常用的、實現較大的,代碼較爲複雜的應用,那就有些得不償失了。
第三方可能會考慮在 php.ini 文件中使用擴展標籤來建立一個新的外部模塊。這些外部模塊徹底同主PHP 包分離,這一點很是適合應用於一些商業環境。商業性的發行商能夠僅發送這些外部模塊而沒必要再額外建立那些並不容許綁定這些商業模塊的PHP 二進制代碼。
內建模塊被直接編譯進 PHP 並存在於每個 PHP 處理請求當中。它們的功能在腳本開始運行時當即生效。和外部模塊同樣,內建模塊也有一下利弊:
優勢 | 缺點 |
---|---|
無需專門手動載入,功能即時生效。 | 修改內建模塊時須要從新編譯PHP。 |
無需額外的磁盤文件,全部功能均內置在 PHP 二進制代碼當中。 | PHP 二進制文件會變大而且會消耗更多的內存。 |
固然,你也能直接在 Zend 引擎裏面進行擴展。若是你須要在語言特性方面作些改動或者是須要在語言核心內置一些特別的功能,那麼這就是一種很好的方式。但通常狀況下應該盡力避免對 Zend 引擎的修改。這裏面的改動會致使和其餘代碼的不兼容,並且幾乎沒有人會適應打過特殊補丁的 Zend 引擎。何況這些改動與主 PHP 源代碼是不可分割的,所以就有可能在下一次的官方的源代碼更新中被覆蓋掉。所以,這種方式一般被認爲是「不良的習慣」。因爲使用極其稀少,本章將再也不對此進行贅述。
在咱們開始討論具體編碼這個話題前,你應該讓本身熟悉一下 PHP 的源代碼樹以即可以迅速地對各個源文件進行定位。這也是編寫和調試 PHP 擴展所必須具有的一種能力。
下表列出了一些主要目錄的內容:
目錄 | 內容 |
---|---|
php-src | 包含了PHP主源文件和主頭文件;在這裏你能夠找到全部的 PHP API 定義、宏等內容。(重要). 其餘的一些東西你也能夠在這裏找到。 |
php-src/ext | 這裏是存放動態和內建模塊的倉庫;默認狀況下,這些就是被集成於主源碼樹中的「官方」 PHP 模塊。自 PHP 4.0開始,這些PHP標準擴展均可以編譯爲動態可載入的模塊。(至少這些是能夠的)。 |
php-src/main | 這個目錄包含主要的 PHP 宏和定義。 (重要) |
php-src/pear | 這個目錄就是「PHP 擴展與應用倉庫」的目錄。包含了PEAR 的核心文件。 |
php-src/sapi | 包含了不一樣服務器抽象層的代碼。 |
TSRM | Zend 和 PHP的 「線程安全資源管理器」 (TSRM) 目錄。 |
除此以外,你也應該注意一下這些文件所包含的一些文件。舉例來講,哪些文件與 Zend 執行器有關,哪些文件又爲 PHP 初始化工做提供了支持等等。在閱讀完這些文件以後,你還能夠花點時間再圍繞PHP包來看一些文件,瞭解一下這些文件和模塊之間的依賴性――它們之間是如何依賴於別的文件又是如何爲其餘文件提供支持的。同時這也能夠幫助你適應一下 PHP 創做者們代碼的風格。要想擴展 PHP,你應該儘快適應這種風格。
Zend 是用一些特定的規範構建的。爲了不破壞這些規範,你應該遵循如下的幾個規則:
幾乎對於每一項重要的任務,Zend 都預先提供了極爲方便的宏。在下面章節的圖表裏將會描述到大部分基本函數、結構和宏。這些宏定義大多能夠在 Zend.h 和 Zend_API.h 中找到。咱們建議您在學習完本節以後仔細看一下這些文件。(固然你也能夠如今就閱讀這些文件,但你可能不會留下太多的印象。)
資源管理仍然是一個極爲關鍵的問題,尤爲是對服務器軟件而言。資源裏最具寶貴的則非內存莫屬了,內存管理也必須極端當心。內存管理在 Zend 中已經被部分抽象,並且你也應該堅持使用這些抽象,緣由顯而易見:因爲得以抽象,Zend 就能夠徹底控制內存的分配。Zend 能夠肯定一塊內存是否在使用,也能夠自動釋放未使用和失去引用的內存塊,所以就能夠避免內存泄漏。下表列出了一些經常使用函數:
函數 | 描述 |
---|---|
emalloc() | 用於替代 malloc()。 |
efree() | 用於替代 free()。 |
estrdup() | 用於替代 strdup()。 |
estrndup() | 用於替代strndup()。速度要快於 estrdup() 並且是二進制安全的。若是你在複製以前預先知道這個字符串的長度那就推薦你使用這個函數。 |
ecalloc() | 用於替代 calloc()。 |
erealloc() | 用於替代 realloc()。 |
emalloc(), estrdup(), estrndup(), ecalloc(), 和 erealloc() 用於申請內部的內存,efree() 則用來釋放這些前面這些函數申請的內存。e*() 函數所用到的內存僅對當前本地的處理請求有效,而且會在腳本執行完畢,處理請求終止時被釋放。
Zend 還有一個線程安全資源管理器,這能夠爲多線程WEB 服務器提供更好的本地支持。不過這須要你爲全部的全局變量申請一個局部結構來支持併發線程。可是由於在寫本章內容時Zend 的線程安全模式仍未完成,所以咱們沒法過多地涉及這個話題。
下列目錄與文件函數應該在 Zend 模塊內使用。它們的表現和對應的 C 語言版本徹底一致,只是在線程級提供了虛擬目錄的支持。
Zend 函數 | 對應的 C 函數 |
---|---|
V_GETCWD() | getcwd() |
V_FOPEN() | fopen() |
V_OPEN() | open() |
V_CHDIR() | chdir() |
V_GETWD() | getwd() |
V_CHDIR_FILE() | 將當前的工做目錄切換到一個以文件名爲參數的該文件所在的目錄。 |
V_STAT() | stat() |
V_LSTAT() | lstat() |
在 Zend 引擎中,與處理諸如整數、布爾值等這些無需爲其保存的值而額外申請內存的簡單類型不一樣,若是你想從一個函數返回一個字符串,或往符號表新建一個字符串變量,或作其餘相似的事情,那你就必須確認是否已經使用上面的 e*() 等函數爲這些字符串申請內存。(你可能對此沒有多大的感受。無所謂,如今你只需在腦子裏有點印象便可,咱們稍後就會再次回到這個話題)
像數組和對象等這些複雜類型須要另外不一樣的處理。它們被出存在哈希表中,Zend 提供了一些簡單的 API 來操做這些類型。
PHP 提供了一套很是靈活的自動構建系統(automatic build system),它把全部的模塊均放在 Ext 子目錄下。每一個模塊除自身的源代碼外,還都有一個用來配置該擴展的 config.m4 文件(詳情請參見http://www.gnu.org/software/m4/manual/m4.html)。
包括 .cvsignore 在內的全部文件都是由位於 Ext 目錄下的 ext_skel 腳本自動生成的,它的參數就是你想建立模塊的名稱。這個腳本會建立一個與模塊名相同的目錄,裏面包含了與該模塊對應的一些的文件。
下面是操做步驟:
:~/cvs/php4/ext:> ./ext_skel --extname=my_module
響應以下:
Creating directory my_module Creating basic files: config.m4 .cvsignore my_module.c php_my_module.h CREDITS EXPERIMENTAL tests/001.phpt my_module.php [done]. To use your new extension, you will have to execute the following steps: 1. $ cd .. 2. $ vi ext/my_module/config.m4 3. $ ./buildconf 4. $ ./configure --[with|enable]-my_module 5. $ make 6. $ ./php -f ext/my_module/my_module.php 7. $ vi ext/my_module/my_module.c 8. $ make Repeat steps 3-6 until you are satisfied with ext/my_module/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary.
這些指令就會生成前面所說的那些文件。爲了可以在自動配置文件和構建程序中包含新增長的模塊,你還須要再運行一次 buildconf 命令。這個命令會經過搜索 Ext 目錄和查找全部 config.m4 文件來從新生成 configure 腳本。默認狀況下的的 config.m4 文件如例 3-1 所示,看起來可能會稍嫌複雜:
例3.1 默認的 config.m4 文件
若是你不太熟悉 M4 文件(如今毫無疑問是熟悉 M4 文件的大好時機),那麼就可能會有點糊塗。可是別擔憂,其實很是簡單。
注意:凡是帶有 dnl 前綴的都是註釋,註釋是不被解析的。
config.m4 文件負責在配置時解析 configure 的命令行選項。這就是說它將檢查所需的外部文件而且要作一些相似配置與安裝的任務。
默認的配置文件將會在 configure 腳本中產生兩個配置指令:--with-my_module 和 --enable-my_module。當須要引用外部文件時使用第一個選項(就像用 --with-apache 指令來引用 Apache 的目錄同樣)。第二個選項可讓用戶簡單的決定是否要啓用該擴展。無論你使用哪個指令,你都應該註釋掉另一個。也就是說,若是你使用了 --enable-my_module,那就應該去掉--with-my_module。反之亦然。
默認狀況下,經過 ext_skel 建立的 config.m4 都能接受指令,而且會自動啓用該擴展。啓用該擴展是經過 PHP_EXTENSION 這個宏進行的。若是你要改變一下默認的狀況,想讓用戶明確的使用 --enable-my_module 或 --with-my_module 指令來把擴展包含在 PHP 二進制文件當中,那麼將 if test "$PHP_MY_MODULE" != "no"改成if test "$PHP_MY_MODULE" == "yes"便可。
if test "$PHP_MY_MODULE" == "yes"; then dnl Action.. PHP_EXTENSION(my_module, $ext_shared) fi
這樣就會致使在每次從新配置和編譯 PHP 時都要求用戶使用 --enable-my_module 指令。
另外請注意在修改 config.m4 文件後須要從新運行 buildconf 命令。
咱們先來建立一個很是簡單的擴展,這個擴展除了一個將其整形參數做爲返回值的函數外幾乎什麼都沒有。下面(「例3-2 一個簡單的擴展」)就是這個樣例的代碼:
例3.2 一個簡單的擴展
這段代碼已經包含了一個完整的 PHP 模塊。稍後咱們會詳細解釋這段代碼,如今讓咱們先討論一下構建過程。(在咱們討論 API 函數前,這可讓心急的人先實驗一下。)
模塊的編譯基本上有兩種方法:
在 Ext 目錄內使用「make」 機制,這種機制也能夠編譯出動態可加載模塊。
手動編譯源代碼。
第一種方法明顯受到人們的偏心。自 PHP 4.0 以來,這也被標準化成了一個的複雜的構建過程。這種複雜性也致使了難於被理解這個缺點。在本章最後咱們會更詳細的討論這些內容,但如今仍是讓咱們使用默認的 make 文件吧。
第二種方法很適合那些(由於某種緣由而)沒有完整 PHP 源碼樹的或者是很喜歡敲鍵盤的人。雖然這些狀況是比較罕見,但爲了內容的完整性咱們也會介紹一下這種方法。
爲了可以使用這種標準機制流程來編譯這些代碼,讓咱們把它全部的子目錄都複製到 PHP 源碼樹的 Ext 目錄下。而後運行 buildconf 命令,這將會建立一個新的包含了與咱們的擴展相對應的選項的 configure 腳本。默認狀況下,樣例中的全部代碼都是未激活的,所以你不用擔憂會破壞你的構建程序。在 buildconf 執行完畢後,再使用 configure --help 命令就會顯示出下面的附加模塊:
--enable-array_experiments BOOK: Enables array experiments --enable-call_userland BOOK: Enables userland module --enable-cross_conversion BOOK: Enables cross-conversion module --enable-first_module BOOK: Enables first module --enable-infoprint BOOK: Enables infoprint module --enable-reference_test BOOK: Enables reference test module --enable-resource_test BOOK: Enables resource test module --enable-variable_creation BOOK: Enables variable-creation module
前面樣例(「例3-2 一個簡單的擴展」)中的模塊(first_module)能夠使用 --enable-first_module 或 enable-first_module=yes 來激活。
手動編譯須要運行如下命令:
動做 | 命令 |
---|---|
編譯 | cc -fpic -DCOMPILE_DL_FIRST_MODULE=1 -I/usr/local/include -I. -I.. -I../Zend -c -o <your_object_file> <your_c_file> |
鏈接 | cc -shared -L/usr/local/lib -rdynamic -o <your_module_file> <your_object_file(s)> |
編譯命令只是簡單的讓編譯器產生一些中間代碼(不要忽略了--fpic 參數),而後又定義了COMPILE_DL 常量來通知代碼這是要編譯爲一個動態可加載的模塊(一般用來測試,咱們稍後會討論它)。這些選項後面是一些編譯這些源代碼所必須包含的庫文件目錄。
注意:
本例中全部 include 的路徑都是都是 Ext 目錄的相對路徑。若是您是在其餘目錄編譯的這些源文件,那麼還要相應的修改路徑名。編譯所須要的目錄有 PHP 目錄,Zend 目錄和模塊所在的目錄(若是有必要的話)。
鏈接命令也是一個很是簡單的把模塊鏈接成一個動態模塊的命令。
你能夠在編譯指令中加入優化選項,儘管這些已經在樣例中忽略了(不過你仍是能夠從前面討論的 make 模版文件中發現一些)。
注意:
手動將模塊靜態編譯和鏈接到 PHP 二進制代碼的指令很長很長,所以咱們在這裏不做討論。(手動輸入那些指令是很低效的。)
根據你所選擇的不一樣的構建過程,你要麼把擴展編譯進一個新的PHP 的二進制文件,而後再鏈接到 Web 服務器(或以CGI 模式運行),要麼將其編譯成一個 .so (共享庫)文件。若是你將上面的樣例文件 first_module.c 編譯成了一個共享庫,那麼編譯後的文件應該是 first_module.so。要想使用它,你就必須把他複製到一個 PHP 能訪問到的地方。若是僅僅是爲了測試的話,簡單起見,你能夠把它複製到你的 htdocs 目錄下,而後用「例3.3 first_module.so 的一個測試文件」中的代碼來進行一下測試。若是你將其直接編譯編譯進 PHP 二進制文件的話,那就不用調用 dl() 函數了,由於這個模塊的函數在腳本一開始運行就生效了。
警告:
爲了安全起見,你不該該將你的動態模塊放入一個公共目錄。即便是一個簡單的測試你能夠那麼作,那也應該把它放進產品環境中的一個隔離的目錄。
例3.3 first_module.so 的一個測試文件
調用這個測試文件,結果應該輸出爲:
We sent '2' and got '2'。
如有須要,你能夠調用 dl() 函數來載入一個動態可加載模塊。這個函數負責尋找指定的共享庫並進行加載使其函數在 PHP 中生效。這個樣例模塊僅輸出了一個函數 first_module(),這個函數僅接受一個參數,並將其轉換爲整數做爲函數的結果返回。
若是你已經進行到了這一步,那麼,恭喜你,你已經成功建立了你的第一個 PHP 擴展!
實際上,在對靜態或動態模塊進行編譯時沒有太多故障處理工做要作。惟一可能的問題就是編譯器會警告說找不到某些定義或者相似的事情。若是出現這種狀況,你應該確認一下全部的頭文件都是可用的而且它們的路徑都已經在編譯命令中被指定。爲了確保每一個文件都能被正確地定位,你能夠先提取一個乾淨的 PHP 源碼樹,而後在 Ext 目錄使用自動構建工具來建立這些文件。用這種方法就能夠確保一個安全的編譯環境。假如這樣也不行,那就只好試試手動編譯了。
PHP 也可能會警告說在你的模塊裏面有一些未定義的函數。(若是你沒有改動樣例文件的話這種狀況應該不會發生。)假如你在模塊中拼錯了一些你想訪問的外部函數的名字,那麼它們就會在符號表中顯示爲「未能鏈接的符號」。這樣在 PHP 動態加載或鏈接時,它們就不會運行--在二進制文件中沒有相應的符號。爲了解決這個問題,你能夠在你的模塊文件中找一下錯誤的聲明或外部引用。注意,這個問題僅僅發生在動態可加載模塊身上。而在靜態模塊身上則不會發生,由於靜態模塊在編譯時就會拋出這些錯誤。
OK,如今你已經有了一個安全的構建環境,也能夠把模塊編譯進 PHP 了。那麼,如今就讓咱們開始詳細討論一下這裏面到底是如何工做的吧~
全部的 PHP 模塊一般都包含如下幾個部分:
包含頭文件(引入所須要的宏、API定義等);
聲明導出函數(用於 Zend 函數塊的聲明);
聲明 Zend 函數塊;
聲明 Zend 模塊;
實現 get_module() 函數;
實現導出函數。
模塊所必須包含的頭文件僅有一個 php.h,它位於 main 目錄下。這個文件包含了構建模塊時所必需的各類宏和API 定義。
小提示:
專門爲模塊建立一個含有其特有信息的頭文件是一個很好的習慣。這個頭文件應該包含 php.h 和全部導出函數的定義。若是你是使用 ext_skel 來建立模塊的話,那麼你可能已經有了這個文件,由於這個文件會被 ext_skel 自動生成。
爲了聲明導出函數(也就是讓其成爲能夠被 PHP 腳本直接調用的原生函數),Zend 提供了一個宏來幫助完成這樣一個聲明。代碼以下:
ZEND_FUNCTION ( my_function );
ZEND_FUNCTION 聲明瞭一個使用 Zend 內部 API 來編譯的新的C 函數。這個 C 函數是 void 類型,以 INTERNAL_FUNCTION_PARAMETERS (這是另外一個宏)爲參數,並且函數名字以 zif_ 爲前綴。把上面這句聲明展開能夠獲得這樣的代碼:
voidzif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
接着再把 INTERNAL_FUNCTION_PARAMETERS 展開就會獲得這樣一個結果:
在解釋器(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 根本就是 <code>zval 的一個別名)。但因爲 INTERNAL_FUNCTION_PARAMETERS 是一個 Zend 宏,所以咱們在上面的聲明中使用了 zval 。在編寫代碼時,你也應該老是使用 zval 以遵循新的 Zend API 規範。
這個聲明中的參數列表很是重要,你應該牢記於心。(表 3.1 「PHP 調用函數的 Zend 參數」詳細介紹了這些參數)
表3.1 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 。這些函數的引入是經過一個包含有 N 個 zend_function_entry 結構的數組來完成的。數組的每一項都對應於一個外部可見的函數,每一項都包含了某個函數在 PHP 中出現的名字以及在 C 代碼中所定義的名字。zend_function_entry 的內部定義如「例3.4 zend_function_entry 的內部聲明」所示:
例3.4 zend_function_entry 的內部聲明
字段 | 說明 |
---|---|
fname | 指定在 PHP 裏所見到的函數名(好比:fopen、mysql_connect 或者是咱們樣例中的 first_module)。 |
handler | 指向對應 C 函數的句柄。樣例能夠參考前面使用宏INTERNAL_FUNCTION_PARAMETERS 的函數聲明。 |
func_arg_types | 用來標識一些參數是否要強制性地按引用方式進行傳遞。一般應將其設定爲 NULL。 |
對於上面的例子,咱們能夠這樣來聲明:
zend_function_entryfirstmod_functions[] = { ZEND_FE(first_module, NULL) {NULL, NULL, NULL} };
你可能已經看到了,這個結構的最後一項是 {NULL, NULL, NULL} 。事實上,這個結構的最後一項也必須始終是 {NULL, NULL, NULL},由於 Zend Engine 須要靠它來確認這些導出函數的列表是否列舉完畢。
注意:
你不該該使用一個預約義的宏來代替列表的結尾部分(即{NULL, NULL, NULL}),由於編譯器會盡可能尋找一個名爲 NULL 的函數的指針來代替 NULL !
宏 ZEND_FE(Zend Function Entry的簡寫)將簡單地展開爲一個 zend_function_entry 結構。不過須要注意,這些宏對函數採起了一種很特別的命名機制:把你的C函數前加上一個 zif_ 前綴。比方說,ZEND_FE(first_module) 實際上是指向了一個名爲 zif_first_module() 的 C 函數。若是你想把宏和一個手工編碼的函數名混合使用時(這並非一個好的習慣),請你務必注意這一點。
小提示:
若是出現了一些引用某個名爲 zif_*() 函數的編譯錯誤,那十有八九與 ZEND_FE 所定義的函數有關。
「表 3.2 可用來定義函數的宏」給出了一個能夠用來定義一個函數的全部宏的列表:
表3.2 可用來定義函數的宏
宏 | 說明 |
---|---|
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 提供的模塊信息。你能夠在「例 3.5 zend_module_entry 的內部聲明」中看到這個 Zend 模塊的內部定義:
例3.5 zend_module_entry 的內部聲明
字段 | 說明 |
---|---|
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 。 |
在咱們的例子當中,這個結構被定義以下:
這基本上是你能夠設定最簡單、最小的一組值。該模塊名稱爲「First Module」,而後是所引用的函數列表,其後全部的啓動和關閉函數都沒有使用,均被設定爲了 NULL。
做爲參考,你能夠在表 3.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 來建立這個函數:
#if COMPILE_DL_FIRSTMOD ZEND_GET_MODULE(firstmod) #endif
這個函數的實現被一條件編譯語句所包圍。這是頗有必要的,由於 get_module() 函數僅僅在你的模塊想要編譯成動態模塊時纔會被調用。經過在編譯命令行指定編譯條件:COMPILE_DL_FIRSTMOD (也就是上面咱們設置的那個預約義)的打開與否,你就能夠決定是編譯成一個動態模塊仍是編譯成一個內建模塊。若是想要編譯成內建模塊的話,那麼這個 get_module() 將被移除。
get_module() 函數在模塊加載時被 Zend 所調用,你也能夠認爲是被你 PHP 腳本中的 dl() 函數所調用。這個函數的做用就是把模塊的信息信息塊傳遞 Zend 並通知 Zend 獲取這個模塊的相關內容。
若是你沒有在一個動態可加載模塊中實現 get_module() 函數,那麼當你在訪問它的時候 Zend 就會向你拋出一個錯誤信息。
導出函數的實現是咱們構建擴展的最後一步。在咱們的 first_module 例子中,函數被實現以下:
這個函數是用宏 ZEND_FUNCTION 來聲明的,和前面咱們討論的 Zend 函數塊中的 ZEND_FE 聲明相對應。在函數的聲明以後,咱們的代碼便開始檢查和接收這個函數的參數。在將參數進行轉換後將其值返回。(參數的接收和處理咱們立刻會在下一節中講到)。
一切基本上就這樣了 ―― 咱們在實現一個模塊時不會再遇到其餘方面的事了。內建模塊也基本上同動態模塊差很少。所以,有了前面幾節咱們所掌握的信息,再在你遇到 PHP 源代碼的時候你就有能力去搞定這些小麻煩。
在下面的幾個小節裏,咱們將會學習到如何利用 PHP 內核來建立一個更爲強大的擴展!
對於擴展來講,最重要的一件事就是如何接收和處理那些經過函數參數傳遞而來的數據。大多數擴展都是用來處理某些特定的輸入數據(或者是根據參數來決定進行某些特定的動做),而函數的參數則是 PHP 代碼層和 C 代碼層之間交換數據的惟一途徑。固然,你也能夠經過事先定義好的全局變量來交換數據(這個咱們稍後會談到),不過這種習慣可不太好,咱們應該儘可能避免。
在 PHP 中並不須要作任何顯式的函數聲明,這也就是咱們爲何說 PHP 的調用語法是動態的並且 PHP 從不會檢查任何錯誤的緣由。調用語法是否正確徹底是留給用戶本身的工做。也就是說,在調用一個函數時徹底有可能此次用一個參數而下次用 4 個參數,並且兩種狀況在語法上都是正確的。
由於 PHP 不但無法根據函數的顯式聲明來對調用進行語法檢查,並且它還支持可變參數,因此咱們就不得不在所調用函數的內部來獲取參數個數。這個工做能夠交給宏 ZEND_NUM_ARGS 來完成。在(PHP4)之前,這個宏(在 PHP3 中應該指的是宏 ARG_COUNT,由於 ZEND_NUM_ARGS 宏是直到 PHP 4.0 纔出現的,而且其定義一直未變。PHP4 及之後雖也有 ARG_COUNT 宏定義,但卻僅僅是爲兼容性而保留的,並不推薦使用,譯者注)是利用所調用的 C 函數中的變量 ht(就是定義在宏 INTERNAL_FUNCTION_PARAMETERS 裏面的那個,HashTable * 類型)來獲取參數個數的,而如今變量 ht 就只包含函數的參數個數了(int 類型)。與此同時還定義了一個啞宏:ZEND_NUM_ARGS(直接等於 ht,見 Zend.h)。儘可能地採用 ZEND_NUM_ARGS 是個好習慣,由於這樣能夠保證在函數調用接口上的兼容性。
下面的代碼展現瞭如何檢查傳入函數的參數個數的正確性:
if(ZEND_NUM_ARGS() != 2) { WRONG_PARAM_COUNT; }
若是沒有爲該函數傳入兩個參數,那麼就會退出該函數而且發出一個錯誤消息。在這段代碼中咱們使用了一個工具宏:WRONG_PARAM_COUNT,它主要用來拋出一個相似
"Warning: Wrong parameter count for firstmodule() in /home/www/htdocs/firstmod.php on line 5"
這樣的錯誤信息。
這個宏會主要負責拋出一個默認的錯誤信息,而後便返回調用者。咱們能夠在 zend_API.h 中找到它的定義:
ZEND_API voidwrong_param_count(void); #defineWRONG_PARAM_COUNT { wrong_param_count(); return; }
正如您所見,它調用了一個內部函數 wrong_param_count() ,這個函數會輸出一個警告信息。至於如何拋出一個自定義的錯誤信息,能夠參見後面的「打印信息」一節。
對傳入的參數進行解析是一件很常見同時也是頗爲乏味的事情,並且同時你還得作好標準化的錯誤檢查和發送錯誤消息等雜事。不過從 PHP 4.1.0 開始,咱們就能夠用一個新的參數解析 API 來搞定這些事情。這個 API 能夠大大簡化參數的接收處理工做,儘管它在處理可變參數時還有點弱。但既然絕大部分函數都沒有可變參數,那麼使用這個 API 也就理所應當地成爲了咱們處理函數參數時的標準方法。
這個用於參數解析的函數的原型大體以下:
intzend_parse_parameters(intnum_args TSRMLS_DC, char *type_spec, ...);
第一個參數 num_args 代表了咱們想要接收的參數個數,咱們常用 ZEND_NUM_ARGS() 來表示對傳入的參數「有多少要多少」。第二參數應該老是宏 TSRMLS_CC 。第三個參數 type_spec 是一個字符串,用來指定咱們所期待接收的各個參數的類型,有點相似於 printf 中指定輸出格式的那個格式化字符串。剩下的參數就是咱們用來接收 PHP 參數值的變量的指針。
zend_parse_parameters() 在解析參數的同時會盡量地轉換參數類型,這樣就能夠確保咱們老是能獲得所指望的類型的變量。任何一種標量類型均可以轉換爲另一種標量類型,可是不能在標量類型與複雜類型(好比數組、對象和資源等)之間進行轉換。
若是成功地解析和接收到了參數而且在轉換期間也沒出現錯誤,那麼這個函數就會返回 SUCCESS,不然返回 FAILURE。若是這個函數不能接收到所預期的參數個數或者不能成功轉換參數類型時就會拋出一些相似下面這樣的錯誤信息:
Warning - ini_get_all() requires at most 1 parameter, 2 given Warning - wddx_deserialize() expects parameter 1 to be string, array given
固然,每一個錯誤信息都會帶有錯誤發生時所在的文件名和行數的。
下面這份清單完整地列舉出了咱們能夠指定接收的參數類型:
l - 長整數
d - 雙精度浮點數
s - 字符串 (也多是空字節)和其長度
b - 布爾值
r - 資源, 保存在 zval*
a - 數組, 保存在 zval*
o - (任何類的)對象, 保存在 zval*
O - (由class entry 指定的類的)對象, 保存在 zval*
z - 實際的 zval*
下面的一些字符在類型說明字符串(就是那個 char *type_spec)中具備特別的含義:
| - 代表剩下的參數都是可選參數。若是用戶沒有傳進來這些參數值,那麼這些值就會被初始化成默認值。
/ - 代表參數解析函數將會對剩下的參數以 SEPARATE_ZVAL_IF_NOT_REF() 的方式來提供這個參數的一份拷貝,除非這些參數是一個引用。
! - 代表剩下的參數容許被設定爲 NULL(僅用在 a、o、O、r和z身上)。若是用戶傳進來了一個 NULL 值,則存儲該參數的變量將會設置爲 NULL。
固然啦,熟悉這個函數的最好的方法就是舉個例子來講明。下面咱們就來看一個例子:
注意,在最後的一個例子中,咱們直接用了數值 3 而不是 ZEND_NUM_ARGS() 來做爲想要取得參數的個數。這樣若是咱們的 PHP 函數具備可變參數的話咱們就能夠只接收最小數量的參數。固然,若是你想操做剩下的參數,你能夠用 zend_get_parameters_array_ex() 來獲得。
這個參數解析函數還有一個帶有附加標誌的擴展版本,這個標誌可讓你控制解析函數的某些動做。
intzend_parse_parameters_ex(intflags, intnum_args TSRMLS_DC, char *type_spec, ...);
這個標誌(flags)目前僅接受 ZEND_PARSE_PARAMS_QUIET 這一個值,它表示這個函數不輸出任何錯誤信息。這對那些能夠傳入徹底不一樣類型參數的函數很是有用,但這樣你也就不得不本身輸出錯誤信息。
下面就是一個如何既能夠接收 3 個長整形數又能夠接收一個字符串的例子:
我想你經過上面的那些例子就能夠基本掌握如何接收和處理參數了。若是你想看更多的例子,請翻閱 PHP 源碼包中那些自帶的擴展的源代碼,那裏麪包含了你可能遇到的各類狀況。
獲取函數參數這件事情咱們還能夠經過 zend_get_parameters_ex() 來完成(不推薦使用這些舊式的 API,咱們推薦您使用前面所述的新式的參數解析函數):
zval **parameter; if(zend_get_parameters_ex(1, ¶meter) != SUCCESS) { WRONG_PARAM_COUNT; }
全部的參數都存儲在一個二次指向的 zval 容器裏面(其實就是一個 zval* 數組,譯者注)。上面的這段代碼嘗試接收 1 個參數而且將其保存在 parameter 所指向的位置。
zend_get_parameters_ex() 至少須要兩個參數。第一個參數表示咱們想要接收參數的個數(這個值一般是對應於 PHP 函數參數的個數,由此也能夠看出事先對調用語法正確性的檢查是多麼重要)。第二個參數(包括剩下的全部參數)指向一個二次指向 zval 的指針。(即 ***zval,是否是有點糊塗了?^_^)這些指針是必須的,由於 Zend 內部是使用 **zval 進行工做的。爲了能被在咱們函數內部定義的**zval局部變量所訪問,咱們就必須在用一個指針來指向它。
zend_get_parameters_ex() 的返回值能夠是 SUCCESS 或> FAILURE,分別表示參數處理的成功或失敗。若是處理失敗,那最大的可能就是因爲沒有指定一個正確的參數個數。若是處理失敗,則應該使用宏 WRONG_PARAM_COUNT 來退出函數。
若是想接收更多的的參數,能夠用相似下面一段的代碼來處理:
zval **param1, **param2, **param3, **param4; if(zend_get_parameters_ex(4, ¶m1, ¶m2, ¶m3, ¶m4) != SUCCESS) { WRONG_PARAM_COUNT; }
zend_get_parameters_ex() 僅檢查你是否在試圖訪問過多的參數。若是函數有 5 個參數,而你僅僅接收了其中的 3 個,那麼你將不會收到任何錯誤信息,zend_get_parameters_ex() 僅返回前三個參數的值。再次調用 zend_get_parameters_ex() 也不會得到剩下兩個參數的值,而仍是返回前三個參數的值。
若是你想接收一些可變參數,那用前面咱們剛剛討論的方法就不太合適了,主要是由於咱們將不得不爲每一個可能的參數個數來逐行調用 zend_get_parameters_ex(),顯然這很不爽。
爲了解決這個問題,咱們能夠借用一下 zend_get_parameters_array_ex() 這個函數。它能夠幫助咱們接收不定量的參數並將其保存在咱們指定的地方:
讓咱們來看看這幾行代碼。首先代碼檢查了傳入參數的個數,確保在咱們可接受的範圍內;而後就調用 zend_get_parameters_array_ex() 把全部有效參數值的指針填入 parameter_array。
咱們能夠在 fsockopen() 函數(位於ext/standard/fsock.c )中找到一個更爲漂亮的實現。代碼大體以下,你也不用擔憂尚未弄懂所有的函數,由於咱們很快就會談到它們。
例3.6 PHP中帶有可變參數的 fsockopen() 函數的實現
fsockopen() 能夠接收 2-5 個參數。在必需的變量聲明以後便開始檢查參數的數量範圍。而後在一個 switch 語句中使用了貫穿(fall-through)法來處理這些的參數。這個 switch 語句首先處理最大的參數個數(即 5),隨後依次處理了參數個數爲 4 和 3 的狀況,最後用 break 關鍵字跳出 switch 來忽略對其餘狀況下參數(也就是隻含有 2 個參數狀況)的處理。這樣在通過 switch 處理以後,就開始處理參數個數爲最小時(即 2)的狀況。
這種像樓梯同樣的多級處理方法能夠幫助咱們很方便地處理一些可變參數。
爲了存取一些參數,讓每一個參數都具備一個明確的(C)類型是頗有必要的。但 PHP 是一種動態語言,PHP 從不作任何類型檢查方面的工做,所以無論你想不想,調用者均可能會把任何類型的數據傳到你的函數裏。好比說,若是你想接收一個整數,但調用者卻可能會給你傳遞個數組,反之亦然 - PHP 可無論這些的。
爲了不這些問題,你就必須用一大套 API 函數來對傳入的每個參數都作一下強制性的類型轉換。(見表3.4 參數類型轉換函數)
注意:
全部的參數轉換函數都以一個 **zval 來做爲參數。
表3.4 參數類型轉換函數
函數 | 說明 |
---|---|
convert_to_boolean_ex() | 強制轉換爲布爾類型。若原來是布爾值則保留,不作改動。長整型值0、雙精度型值0.0、空字符串或字符串‘0’還有空值 NULL 都將被轉換爲 FALSE(本質上是一個整數 0)。數組和對象若爲空則轉換爲 FALSE,不然轉爲 TRUE。除此以外的全部值均轉換爲 TRUE(本質上是一個整數 1)。 |
convert_to_long_ex() | 強制轉換爲長整型,這也是默認的整數類型。若是原來是空值NULL、布爾型、資源固然還有長整型,則其值保持不變(由於本質上都是整數 0)。雙精度型則被簡單取整。包含有一個整數的字符串將會被轉換爲對應的整數,不然轉換爲 0。空的數組和對象將被轉換爲 0,不然將被轉換爲 1。 |
convert_to_double_ex() | 強制轉換爲一個雙精度型,這是默認的浮點數類型。若是原來是空值 NULL 、布爾值、資源和雙精度型則其值保持不變(只變一下變量類型)。包含有一個數字的字符串將被轉換成相應的數字,不然被轉換爲 0.0。空的數組和對象將被轉換爲 0.0,不然將被轉換爲 1.0。 |
convert_to_string_ex() | 強制轉換爲數組。若原來就是一數組則不做改動。對象將被轉換爲一個以其屬性爲鍵名,以其屬性值爲鍵值的數組。(方法強制轉換爲字符串。空值 NULL 將被轉換爲空字符串。布爾值 TRUE 將被轉換爲 ‘1’,FALSE 則被轉爲一個空字符串。長整型和雙精度型會被分別轉換爲對應的字符串,數組將會被轉換爲字符串‘Array’,而對象則被轉換爲字符串‘Object’。 |
convert_to_array_ex(value) | 強制轉換爲數組。若原來就是一數組則不做改動。對象將被轉換爲一個以其屬性爲鍵名,以其屬性值爲鍵值的數組。(方法將會被轉化爲一個‘scalar’鍵,鍵值爲方法名)空值 NULL 將被轉換爲一個空數組。除此以外的全部值都將被轉換爲僅有一個元素(下標爲 0)的數組,而且該元素即爲該值。 |
convert_to_object_ex(value) | 強制轉換爲對象。若原來就是對象則不做改動。空值 NULL 將被轉換爲一個空對象。數組將被轉換爲一個以其鍵名爲屬性,鍵值爲其屬性值的對象。其餘類型則被轉換爲一個具備‘scalar’屬性的對象,‘scalar’屬性的值即爲該值自己。 |
convert_to_null_ex(value) | 強制轉換爲空值 NULL。 |
在你的參數上使用這些函數能夠確保傳遞給你的數據都是類型安全的。若是提供的類型不是須要的類型,PHP 就會強制性地返回一個相應的僞值(好比空字符串、空的數組或對象、數值 0 或布爾值的 FALSE 等)來確保結果是一個已定義的狀態。
下面的代碼是從前面討論過的模塊中摘錄的,其中就用到了這些轉換函數:
在收到參數指針之後,參數值就被轉換成了一個長整型(或整形),轉換的結果就是這個函數的返回值。若是想要弄懂如何存取到這個返回值,咱們就須要對 zval 有一點點認識。它的定義以下:
例3.7 PHP/Zend zval 類型的定義
實際上,pzval(定義在 php.h)就是 zval(定義在 zend.h)的一個別名,都是 _zval_struct 結構的一個別名。_zval_struct 是一個頗有趣的結構,它保存了這個結構的真實值 value、類型 type 和引用信息 is_ref。字段 value 是一個 zvalue_value 聯合,根據變量類型的不一樣,你就能夠訪問不一樣的聯合成員。對於這個結構的描述,可參見「表3.5 Zend zval 結構」、「表3.6 Zend zvalue_value 結構」和「表3.7 Zend 變量類型」。
表3.5 Zend zval 結構
字段 | 說明 |
---|---|
value | 變量內容的聯合,參見「表3.6 Zend zvalue_value 結構」。 |
type | 變量的類型。「表3.7 Zend 變量類型」給出了一個完整的變量類型列表。 |
is_ref | 0 表示這個變量還不是一個引用。1 表示這個變量還有被別的變量所引用。 |
refcount | 表示這個變量是否仍然有效。每增長一個對這個變量的引用,這個數值就增長 1。反之,每失去一個對這個變量的引用,該值就會減1。當引用計數減爲0的時候,就說明已經不存在對這個變量的引用了,因而這個變量就會自動釋放。 |
表3.6 Zend zvalue_value 結構
字段 | 說明 |
---|---|
lval | 若是變量類型爲 IS_LONG、IS_BOOLEAN 或 IS_RESOURCE 就用這個屬性值。 |
dval | 若是變量類型爲 IS_DOUBLE 就用這個屬性值。 |
str | 若是變量類型爲 IS_STRING 就訪問這個屬性值。它的字段 len 表示這個字符串的長度,字段 val 則指向該字符串。因爲 Zend 使用的是 C 風格的字符串,所以字符串的長度就必須把字符串末尾的結束符 0×00 也計算在內。 |
ht | 若是變量類型爲數組,那這個 ht 就指向數組的哈希表入口。 |
obj | 若是變量類型爲 IS_OBJECT 就用這個屬性值。 |
表3.7 Zend 變量類型
類型常量 | 說明 |
---|---|
IS_NULL | 表示是一個空值 NULL。 |
IS_LONG | 是一個(長)整數。 |
IS_DOUBLE | 是一個雙精度的浮點數。 |
IS_STRING | 是一個字符串。 |
IS_ARRAY | 是一個數組。 |
IS_OBJECT | 是一個對象。 |
IS_BOOL | 是一個布爾值。 |
IS_RESOURCE | 是一個資源(關於資源的討論,咱們之後會在適當的時候討論到它)。 |
IS_STRING | 是一個常量。 |
想訪問一個長整型數,那你就訪問 zval.value.lval;想訪問一個雙精度數,那你就訪問 zval.value.dval,依此類推。不過注意,由於全部的值都是保存在一個聯合裏面,因此若是你用了不恰當的字段去訪問,那就可能會獲得一個毫無心義的結果。
訪問一個數組和對象可能會稍微複雜些,稍後再說。
若是函數裏面的參數是經過引用傳遞進來的,可是你又想去修改它,那就須要多加當心了。
根據咱們前面所討論的知識,咱們尚未辦法去修改一個經 PHP 函數參數傳進來的 zval 。固然你能夠修改那些在函數內部建立的局部變量的 zval ,但這並表明你能夠修改任何一個指向 Zend 自身內部數據的 zval (也就是那些非局部的 zval)!
這是爲何呢?我想你可能注意到了,咱們前面討論的 API 函數都是相似於 *_ex() 這樣子的。好比咱們用 zend_get_parameters_ex() 而不用 zend_get_parameters(),用 convert_to_long_ex() 而不用 convert_to_long() 等等。這些 *_ex() 函數被稱爲新的「擴展」的 Zend API,它們的速度要快於對應的傳統 API,但反作用是它們只提供了只讀訪問機制。
由於 Zend 內部是靠引用機制來運行的,所以不一樣的變量就有可能引自同一個 value (zval 結構的字段 value)。而修改一個 zval 就要求這個 zval 的 value 必須是獨立的,也就是說這個 value 不能被其餘 zval 引用。若是有一個 zval 裏面的 value 還被其餘 zval 引用了,你也同時把這個 value 給修改了,那你也同時就把其餘 zval 的 value 給修改了,由於它們的 value 只是簡單地指向了這個 value 而已。
但 zend_get_parameters_ex() 是根本無論這些的,它只是簡單地返回一個你所指望的那個 zval 的指針。至於這個 zval 是否還存在其餘引用,who care?(因此咱們說這些 *_ex() 只提供了只讀機制,並無提供可寫機制。你若利用 *_ex() 的結果強行賦值也是能夠的,但這樣就無法保證數據安全了。譯註)。而和這個 API 對應的傳統 API zend_get_parameters () 就會即時檢查 value 的引用狀況。若是它發現了對 value 的引用,它就會立刻再從新建立一個獨立的 zval ,而後把引用的數據複製一份到新的剛剛申請的空間裏面,而後返回這個新的 zval 的指針。
這個動做咱們稱之爲「zval 分離(或者 pval 分離)」。因爲 *_ex()函數並不執行「zval 分離」操做,所以它們雖然快,可是卻不能用於進行寫操做。
但無論怎樣,要想修改參數,寫操做是不可避免的。因而 Zend 使用了這樣一個特別的方式來處理寫操做:不管什麼時候,只要函數的某個參數使用過引用傳遞的,那它就自動進行 zval 分離。這也就意味着無論什麼時間,只要你像下面這樣來調用一個 PHP 函數,Zend 就會自動確保傳入的是一個獨立的 value 並處於「寫安全」狀態:
my_function(&$parameter);
但這不是通常參數(指不帶 & 前綴但也是引用的參數,譯者注)的狀況。全部不是直接經過引用(指不帶 & 前綴)傳遞的參數都將只是處在一種「只讀」狀態(其實這裏的「只讀」狀態能夠理解爲「寫不安全」狀態)。
這就要求你確認是否真的在同一個引用打交道,不然你可能會收到你不太想要的結果。咱們能夠使用宏 PZVAL_IS_REF 來檢查一個參數是不是經過引用傳遞的。這個宏接收一個 zval* 參數。「例3.8 檢查參數是否經引用傳遞」給出了這樣一個例子:
例3.8 檢查參數是否經引用傳遞
有時候你可能會遇到過這種狀況:你想對用 zend_get_parameters_ex() 接收的可是沒有經過引用傳遞的一個參數進行寫操做。這時你能夠用宏 SEPARATE_ZVAL 來手工進行 zval 分離操做。這樣能夠獲得一個新建立的與原來內部數據獨立的 zval,但這個 zval 僅在局部有效,它能夠被修改或銷燬而不影響外部的全局 zval 。
zval **parameter; /* 接收參數 */ zend_get_parameters_ex(1, ¶meter); /* 此時仍然關聯在 Zend 的內部數據緩衝區 *//* 如今將「寫安全」化 */ SEPARATE_ZVAL(parameter); /* 如今你能夠放心大膽去修改了,無需擔憂外部的 zval 會受到影響 */// ……
由於宏 SEPARATE_ZVAL 經過 emalloc() 函數來申請一個新的 zval,因此這也就意味着若是你不主動去釋放這段內存的話,那它就會直到腳本停止時才被釋放。若是你大量調用這個宏卻沒有釋放,那它可能會瞬間塞滿你的內存。
注意:由於如今已經不多遇到和須要傳統 API(諸如 zend_get_parameters() 等等)了(貌似它有點過期了),因此有關這些 API 本節再也不贅述。
當 PHP 腳本與擴展互相交換數據時,咱們還須要作一件很重要的事情,那就是建立變量。這一小節將會展現如何處理那些 PHP 腳本所支持的變量類型。
要建立一個可以被 PHP 腳本所訪問的「外部變量」,咱們只需先建立一個 zval 容器,而後對這個 zval 結構進行必要的填充,最後再把它引入到 Zend 的內部符號表中就能夠了。並且幾乎全部變量的建立基本上都是這幾個步驟:
宏 MAKE_STD_ZVAL 經過 ALLOC_ZVAL 來申請一個新的 zval 容器的內存空間並調用 INIT_ZVAL(查看 PHP4/5 的源代碼可知此處可能爲原文筆誤,實際上應爲 INIT_PZVAL,下同。譯註)將其初始化。在當前的 Zend Engine 中,INIT_ZVAL 所負責的初始化工做除了將 zval 容器的引用計數(refcount)置爲 1 以外,還會把引用標識也清除(即把 is_ref 也置爲 0)。並且在之後的 Zend Engine 中還可能會繼續擴展這個 INIT_ZVAL 宏操做,所以咱們推薦您使用 MAKE_STD_ZVAL 而非簡單使用一個 ALLOC_ZVAL 來完成一個變量的建立工做。固然,若是您是想優化一下速度(或者是不想明確地初始化這個 zval 容器),那仍是能夠只用 ALLOC_ZVAL 來搞定的。不過咱們並不推薦這麼作,由於這將不能保證數據的完整性。
ZEND_SET_SYMBOL 宏負責將咱們新建的變量引入 Zend 內部的符號表。這個宏會首先檢查一下這個變量是否已經存在於符號表中,若是已經存在則將其轉換爲一個引用變量(同時會自動銷燬原有的 zval 容器)。事實上這個方法常常用在某些速度要求並不苛刻但但願能少用一些內存的狀況下。
您可能注意到了 ZEND_SET_SYMBOL 是經過宏 EG 來訪問 Zend 執行器(executor)的全局結構的。特別的,若是你使用的是 EG(active_symbol_table),那你就能夠訪問到當前的活動符號表,從而能夠處理一些全局或局部變量。其中局部變量可能會依不一樣的函數而有所不一樣。
固然,要是你很在乎程序的運行速度而且不在意那一點點內存的話,那你能夠跳過對相同名字變量存在性的檢查而直接使用 zend_hash_update() 函數強行將這個名字的變量插入符號表。
實際上這段代碼也是不少擴展使用的標準方法。
上面這段代碼所產生的變量是局部變量,做用範圍跟調用函數的上下文相關。若是你想建立一個全局變量那也很簡單,方法仍是老方法,只需換個符號表就能夠了。
注意:
如今宏 ZEND_SET_SYMBOL 使用的符號表是全局符號表 EG(symbol_table)。另外,active_symbol_table 是一個指針,而 symbol_table 卻不是。這就是咱們爲何分別使用 EG(active_symbol_table) 和 &EG(symbol_table) 的緣由 - ZEND_SET_SYMBOL 須要一個指針做爲其參數。
固然,你一樣也能夠強行更新這個符號表:
例 3.9 「建立不一樣做用域的變量」向咱們展現了建立一個局部變量local_variable和一個全局變量global_variable的過程。
注意:
你可能會發如今 PHP 函數裏彷佛還不能直接訪問這個全局變量global_variable,由於你在使用前還必須使用 global $global_variable; 聲明一下。
例3.9 建立不一樣做用域的變量
如今讓咱們以長整型變量起點,瞭解一下如何爲一個變量賦值。PHP 中的整數所有是長整型,其值的存儲方法也是很是簡單的。看一下咱們前面討論過的 zval.value 容器的結構你就會明白,全部的長整型數據都是直接保存在這個聯合中的 lval 字段,相應的數據類型(type 字段)爲 IS_LONG(見例3.10 「長整型變量的建立」)。
例3.10 長整型變量的建立
zval *new_long; MAKE_STD_ZVAL(new_long); new_long->type = IS_LONG; new_long->value.lval = 10;
或者你也能夠直接使用 ZVAL_LONG 宏:
zval *new_long; MAKE_STD_ZVAL(new_long); ZVAL_LONG(new_long, 10);
PHP 中的浮點數都是雙精度型,存儲方法和整型差很少,也很簡單。它的值是直接放在聯合中的 dval 字段,對應數據類型爲 IS_DOUBLE。
zval *new_double; MAKE_STD_ZVAL(new_double); new_double->type = IS_DOUBLE; new_double->value.dval = 3.45;
一樣你也能夠直接使用宏 ZVAL_DOUBLE:
zval *new_double; MAKE_STD_ZVAL(new_double); ZVAL_DOUBLE(new_double, 3.45);
字符串的存儲可能會稍費點事。字符串的值是保存在 zval.value 容器中的 str 結構裏面,相應的數據類型爲 IS_STRING。不過須要注意的是,前面咱們已經提到過,全部與 Zend 內部數據結構相關的字符串都必須使用 Zend 本身的內存管理函數來申請空間。這樣一來,就不能使用那些靜態字符串(由於這種字符串的內存空間是編譯器預先分配的)或經過標準函數(好比 malloc() 等函數)來申請空間的字符串。
請注意:
在這咱們使用了 estrdup() 函數。固然咱們仍可直接使用一個預約義宏 ZVAL_STRING 來完成這項工做:
ZVAL_STRING 宏的第三個參數指明瞭該字符串是否須要被複制(使用 estrdup() 函數)。值爲 1 將致使該字符串被複制,爲 0 時則僅僅是簡單地將其指向該變量的值容器(即字符串地址,譯註)。這項特性將會在你僅僅須要建立一個變量並將其指向一個已經由 Zend 內部數據內存時變得頗有用。
若是你想在某一位置截取該字符串或已經知道了這個字符串的長度,那麼能夠使用宏 ZVAL_STRINGL(zval, string, length, duplicate) 來完成這項工做。這個函數會額外須要一個代表該字符串長度地參數。這個宏不但速度上要比 ZVAL_STRING 快,並且仍是二進制安全的。
若是想建立一個空字符串,那麼將其長度置 0 而且把 empty_string 做爲字符串的內容便可:
new_string->type = IS_STRING; new_string->value.str.len = 0; new_string->value.str.val = empty_string;
固然,咱們也專門爲您準備了一個相應的宏 ZVAL_EMPTY_STRING 來搞定這個步驟:
MAKE_STD_ZVAL(new_string); ZVAL_EMPTY_STRING(new_string);
布爾類型變量的建立跟長整型差很少,只是數據類型爲 IS_BOOL,而且字段 lval 所容許的值只能爲 0 和 1:
zval *new_bool; MAKE_STD_ZVAL(new_bool); new_bool->type = IS_BOOL; new_bool->value.lval = 1;
也能夠使用宏 ZVAL_BOOL (須要另外指定一個值)來完成這件事情,或者乾脆直接使用 ZVAL_TRUE 或 ZVAL_FALSE 直接將其值設定爲 TRUE 或 FALSE。
數組在 Zend 內部是用哈希表(HashTable)來存儲的,這個哈希表能夠使用一系列的 zend_hash_*() 函數來訪問。所以咱們在建立一個數組時必須先建立一個哈希表,而後再將其保存在 zval.value 容器的 ht 字段中。
不過針對數組的建立咱們如今另有一套很是方便 API 可供使用。爲了建立一個數組,咱們可先調用一下 array_init() 函數:
zval *new_array; MAKE_STD_ZVAL(new_array); array_init(new_array); // array_init() 函數老是返回 SUCCESS
要給數組增長一個元素,根據實際須要,咱們有 N 個函數可供調用。「表3.8 用於關聯數組的 API」、「表3.9 用於索引數組的 API 第一部分」和「表3.10 用於索引數組的 API 第二部分」有這些函數的說明。全部這些函數在調用成功時返回 SUCCESS,在調用失敗時返回 FAILURE。
表3.8 用於關聯數組的 API
函數 | 說明 |
---|---|
add_assoc_long(zval *array, char *key, long n); | 添加一個長整型元素。 |
add_assoc_unset(zval *array, char *key); | 添加一個 unset 元素。 |
add_assoc_bool(zval *array, char *key, int b); | 添加一個布爾值。 |
add_assoc_resource(zval *array, char *key, int r); | 添加一個資源。 |
add_assoc_double(zval *array, char *key, double d); | 添加一個浮點值。 |
add_assoc_string(zval *array, char *key, char *str, int duplicate); | 添加一個字符串。duplicate 用於代表這個字符串是否要被複制到 Zend 的內部內存。 |
add_assoc_stringl(zval *array, char *key, char *str, uint length, int duplicate); | 添加一個指定長度的字符串。其他跟add_assoc_string () 相同。 |
add_assoc_zval(zval *array, char *key, zval *value); | 添加一個 zval 結構。 這在添加另一個數組、對象或流等數據時會頗有用。 |
表3.9 用於索引數組的 API 第一部分
函數 | 說明 |
---|---|
add_index_long(zval *array, uint idx, long n); | 添加一個長整型元素。 |
add_index_unset(zval *array, uint idx); | 添加一個 unset 元素。 |
add_index_bool(zval *array, uint idx, int b); | 添加一個布爾值。 |
add_index_resource(zval *array, uint idx, int r); | 添加一個資源。 |
add_index_double(zval *array, uint idx, double d); | 添加一個浮點值。 |
add_index_string(zval *array, uint idx, char *str, int duplicate); | 添加一個字符串。duplicate 用於代表這個字符串是否要被複制到 Zend 的內部內存。 |
add_index_stringl(zval *array, uint idx, char *str, uint length, int duplicate); | 添加一個指定長度的字符串。其他跟add_index_string () 相同。 |
add_index_zval(zval *array, uint idx, zval *value); | 添加一個 zval 結構。 這在添加另一個數組、對象或流等數據時會頗有用。 |
表3.10 用於索引數組的 API 第二部分
函數 | 說明 |
---|---|
add_next_index_long(zval *array, long n); | 添加一個長整型元素。 |
add_next_index_unset(zval *array); | 添加一個 unset 元素。 |
add_next_index_bool(zval *array, int b); | 添加一個布爾值。 |
add_next_index_resource(zval *array, int r); | 添加一個資源。 |
add_next_index_double(zval *array, double d); | 添加一個浮點值。 |
add_next_index_string(zval *array, char *str, int duplicate); | 添加一個字符串。duplicate 用於代表這個字符串是否要被複制到 Zend 的內部內存。 |
add_next_index_stringl(zval *array, char *str, uint length, int duplicate); | 添加一個指定長度的字符串。其他跟add_next_index_string () 相同。 |
add_next_index_zval(zval *array, zval value); | 添加一個 zval 結構。 這在添加另一個數組、對象或流等數據時會頗有用。 |
全部這些函數都是對 Zend 內部 hash API 的一種友好抽象。所以,若你願意,你大可直接使用那些 hash API 進行操做。比方說,假如你已經有了一個 zval 容器並想把它插入到一個數組,那麼你就能夠直接使用 zend_hash_update() 來把它添加到一個關聯數組(例3.11 給關聯數組添加一個元素)或索引數組(例3.12 給索引數組添加一個元素)。
例3.11 給關聯數組添加一個元素
例3.12 給索引數組添加一個元素
若是還想模擬下 add_next_index_*() ,那能夠這麼作:
zend_hash_next_index_insert(ht, zval **new_element, sizeof(zval *), NULL)
注意:
若是要從函數裏面返回一個數組,那就必須首先對預約義變量 return_value (return_value 是咱們導出函數中的一個預約義參數,用來存儲返回值)使用一下 array_init() 函數。不過倒沒必要對其使用 MAKE_STD_ZVAL 。
提示:
爲了不一遍又一遍地書寫 new_array->value.ht,咱們能夠用 HASH_OF(new_array) 來代替。並且出於兼容性和風格上的考慮,咱們也推薦您這麼作。
既然對象能夠被轉換成數組(反之亦然),那麼你可能已經猜到了二者應該具備不少類似之處。實際上,對象就是使用相似的函數進行操做的,所不一樣的是建立它們時所用的 API。
咱們能夠調用 object_init() 函數來初始化一個對象:
zval *new_object; MAKE_STD_ZVAL(new_object); if(object_init(new_object) != SUCCESS) { // do error handling here }
能夠使用「表3.11 用於建立對象的 API」來給對象添加一些成員。
表3.11 用於建立對象的 API
函數 | 說明 |
---|---|
add_property_long(zval *object, char *key, long l); | 添加一個長整型類型的屬性值。 |
add_property_unset(zval *object, char *key); | 添加一個 unset 類型的屬性值。 |
add_property_bool(zval *object, char *key, int b); | 加一個布爾類型的屬性值。 |
add_property_resource(zval *object, char *key, long r); | 添加一個資源類型的屬性值。 |
add_property_double(zval *object, char *key, double d); | 添加一個浮點類型的屬性值。 |
add_property_string(zval *object, char *key, char *str, int duplicate); | 添加一個字符串類型的屬性值。 |
add_property_stringl(zval *object, char *key, char *str, uint length, int duplicate); | 添加一個指定長度的字符串類型的屬性值,速度要比 add_property_string() 函數快,並且是二進制安全的。 |
add_property_zval(zval *obect, char *key, zval container); | 添加一個 zval 結構的屬性值。 這在添加另一個數組、對象等數據時會頗有用。 |
資源是 PHP 中一種比較特殊的數據類型。「資源」這個詞其實並不特指某些特殊類型的數據,事實上,它指的是一種能夠維護任何類型數據信息方法的抽象。全部的資源均保存在一個 Zend 內部的資源列表當中。列表中的每份資源都有一個指向能夠代表其種類的類型定義的指針。Zend 在內部統一管理全部對資源的引用。直接訪問一個資源是不大可能的,你只能經過提供的 API 來對其進行操做。某個資源一旦失去引用,那就會觸發調用相應的析構函數。
舉例來講,數據庫鏈接和文件描述符就是一種資源。MySQL 模塊中就有其「標準」實現。固然其餘模塊(好比 Oracle 模塊)也都用到了資源。
注意:
實際上,一個資源能夠指向函數中任何一種你所感興趣的數據(好比指向一個結構等等)。而且用戶也只能經過某個資源變量來將資源信息傳遞給相應的函數。
要想建立一個資源你必須先註冊一個這個資源的析構函數。這是由於Zend 須要瞭解當你把某些數據存到一個資源裏後,若是再也不須要這份資源時該如何將其釋放。這個析構函數會在釋放資源(不管是手工釋放仍是自動釋放)時被 Zend 依次調用。析構函數註冊後,Zend 會返回一個此種資源類型句柄。這個句柄會在之後任何訪問此種類型的資源的時候被用到,並且這個句柄絕大部分時間都保存在擴展的全局變量裏面。這裏你不須要擔憂線程安全方面的問題,由於你只是須要在模塊初始化註冊一次就好了。
下面是這個用於註冊資源析構函數的 Zend 函數定義:
你或許已經注意到了,在該函數中咱們須要提供兩種不一樣的資源析構函數:一種是普通資源的析構函數句柄,一種是持久化資源的析構函數句柄。持久化資源通常用於諸如數據庫鏈接等這類狀況。在註冊資源時,這兩個析構函數至少得提供一個,另一個析構函數可簡單地設爲 NULL。
zend_register_list_destructors_ex() 接受如下幾個參數:
ld | 普通資源的析構函數。 |
pld | 持久化資源的析構函數。 |
type_name | 爲你的資源指定一個名稱。在 PHP 內部爲某個資源類型起個名字這是個好習慣(固然名字不能重複)。用戶調用 var_dump($resource) 時就可取得該資源的名稱。 |
module_number | 這個參數在你模塊的 PHP_MINIT_FUNCTION 函數中會自動定義,所以你大可將其忽略。 |
返回值是表示該資源類型的具備惟一性的整數標識符,即資源類型句柄。
資源(不管是不是持久化資源)的析構函數都必須具備如下的函數原型:
voidresource_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC);
參數 rsrc 指向一個 zend_rsrc_list_entry 結構:
typedefstruct_zend_rsrc_list_entry { void *ptr; inttype; intrefcount; } zend_rsrc_list_entry;
成員 void *ptr 才真正指向你的資源。
如今咱們就知道該怎麼開始了。咱們先定義一個將要註冊到 Zend 內部的資源類型 my_resource,這個類型的結構很簡單,只有兩個整數成員:
typedefstruct { intresource_link; intresource_type; } my_resource;
接着咱們再定義一下這種資源的析構函數。這個析構函數大體上是如下這個樣子:
注意:
有一個很重要的事情必需要提一下:若是你的資源是一個比較複雜的結構,好比包含有你在運行時所申請內存的指針等,那你就必須在釋放資源自己前先釋放它們!
OK。如今咱們定義了
咱們的資源是什麼樣子;
咱們資源的析構函數是什麼樣子。
那麼,咱們還須要作哪些工做呢?咱們還須要:
建立一個在整個擴展範圍內有效的全局變量用於保存資源類型句柄,這樣就能夠在每一個須要它的函數中都能訪問到它;
給咱們的資源類型定義一個名稱;
完成前面定義的資源析構函數;
最後註冊這個析構函數。
註冊完這種資源的析構函數後,要真正註冊一個資源(實例),咱們能夠使用 zend_register_resource() 函數或使用 ZEND_REGISTER_RESOURE() 宏。這兩個的定義能夠在 zend_list.h 中找到。儘管二者的參數定義都是一一對應的,但使用宏一般能夠獲得更好的前向兼容性:
intZEND_REGISTER_RESOURCE(zval *rsrc_result, void *rsrc_pointer, intrsrc_type);
rsrc_result | 這是一個初始化過 zval * 容器。 |
rsrc_pointer | 指向所保存的資源。 |
rsrc_type | 這個參數就是你在註冊函數析構函數時返回的資源類型句柄。對上面的代碼來講就是le_myresource le_myresource。 |
返回值就是表示這個資源(實例)的具備惟一性的整數。
那麼在咱們註冊這個資源(實例)時究竟發生了什麼事呢?函數會從 Zend 內部某個列表取得一個空閒空間,而後將資源指針及類型保存到這個空間。最後這個空閒空間的索引被簡單地保存在給定的 zval * 容器裏面:
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type); if (rsrc_result) { rsrc_result->value.lval = rsrc_id; rsrc_result->type = IS_RESOURCE; } return rsrc_id;
返回值 rsrc_id 就惟一性地標識了咱們新註冊獲得的那個資源。你能夠使用宏 RETURN_RESOURE 來將其返回給用戶:
RETURN_RESOURCE(rsrc_id)
注意:
若是你想馬上把這個資源返回給用戶,那你就應該把 return_value 做爲那個 zval * 容器。這也是咱們推薦的一種編程實踐。
Zend 引擎從如今就會開始跟蹤全部對這個資源的引用。一旦對這個資源的引用全都不存在了,那麼你在前面爲這個資源所註冊的析構函數就會被調用。這樣作的好處就是你不用擔憂會在你的模塊裏面引入內存泄漏-你只須要把你調用腳本中全部須要分配的內存都註冊成資源便可。這樣一來,一旦腳本認爲再也不須要它們的時候,Zend 就會找到它們而後再通知你(這就是 callback,譯註)。
如今用戶已經經過在某處傳入到你函數的參數拿到了他的資源。zval * 容器中的 value.lval 包含了你資源的標識符,而後他就能夠宏 ZEND_FETCH_RESOURCE 來獲取資源了:
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, rsrc_id, default_rsrc_id, resource_type_name, resource_type)
rsrc | 這個指針將指向你前面已經聲明過的資源。 |
rsrc_type | 這個參數用以代表你你想要把前面參數的那個指針轉換爲什麼種類型。好比 myresource * 等等。 |
rsrc_id | 這個是用戶傳進你函數的那個 zval *container 的地址。 假如給出的是 zval *z_resource ,那麼此處就應該是 &z_resource。 |
default_rsrc_id | 這個參數代表假如沒有取到資源時默認指定的資源標識符。一般爲 -1。 |
resource_type_name | 所請求的資源類型資源類型名稱。當不能找到資源時,就用這個字符串去填充系統因爲維護而拋出的錯誤信息。 |
resource_type | 這個能夠取回在註冊資源析構函數時返回的資源類型。在本例就是 le_myresource。 |
這個宏沒有返回值。這對開發人員可能會方便了點。不過仍是要注意添加 TSRM 參數和確認一下是否取回了資源。若是在接收資源時出現了問題,那它就會拋出一個警告信息而且會馬上從當前函數返回,其返回值爲 NULL。
若是想從列表強行刪除一個資源,能夠使用 zend_list_delete() 函數。固然也能夠強行增長引用計數,若是你知道你正在建立一個指向已分配內存資源的引用(好比說你可能想重用一個默認的數據庫鏈接)。對於這種狀況你能夠使用函數 zend_list_addref() 。想要查找一個已分配內存的資源,請使用 zend_list_find() 函數。關於這些操做的完整 API 請參見 zend_list.h。
做爲咱們早期所談論的一些宏的補充,還有一些宏可讓咱們很方便的建立全局變量。瞭解了它們,咱們在引入一些全局標識時就會感受很爽,不過這個習慣可能會不太好。在「表3.12 建立全局變量的宏」中描述了完成這些任務所用到的正確的宏。它們不須要申請任何 zval 容器,你只需簡單地提供一個變量名和其值便可。
表3.12 建立全局變量的宏
宏 | 說明 |
---|---|
SET_VAR_STRING(name, value) | 新建一個字符串變量。 |
SET_VAR_STRINGL(name, value, length) | 新建一個指定長度的字符串變量。這個宏要比 SET_VAR_STRING 快並且仍是二進制安全的。 |
SET_VAR_LONG(name, value) | 新建一個長整型變量。 |
SET_VAR_DOUBLE(name, value) | 新建一個雙精度變量。 |
Zend 支持建立真正的常量。訪問常量時不須要 $ 前綴,並且常量是全局有效的。好比 TRUE 和 FALSE 這兩個常量。
要想建立一個常量,你能夠使用「表3.13 建立常量的宏」中所列舉的宏來完成這項工做。全部的宏在建立常量時都必須指定一個名稱和值。
你還能夠爲常量指定一個特別的標識:
CONST_CS – 這個常量的名稱是大小寫敏感的;
CONST_PERSISTENT – 這個常量是持久化的。換句話說,當攜帶這個常量的進程關閉時這個常量在剩下的請求中還依然有效,並不會被「遺忘」。
能夠使用二進制的「或(OR)」操做來使用其中的一個或兩個標識:
// 註冊一個長整型常量 REGISTER_LONG_CONSTANT("NEW_MEANINGFUL_CONSTANT", 324, CONST_CS | CONST_PERSISTENT);
咱們提供有兩種不一樣類型的宏,分別是 REGISTER_*_CONSTANT 和 REGISTER_MAIN_*_CONSTANT。第一種類型在建立常量時只會綁定到當前模塊。一旦註冊這個模塊的常量從內存中卸載,那麼這個常量也就會隨即消逝。第二種類型建立的變量將會獨立於該模塊,始終保存在符號表中。
表3.13 建立常量的宏
宏 | 說明 |
---|---|
REGISTER_LONG_CONSTANT(name, value, flags) REGISTER_MAIN_LONG_CONSTANT(name, value, flags) |
新建一個長整型常量。 |
REGISTER_DOUBLE_CONSTANT(name, value, flags) REGISTER_MAIN_DOUBLE_CONSTANT(name, value, flags) |
新建一個雙精度型常量。 |
REGISTER_STRING_CONSTANT(name, value, flags) REGISTER_MAIN_STRING_CONSTANT(name, value, flags) |
新建一個字符串常量。給定的字符串的空間必須在Zend 內部內存。 |
REGISTER_STRINGL_CONSTANT(name, value, length, flags) REGISTER_MAIN_STRINGL_CONSTANT(name, value, length, flags) |
新建一個指定長度的字符串常量。一樣,這個給定的字符串的空間也必須在Zend 內部內存。 |
早晚你會遇到把一個 zval 容器的內容賦給另一個 zval 容器的狀況。不過可別想固然,這事提及來容易作起來可有點難度。由於 zval 容器不但包含了類型信息,並且還有對 Zend 內部數據的一些引用。好比,數組以及對象等依據其大小大都或多或少包含了一些哈希表結構。而咱們在將一個 zval 賦給另一個 zval 時,一般都沒有複製這些哈希表自己,複製的只是這些哈希表的引用而已。
爲了可以正確複製這些複雜類型的數據,咱們能夠使用「拷貝構造函數(copy constructor)」來完成這項工做。拷貝構造函數在某些爲了能夠複製複雜類型數據而支持操做符重載的語言中有着表明性的應用。若是你在這種語言中定義了一個對象,那你就可能想爲其重載(Overloading)一下「=」操做符,這個操做符一般用於將右值(操做符右邊表達式的值)賦給左值(操做符左邊表達式的值)。
「重載」就意味着將給予這個操做符另一種不一樣的含義,它一般會把這個操做符跟某個函數調用關聯起來。當這個操做符做用在一個對象上時,與之關聯的函數就將會被調用,同時該操做符的左值和右值也會做爲該函數的參數一併傳入。這樣,這個函數就能夠完成「=」操做符想要完成的事情(通常是某些額外數據的複製)。
這些「額外數據的複製」對 PHP 的 zval 容器來講也是頗有必要的。對於數組來講,「額外數據的複製」就是指另外再重建和複製那些與該數組有關的哈希表(由於當初咱們複製 zval 時複製的僅僅是這些哈希表的指針)。而對字符串來講,「額外數據的複製」就意味着咱們必須從新爲字符串值去申請空間。如此類推。
Zend Engine 會調用一個名爲 zval_copy_ctor()(在之前的 PHP 版本中這個函數叫作 pval_copy_constructor() )的函數來完成這項工做。
下面這個示例爲咱們展現了這樣一個函數:它接收一個複雜類型的參數,在對其進行必定的修改後把它做爲結果返回給 PHP:
函數的頭一部分沒什麼可說的,只是一段很日常的接收參數的代碼而已。不過在對這個參數進行了某些修改後就變得有趣起來了:先是把 parameter 容器值賦給了(預先定義好的)return_value 容器,而後爲了可以真正複製這個容器,咱們便調用了拷貝構造函數。這個拷貝構造函數可以直接處理它的參數,處理成功則返回 SUCCESS,不然返回 FAILURE。
在這個例子當中若是你忘了調用這個拷貝構造函數,那麼 parameter 和 return_value 就會分別指向同一個 Zend 內部數據,也就是說返回值 return_value 非法指向了一個數據結構。當你修改了參數 parameter 時這個函數的返回值就可能會受到影響。所以爲了建立一個獨立的拷貝,咱們必須調用這個函數。
在 Zend API 中還有一個與拷貝構造函數相對應的拷貝析構函數:zval_dtor(),它作的工做正好與拷貝構造函數相反。
關於擴展內函數到 PHP 腳本的返回值咱們前面談得比較少,這一節咱們就來詳細說一下。任何函數的返回值都是經過一個名爲 return_value 的變量傳遞的。這個變量同時也是函數中的一個參數。這個參數老是包含有一個事先申請好空間的 zval 容器,所以你能夠直接訪問其成員並對其進行修改而無需先對 return_value 執行一下 MAKE_STD_ZVAL 宏指令。
爲了可以更方便從函數中返回結果,也爲了省卻直接訪問 zval 容器內部結構的麻煩,ZEND 提供了一大套宏命令來完成相關的這些操做。這些宏命令會自動設置好類型和數值。「表3.14 從函數直接返回值的宏」和「表3.15 設置函數返回值的宏」列出了這些宏和對應的說明。
注意:
使用「表3.14 從函數直接返回值的宏」會自動攜帶結果從當前函數返回。而使用「表3.15 設置函數返回值的宏」則只是設置了一下函數返回值,並不會立刻返回。
表3.14 從函數直接返回值的宏
宏 | 說明 |
---|---|
RETURN_RESOURCE(resource) | 返回一個資源。 |
RETURN_BOOL(bool) | 返回一個布爾值。 |
RETURN_NULL() | 返回一個空值。 |
RETURN_LONG(long) | 返回一個長整數。 |
RETURN_DOUBLE(double) | 返回一個雙精度浮點數。 |
RETURN_STRING(string, duplicate) | 返回一個字符串。duplicate 表示這個字符是否使用 estrdup() 進行復制。 |
RETURN_STRINGL(string, length, duplicate) | 返回一個定長的字符串。其他跟 RETURN_STRING 相同。這個宏速度更快並且是二進制安全的。 |
RETURN_EMPTY_STRING() | 返回一個空字符串。 |
RETURN_FALSE | 返回一個布爾值假。 |
RETURN_TRUE | 返回一個布爾值真。 |
表3.15 設置函數返回值的宏
宏 | 說明 |
---|---|
RETVAL_RESOURCE(resource) | 設定返回值爲指定的一個資源。 |
RETVAL_BOOL(bool) | 設定返回值爲指定的一個布爾值。 |
RETVAL_NULL | 設定返回值爲空值 |
RETVAL_LONG(long) | 設定返回值爲指定的一個長整數。 |
RETVAL_DOUBLE(double) | 設定返回值爲指定的一個雙精度浮點數。 |
RETVAL_STRING(string, duplicate) | 設定返回值爲指定的一個字符串,duplicate 含義同 RETURN_STRING。 |
RETVAL_STRINGL(string, length, duplicate) | 設定返回值爲指定的一個定長的字符串。其他跟 RETVAL_STRING 相同。這個宏速度更快並且是二進制安全的。 |
RETVAL_EMPTY_STRING | 設定返回值爲空字符串。 |
RETVAL_FALSE | 設定返回值爲布爾值假。 |
RETVAL_TRUE | 設定返回值爲布爾值真。 |
若是須要返回的是像數組和對象這樣的複雜類型的數據,那就須要先調用 array_init() 和 object_init(),也能夠使用相應的 hash 函數直接操做 return_value。因爲這些類型主要是由一些雜七雜八的東西構成,因此對它們就沒有了相應的宏。
就像咱們在腳本中使用 print() 函數同樣,咱們也常常須要從擴展向輸出流輸出一些信息。在這方面-好比輸出警告信息、phpinfo() 中對應的信息等通常性任務-PHP 也爲咱們提供了一系列函數。這一節咱們就來詳細地討論一下它們。
zend_printf() 功能跟 printf() 差很少, 惟一不一樣的就是它是向 Zend 的輸出流提供信息。
zend_error() 用於建立一個錯誤信息。這個函數接收兩個參數:第一個是錯誤類型(見 zend_error.h),第二個是錯誤的提示消息。
zend_error(E_WARNING, "This function has been called with empty arguments");
「表3.16 Zend 預約義的錯誤信息類型」 列出了一些可能的值(在 PHP 5.0 及以上版本中又增長了一些錯誤類型,可參見 zend_error.h,譯註)。這些值也能夠用在 php.ini 裏面,這樣你的錯誤信息將會依照 php.ini 裏面的設置,根據不一樣的錯誤類型而被選擇性地記錄。
表 3.16 Zend 預約義的錯誤信息類型
錯誤類型 | 說明 |
---|---|
E_ERROR | 拋出一個錯誤,而後當即停止腳本的執行。 |
E_WARNING | 拋出一個通常性的警告。腳本會繼續執行。 |
E_NOTICE | 拋出一個通知,腳本會繼續執行。注意: 默認狀況下 php.ini 會關閉顯示這種錯誤。 |
E_CORE_ERROR | 拋出一個 PHP 內核錯誤。一般狀況下這種錯誤類型不該該被用戶本身編寫的模塊所引用。 |
E_COMPILE_ERROR | 拋出一個編譯器內部錯誤。一般狀況下這種錯誤類型不該該被用戶本身編寫的模塊所引用。 |
E_COMPILE_WARNING | 拋出一個編譯器內部警告。一般狀況下這種錯誤類型不該該被用戶本身編寫的模塊所引用。 |
示例:
在瀏覽器中顯示警告信息
在建立完一個模塊以後,你可能就會想往 phpinfo() 裏面添加一些關於你本身模塊的一些信息了(默認是隻顯示你的模塊名)。PHP 容許你用 ZEND_MINFO() 函數向 phpinfo() 裏面添加一段你本身模塊的信息。這個函數應該被放在模塊描述塊(見前文)部分,這樣在腳本調用 phpinfo() 時模塊的這個函數就會被自動調用。
若是你指定了 ZEND_MINFO 函數,phpinfo() 會自動打印一個小節,這個小節的頭部就是你的模塊名。其他的信息就須要你本身去指定一下格式並輸出了。
通常狀況下,你須要先調用一下 php_info_print_table_start(),而後再調用 php_info_print_table_header() 和 php_info_print_table_row() 這兩個標準函數來打印表格具體的行列信息。這兩個函數都以表格的列數(整數)和相應列的內容(字符串)做爲參數。最後使用 php_info_print_table_end() 來結束打印表格。「例3.13 源代碼及其在 phpinfo() 函數中的屏幕顯示」向咱們展現了某個樣例和它的屏幕顯示效果。
例3.13 源代碼及其 在 phpinfo() 函數中的屏幕顯示
你還能夠輸出一些執行時信息,像當前被執行的文件名、當前正在執行的函數名等等。當前正在執行的函數名能夠經過 get_active_function_name() 函數來獲取。這個函數沒有參數(譯註:原文便是如此,事實上是跟後面提到的 zend_get_executed_filename() 函數同樣須要提交 TSRMLS_C 宏參數,譯註),返回值爲函數名的指針。當前被執行的文件名能夠由 zend_get_executed_filename() 函數來得到。這個函數須要傳入 TSRMLS_C 宏參數來訪問執行器全局變量。這個執行器全局變量對每一個被 Zend 直接調用的函數都是有效的(由於 TSRMLS_C 是咱們前文討論過的參數宏 INTERNAL_FUNCTION_PARAMETERS 的一部分)。若是你想在其餘函數中也訪問這個執行器全局變量,那就須要如今那個函數中調用一下宏 TSRMLS_FETCH()。
最後你還能夠經過 zend_get_executed_lineno() 函數來取得當前正在執行的那一行代碼所在源文件中的行數。這個函數一樣須要訪問執行器全局變量做爲其參數。關於這些函數的應用,請參閱「例3.14 輸出執行時信息」。
例 3.14 輸出執行時信息
示例:
輸出執行時信息
啓動函數和關閉函數會在模塊的(載入時)初始化和(卸載時)反初始化時被調用,並且只調用這一次。正如咱們在本章前面(見 Zend 模塊描述塊的說明)所提到的,它們是模塊和請求啓動和關閉時所發生的事件。
模塊啓動/關閉函數會在模塊加載和卸載時被調用。請求啓動/關閉函數會在每次處理一個請求時(也就是在執行一個腳本文件時)被調用。
對於動態加載的擴展而言,模塊和請求的啓動函數與模塊和請求的關閉函數都是同時發生的(嚴格來講模塊啓動函數是先於請求啓動函數被調用的,譯註)。
能夠用某些宏來聲和明實現這些函數,詳情請參閱前面的關於「Zend 模塊聲明」的討論。
PHP 還容許你在你的模塊裏面調用一些一些用戶定義的函數,這樣在實現某些回調機制(好比在作一些數組的輪循(array walking)、搜索或設計一些簡單的事件驅動的程序時)時會很方便。
下一個參數是返回值 return_value 的指針。這個容器的空間函數會自動幫你申請,因此咱們無需手動申請,但在過後這個容器空間的銷燬釋放工做得由咱們本身(使用 zval_dtor())來作。
We are in the test function! We have 3 as type Return value: 'hello'
PHP4 重寫了對初始化文件的支持。如今你能夠直接在代碼中指定一些初始化選項,而後在運行時讀取和改變這些選項值,甚至還能夠在選項值改變時接到相關通知。
PHP_INI_ENTRY() 總共接收 4 個參數:配置項名稱、初始值、改變這些值所需的權限以及在值改變時用於接收通知的函數句柄。配置項名稱和初始值必須是一個字符串,即便它們是一個整數。
第四個參數是初始值被改變時接收通知的函數句柄。一旦某個初始值被改變,那麼相應的函數就會被調用。這個函數咱們能夠用宏 PHP_INI_MH 來定義:
改變後的新值將會以字符串的形式並經過一個名爲 new_value 變量傳遞給函數。要是再注意一下 PHP_INI_MH 的定義就會發現,咱們實際上用到了很多參數:
能夠使用「表3.17 PHP 中用以訪問初始化配置項的宏」來訪問初始化配置項:
宏 | 說明 |
---|---|
INI_INT(name) | 將配置項 name 的當前值以長整數返回。 |
INI_FLT(name) | 將配置項 name 的當前值以雙精度浮點數返回。 |
INI_STR(name) | 將配置項 name 的當前值以字符串返回。 注意:這個字符串不是複製過的字符串,而是直接指向了內部數據。若是你須要進行進一步的訪問的話,那就須要再進行復制一下。 |
INI_BOOL(name) | 將配置項 name 的當前值以布爾值返回。(返回值被定義爲 zend_bool,也就是說是一個 unsigned char)。 |
INI_ORIG_INT(name) | 將配置項 name 的初始值以長整型數返回。 |
INI_ORIG_FLT(name) | 將配置項 name 的初始值以雙精度浮點數返回。 |
INI_ORIG_STR(name) | 將配置項 name 的初始值以字符串返回。 注意:這個字符串不是複製過的字符串,而是直接指向了內部數據。若是你須要進行進一步的訪問的話,那就須要再進行復制一下。 |
INI_ORIG_BOOL(name) | 將配置項 name 的初始值以布爾值返回。(返回值被定義爲 zend_bool,也就是說是一個 unsigned char)。 |
最後,你還得把整個初始化配置項引入 PHP。這項工做能夠在模塊的起始/結束函數中使用宏 REGISTER_INI_ENTRIES() 和 UNREGISTER_INI_ENTRIES() 來搞定。
ZEND_MINIT_FUNCTION(mymodule) { REGISTER_INI_ENTRIES(); } ZEND_MSHUTDOWN_FUNCTION(mymodule) { UNREGISTER_INI_ENTRIES(); }
宏 | 說明 |
---|---|
AC_MSG_CHECKING(message) | 在執行 configure 命令時輸出「checking <message>」等信息。 |
AC_MSG_RESULT(value) | 取得 AC_MSG_CHECKING 的執行結果,通常狀況下 value 應爲 yes 或 no。 |
AC_MSG_ERROR(message) | 在執行 configure 命令時輸出一條錯誤消息 message 並停止腳本的執行。 |
AC_DEFINE(name,value,description) | 向 php_config.h 添加一行定義:#define name value // description(這對模塊的條件編譯頗有用。) |
AC_ADD_INCLUDE(path) | 添加一條編譯器的包含路徑,好比用於模塊須要爲頭文件添加搜索路徑。 |
AC_ADD_LIBRARY_WITH_PATH(libraryname,librarypath) | 指定一個庫的鏈接路徑。 |
AC_ARG_WITH(modulename,description,unconditionaltest,conditionaltest) | 這是一款比較強大的宏,用於將模塊的描述 description 添加到「configure –help」命令的輸出裏面。PHP 會檢查當前執行的 configure 腳本里面有沒有–with-<modulename> 這個選項。 若是有則執行 unconditionaltest 語句(好比 –with-myext=yes 等), 此時,選項的值會被包含在 $withval 變量裏面。不然就執行 conditionaltest 語句。 |
PHP_EXTENSION(modulename, [shared]) | 這個是配置你的擴展時 PHP 一定調用的一個宏。你能夠在模塊名後面提供第二個參數,用來代表是否將其編譯爲動態共享模塊。這會致使在編譯時爲你的源碼提供一個 COMPILE_DL_<modulename> 的定義。 |
下面(見表3.19 訪問 zval 容器的 API 宏)是一些引入到 Zend API 裏面用於訪問 zval 容器的 API 宏。
宏 | 指向 |
---|---|
Z_LVAL(zval) | (zval).value.lval |
Z_DVAL(zval) | (zval).value.dval |
Z_STRVAL(zval) | (zval).value.str.val |
Z_STRLEN(zval) | (zval).value.str.len |
Z_ARRVAL(zval) | (zval).value.ht |
Z_LVAL_P(zval) | (*zval).value.lval |
Z_DVAL_P(zval) | (*zval).value.dval |
Z_STRVAL_P(zval_p) | (*zval).value.str.val |
Z_STRLEN_P(zval_p) | (*zval).value.str.len |
Z_ARRVAL_P(zval_p) | (zval).value.ht |
Z_LVAL_PP(zval_pp) | (*zval).value.lval |
Z_DVAL_PP(zval_pp) | (**zval).value.dval |
Z_STRVAL_PP(zval_pp) | (**zval).value.str.val |
Z_STRLEN_PP(zval_pp) | (**zval).value.str.len |
Z_ARRVAL_PP(zval_pp) | (**zval).value.ht |