PHP 擴展開發的文章,我均已更新至《TIPI》php
在閱讀下面的內容以前,咱們假定你已經對 PHP 7 基本的數據結構 都有大體的瞭解了,這是下面內容閱讀的前提。
咱們分爲兩大塊:
首先實現一個自定義的文件打開、讀取、寫入、關閉的文件操做擴展;
而後分析各個操做背後的實現原理,其中某些部分的實現會和PHP 5.3 使用資源對比分析。git
首先進入到源碼目錄的ext
目錄中,添加一個文件操做的原型文件github
[shell] [root@localhost php-src-php-7.0.3]# cd ext/ [root@localhost ext]# vim tipi_file.proto
編輯爲shell
[shell] resource file_open(string filename, string mode) string file_read(resource filehandle, int size) bool file_write(resource filehandle, string buffer) bool file_close(resource filehandle)
而後生成骨架,這些前面都說過,咱們再也不詳細說vim
[shell] [root@localhost ext]# ./ext_skel --extname=tipi_file --proto=./tipi_file.proto
完整的代碼 tipi_file.c 能夠先有一個大體的瞭解,這樣後面閱讀時,思路可能會清晰不少。php7
[c] ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number)
參數 | 描述 |
---|---|
ld | 釋放該資源時調用的函數。 |
pld | 釋放用於在不一樣請求中始終存在的永久資源的函數。 |
type_name | 描述性類型名稱的字符串別名。 |
module_number | 爲引擎內部使用,已經定義好了,好比在PHP_FUNCTION宏中已定義 |
該 API 返回一個資源類型 id,該 id 應當被做爲全局變量保存在擴展裏,以便在必要的時候傳遞給其餘資源 API。數據結構
該方法表示在釋放該類型資源時都須要關閉打開的文件描述符。函數
[c] static void tipi_file_dtor(zend_resource *rsrc TSRMLS_DC){ FILE *fp = (FILE *) rsrc->ptr; fclose(fp); }
咱們發現該函數的參數類型是 zend_resource
。這是 PHP7 新增的數據結構,在 PHP 5 則是 zend_rsrc_list_entry
。細節的內容,咱們留在後面分析。測試
咱們知道在 PHP 生命週期中,當 PHP 被裝載時,PHP_MINIT_FUNCTION
(模塊啓動函數)即被引擎調用。
這使得引擎作一些例如資源類型,註冊INI變量等的一次初始化。
那麼咱們須要在這裏經過 zend_register_list_destructors_ex
在 PHP_MINIT_FUNCTION
來註冊資源類型。fetch
[c] PHP_MINIT_FUNCTION(tipi_file) { /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */ le_tipi_file = zend_register_list_destructors_ex(tipi_file_dtor, NULL, TIPI_FILE_TYPE, module_number); return SUCCESS; }
NOTICE 其中
TIPI_FILE_TYPE
在前面已經定義了,是該擴展的別名。具體請參考最該節最開始給予的完整代碼樣例。
前面是註冊了新的資源類型,而後須要註冊一個該類型的資源。
在 PHP 7 中刪除了原來的 ZEND_REGISTER_RESOURCE
宏,直接使用 zend_register_resource
函數
[c] ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
參數 | 描述 |
---|---|
rsrc_pointer | 資源數據指針 |
rsrc_type | 註冊資源類型時得到的資源類型 id |
[c] PHP_FUNCTION(file_open) { char *filename = NULL; char *mode = NULL; int argc = ZEND_NUM_ARGS(); size_t filename_len; size_t mode_len; #ifndef FAST_ZPP if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename, &filename_len, &mode, &mode_len) == FAILURE) return; #else ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(filename, filename_len) Z_PARAM_STRING(mode, mode_len) ZEND_PARSE_PARAMETERS_END(); #endif // 使用 VCWD 宏取代標準 C 文件操做函數 FILE *fp = VCWD_FOPEN(filename, mode); if (fp == NULL) { RETURN_FALSE; } RETURN_RES(zend_register_resource(fp, le_tipi_file)); }
其中 RETURN_RES
宏的做用是將返回的 zend_resource
添加到 zval
中,而後將最後的 zval
做爲返回值。也就是說該函數的返回值爲zval
指針。RETURN_RES(zend_register_resource(fp, le_tipi_file))
會將返回值的 value.res
設爲 fp
,u1.type_info
設爲 IS_RESOURCE_EX
。你們能夠根據源碼很是直觀的瞭解到,這裏不粘貼代碼詳細說明了。
在 PHP 7 中刪除了原有的 ZEND_FETCH_RESOURCE
宏,直接使用函數 zend_fetch_resource
,並且解析方式也變得簡單了不少,相比 PHP 5 要高效不少,後面咱們再經過繪圖的形式分析對比。
[c] ZEND_API void *zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type)
參數 | 描述 |
---|---|
res | 資源指針 |
resource_type_name | 該類資源的字符串別名 |
resource_type | 該類資源的類型 id |
當咱們要實現文件的讀取時,最終仍是須要使用原生的 fread
函數,因此這裏須要經過 zend_fetch_resource
將 zend_resource
解析成爲該資源包裹的原始的 FILE *
的指針。
[c] PHP_FUNCTION(file_read) { int argc = ZEND_NUM_ARGS(); int filehandle_id = -1; zend_long size; zval *filehandle = NULL; FILE *fp = NULL; char *result; size_t bytes_read; #ifndef FAST_ZPP if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle, &size) == FAILURE) return; #else ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_RESOURCE(filehandle) Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); #endif if ((fp = (FILE *)zend_fetch_resource(Z_RES_P(filehandle), TIPI_FILE_TYPE, le_tipi_file)) == NULL) { RETURN_FALSE; } result = (char *) emalloc(size+1); bytes_read = fread(result, 1, size, fp); result[bytes_read] = '\0'; RETURN_STRING(result, 0); }
這裏須要說明,腳本自動生成的擴展代碼中仍是使用 ZEND_FETCH_RESOURCE
, 是個 BUG,由於自動生成的腳本(ext/skeleton/create_stubs)還沒更新。
與之相似的文件的寫入操做,也很相似,這裏就不復制代碼了。
[c] ZEND_API int zend_list_close(zend_resource *res)
傳入須要被刪除的資源便可。該 API 看似很是簡單,實際作了不少工做,後面原理分析細說。
咱們在函數 file_close
中須要調用資源刪除 API
[c] PHP_FUNCTION(file_close) { int argc = ZEND_NUM_ARGS(); int filehandle_id = -1; zval *filehandle = NULL; #ifndef FAST_ZPP if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) return; #else ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_RESOURCE(filehandle) ZEND_PARSE_PARAMETERS_END(); #endif zend_list_close(Z_RES_P(filehandle)); RETURN_TRUE; }
關於編譯的代碼請參考本章的第一節,這裏再也不說明,咱們說下測試環節。直接用 php 腳本測試,就不一個功能一個功能寫測試樣例了,修改 tipi_file.php
文件。
[php] $fp = file_open("./CREDITS","r+"); var_dump($fp); var_dump(file_read($fp,6)); var_dump(file_write($fp,"zhoumengakng")); var_dump(file_close($fp));
而後經過命令行執行
[shell] php7 -d"extension=tipi_file.so" tipi_file.php