資源資料類型
迄今為止, 你都是工作在非常基礎的使用者空間資料類型上, 字串, 數值, TRUE/FALSE等值. 即便上一章你已經開始接觸數組了, 但也只是收集這些基礎資料類型的數組.
複雜的結構體
現實世界中, 你通常需要在更加複雜的資料集合下工作, 通常涉及到晦澀的結構體指標. 一個常見的晦澀的結構體指標樣本就是stdio的檔案描述符, 即便是在C語言中也只是一個指標.
#include <stdio.h> int main(void) { FILE *fd; fd = fopen("/home/jdoe/.plan", "r"); fclose(fd); return 0; }
stdio的檔案描述符和其他多數檔案描述符一致, 都像是一個書籤. 你擴充的調用應用僅需要在feof(), fread(), fwrite(), fclose()這樣的實現函數調用時傳遞這個值. 有時, 這個書籤必須是使用者空間代碼可訪問的; 因此, 就需要在標準的php變數或者說zval *中有表示它的方法.
這裡就需要一種新的資料類型. RESOURCE資料類型在zval *中儲存一個簡單的整型值, 使用作為登入資源的索引用來尋找. 資源條目包含了資源索引所表示的內部資料類型, 以及儲存資源資料的指標等資訊.
定義資源類型
為了使註冊的資源條目所包含的資源資訊更加明確, 需要定義資源的類型. 首先在你的sample.c中已有的函數實現下增加下面的程式碼片段
static int le_sample_descriptor; PHP_MINIT_FUNCTION(sample) { le_sample_descriptor = zend_register_list_destructors_ex( NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME, module_number); return SUCCESS; }
接下來, 滾動到你的代碼檔案末尾, 修改sample_module_entry結構體, 將NULL, /* MINIT */一行替換為下面的內容. 就像你給這個結構中增加函數列表結構時一樣, 你需要確認在這一行末尾保留一個逗號.
PHP_MINIT(sample), /* MINIT */
最後, 你需要在php_sample.h中定義PHP_SAMPLE_DESCRIPTOR_RES_NAME, 將下面的代碼放到你的其他常量定義下面:
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "File Descriptor"
PHP_MINIT_FUNCTION()代表第1章"PHP生命週期"中介紹的4個特殊的啟動和終止操作中的第一個, 關於生命週期, 在第12章"啟動, 終止以及之間的幾個關鍵點"和第13章"INI設定"中還將深入討論.
這裡需要知道的非常重要的一點是MINIT函數在你的擴充第一次載入時執行一次, 它會在所有請求到達之前被執行. 這裡我們利用這個機會註冊了解構函式, 不過它們是NULL值, 不過在通過一個唯一整型ID足以知道一個資源類型時, 你很快就會修改它.
註冊資源
現在引擎已經知道了你要儲存一些資源資料, 是時候給使用者空間的代碼一種方式去產生實際的資源了. 要做到這一點, 需要如下重新實現fopen()命令:
PHP_FUNCTION(sample_fopen) { FILE *fp; char *filename, *mode; int filename_len, mode_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &filename, &filename_len, &mode, &mode_len) == FAILURE) { RETURN_NULL(); } if (!filename_len || !mode_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid filename or mode length"); RETURN_FALSE; } fp = fopen(filename, mode); if (!fp) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open %s using mode %s", filename, mode); RETURN_FALSE; } ZEND_REGISTER_RESOURCE(return_value, fp, le_sample_descriptor); }
為了讓編譯器知道什麼是FILE *, 你需要包含stdio.h. 這可以放在sample.c中, 但是為了本章後面部分做準備, 我還是要求你放到php_sample.h中.
如果你對前面的章節付出了努力, 最後一行前面的所有內容都應該可以讀懂. 這一行代碼執行的任務是將fp指標儲存到資源的索引中, 將它和MINIT中定義的類型關聯起來, 並儲存一個可用於尋找的key到return_value中.
如果需要儲存多於一個指標的值, 或者儲存一個直接量, 則必須新分配一段記憶體用來儲存資料, 接著將指向這段記憶體的指標註冊為資源.
譯註:
1. 資源資料類型的註冊實際上是在list_destructors(Zend/zend_list.c中定義的靜態全域變數HashTable)中插入一個新構建的zend_rsrc_list_dtors_entry結構體, 這個結構體描述了這個資源類型的資訊.
2. 資源資料的註冊(ZEND_REGISTER_RESOURCE)實際上是在EG(regular_list)中使用zend_hash_next_free_element()得到下一個數值下標, 作為資源的ID, 並將傳入的資源指標(封裝為zend_rsrc_list_entry結構體)儲存到EG(regular_list)中這個下標對應的元素中.
3. EG(regular_list)的初始化是在請求初始化階段完成的, 通過跟蹤代碼, 可以看到其函數調用流程如下: php_request_startup(main/main.c) --> zend_active(Zend/zend.c) --> init_compiler(Zend/zend_compile.c) --> zend_init_rsrc_list(Zend/zend_list.c). 通過觀察zend_init_rsrc_list()函數可以看出EG(regular_list)的解構函式是list_entry_destructor(Zend/zend_list.c). 而list_entry_destructor()的邏輯是從list_destructors(上面第一步所述的靜態全域變數)中尋找要釋放的資來源物件類型的資訊, 接著按照註冊資源類型時所指定的析構器進行析構.
4. 按照上面幾點, 可以很容易理清本章前面所述內容. 首先註冊一個資源類型, 這個資源類型中包含了諸如所屬模組編號, 析構器控制代碼這樣的資訊. 接著, 在建立具體的資來源物件時, 將資來源物件和資源類型做了一個關聯.