PHP 擴充套件開發的文章,我均已更新至《TIPI》
在閱讀下面的內容之前,我們假定你已經對 PHP 7 基本的資料結構 都有大致的瞭解了,這是下面內容閱讀的前提。
我們分為兩大塊:
首先實現一個自定義的檔案開啟、讀取、寫入、關閉的檔案操作擴充套件;
然後分析各個操作背後的實現原理,其中某些部分的實現會和PHP 5.3 使用資源對比分析。
通過原型生成擴充套件骨架
首先進入到原始碼目錄的ext
目錄中,新增一個檔案操作的原型檔案
[shell]
[root@localhost php-src-php-7.0.3]# cd ext/
[root@localhost ext]# vim tipi_file.proto
編輯為
[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)
然後生成骨架,這些前面都說過,我們不再詳細說
[shell]
[root@localhost ext]# ./ext_skel --extname=tipi_file --proto=./tipi_file.proto
完整的程式碼 tipi_file.c 可以先有一個大致的瞭解,這樣後面閱讀時,思路可能會清晰很多。
註冊資源型別
認識註冊資源型別API
[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_MINIT_FUNCTION 中註冊資源型別
我們知道在 PHP 生命週期中,當 PHP 被裝載時,PHP_MINIT_FUNCTION
(模組啟動函式)即被引擎呼叫。
這使得引擎做一些例如資源型別,註冊INI變數等的一次初始化。
那麼我們需要在這裡通過 zend_register_list_destructors_ex
在 PHP_MINIT_FUNCTION
來註冊資源型別。
[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
在前面已經定義了,是該擴充套件的別名。具體請參考最該節最開始給予的完整程式碼樣例。
註冊資源
前面是註冊了新的資源型別,然後需要註冊一個該型別的資源。
註冊資源 API
在 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 |
在自定義的 file_open 函式中實現資源的註冊
[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
。大家可以根據原始碼非常直觀的瞭解到,這裡不貼上程式碼詳細說明了。
使用資源
使用資源API
在 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] = ` `;
RETURN_STRING(result, 0);
}
這裡需要說明,指令碼自動生成的擴充套件程式碼中還是使用 ZEND_FETCH_RESOURCE
, 是個 BUG,因為自動生成的指令碼(ext/skeleton/create_stubs)還沒更新。
與之類似的檔案的寫入操作,也很類似,這裡就不復制程式碼了。
資源的刪除
資源刪除 API
[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