什麼情況下需要創建php擴展
擴展作爲php的重要組成部分,爲php提供了更多的功能和特性,由於php是解釋型語言,所以在性能上不如C,所以可以將一些消耗CPU的操作通過擴展的方式代替。
比如:
- 可以用擴展實現一個內部函數,如處理數組的函數。
- 實現一個內部類。
- 實現RPC客戶端,我們在PHP中連接Mysql,Redis,Memcache等,其實就是通過這種擴展進行的。
PHP內部擴展的數據結構
php擴展通過zend_module_entry這個結構來標識,這個結構是用於保存擴展的基本信息,包括擴展名,版本號,擴展提供的函數列表等。我們每開發一個新擴展,都需要定義一個此結構的變量:
struct _zend_module_entry {
unsigned short size; // 此結構體的大小,即sizeof(struct_zend_module_entry)
unsigned int zend_api;
unsigned char zend_debug; // 是否開啓debug模式
unsigned char zts; //線程是否安全
const struct _zend_ini_entry *ini_entry; // php.ini相關
const struct _zend_module_dep *deps;
const char *name; // 擴展名稱,不得與其他擴展名相同
const struct _zend_function_entry *functions; // 擴展提供的內部函數列表
int (*module_startup_func)(INIT_FUNC_ARGS); // 模塊初始化階段回調的鉤子函數
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 模塊關閉階段回調的鉤子函數
int (*request_startup_func)(INIT_FUNC_ARGS); // 請求初始化階段回調的鉤子函數
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 請求關閉階段回調的鉤子函數
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); // 被php_info函數調用的展示擴展信息的函數
const char *version; // 擴展的版本號
size_t globals_size;
void* globals_ptr;
void (*globals_ctor)(void *global);
void (*globals_dtor)(void *global);
int (*post_deactivate_func)(void);
int module_started;
unsigned char type;
void *handle;
int module_number;
const char *build_id;
};
生成擴展的腳本工具
php提供了幾個用於簡化擴展開發的腳本工具:ext_skel,phpize,php-config,這幾個工具主要用於擴展的生成和編譯:
ext_skel
ext_skel工具位於php源碼的ext/目錄下,可以用來生成一個擴展的基本骨架,幫助開發者快速的生成一個規範的擴展結構,通過ext_skel --help可以看到,我們可以用以下命令生成一個擴展結構:
假如我們想實現一個名字叫mytest的函數擴展:
$ ./ext_skel --extname=mytest
當我們執行上述命令,會在當前目錄下生成幾個文件,這裏拿幾個重要的來說明一下:
- config.m4:autoconf語法規則的編譯配置文件,它可以指定擴展支持的configure選項以及擴展需要的額外的庫,包含哪些源文件等。
- php_mytest.h:當前擴展的c文件的頭文件,主要包含一些宏定義、全局變量和函數原型。
- tests:測試腳本所在目錄。
- mytest.c:這裏就是編寫擴展的主要代碼,擴展的功能都在這裏面實現。
- mytest.php:測試腳本,可以輸出擴展支持的函數列表以及當前擴展是否已經被編譯到PHP中。
phpize
phpize我也還沒有深入研究過,在這裏我們只要知道它是用來生成擴展的configure文件就好了。
php-config
php-config在php的安裝目錄下的/bin目錄下,按照php手冊上的解釋,php-config是一個命令行腳本,用於獲取所安裝的php配置的信息。
在編譯擴展時,如果服務器上安裝有多個PHP版本,可以在配置時用–with-php-config=[某一個php版本源碼/bin/php-config]選項來指定使用哪一個版本編譯,該選項指定了相對應的php-config腳本的路徑。
假如你的服務器上,在/usr/local/php/目錄下,分別有php-5.6.3和php-7.1.0兩個版本,你想用php7來編譯你的擴展,那麼就可以這樣做:
$ ./configure --with-php-config=/usr/local/php/php-7.1.0/bin/php-config
擴展的編寫步驟:
要編寫一個擴展,除了生成擴展骨架,完善功能代碼,測試通過之外,我們還要生成configure配置文件,編譯擴展和安裝擴展:
- 通過執行源碼ext/目錄下的ext_skel腳本生成一個擴展的基本骨架
- 修改config.m4配置:設置編譯配置參數,設置擴展的源文件等
- 實現擴展的功能代碼:這一步其實就是c代碼的實現
- 調用phpize生成configure:擴展編寫完成後就可以執行phpize生成configure配置文件
- 編譯和安裝:分別執行./configure,make && make install
- 將編譯好的擴展,一般是.so後綴的擴展路徑,添加到php.ini中
實現一個自己的php擴展
現在我們來實現一個類似於PHP內置函數中操作文件的系列函數:
- fopen
- fread
- fwrite
- fclose
實現fopen功能,這裏我自己的函數名字叫:myfile_fopen
PHP_FUNCTION(myfile_fopen) {
zend_string *filename;
zend_string *mode;
FILE *fp;
my_resource *myresource;
zend_resource *var_resource;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"SS",&filename,&mode) == FAILURE) {
RETURN_FALSE;
}
fp = fopen(ZSTR_VAL(filename),ZSTR_VAL(mode));
if(fp == NULL) {
RETURN_FALSE;
}
myresource = emalloc(sizeof(my_resource));
myresource->file_name = filename;
myresource->mode = mode;
myresource->fp = fp;
var_resource = zend_register_resource(myresource,le_myfile);
RETURN_RES(var_resource);
}
實現fread功能,這裏我自己的函數名字叫:myfile_fread
PHP_FUNCTION(myfile_fread) {
long size;
char *result;
size_t bytes_read;
zval *res;
my_resource *myresource;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"rl",&res,&size) == FAILURE) {
RETURN_FALSE;
}
myresource = Z_RES_VAL_P(res);
result = (char *) emalloc(size+1);
bytes_read = fread(result,sizeof(char),size,myresource->fp);
result[bytes_read] = '\0';
RETURN_STRING(result);
}
實現fwrite功能,這裏我自己的函數名字叫:myfile_fwrite
PHP_FUNCTION(myfile_fwrite) {
zval *res;
zend_string *content;
my_resource *myresource;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"rS",&res,&content) == FAILURE) {
RETURN_FALSE;
}
myresource = Z_RES_VAL_P(res);
if(fwrite(ZSTR_VAL(content),sizeof(char),content->len,myresource->fp) != content->len) {
RETURN_FALSE;
}
RETURN_TRUE;
}
實現fclose功能,這裏我自己的函數名字叫:myfile_fclose
PHP_FUNCTION(myfile_fclose) {
zval *res;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"r",&res) == FAILURE) {
return;
}
if(zend_list_delete(Z_RES_VAL_P(res)) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
這裏是擴展的函數列表:
const zend_function_entry myfile_functions[] = {
PHP_FE(confirm_myfile_compiled, NULL) /* For testing, remove later. */
PHP_FE(myfile_fopen,NULL)
PHP_FE(myfile_fread,NULL)
PHP_FE(myfile_fwrite,NULL)
PHP_FE(myfile_fclose,NULL)
PHP_FE_END /* Must be the last line in myfile_functions[] */
};
這裏是該擴展的結構信息:
zend_module_entry myfile_module_entry = {
STANDARD_MODULE_HEADER,
"myfile",
myfile_functions,
PHP_MINIT(myfile),
PHP_MSHUTDOWN(myfile),
PHP_RINIT(myfile), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(myfile), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(myfile),
PHP_MYFILE_VERSION,
STANDARD_MODULE_PROPERTIES
};
這裏只是部分功能代碼,完整的代碼請參照我的github地址:https://github.com/yangclsir/myfile