創建一個自己的PHP擴展

什麼情況下需要創建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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章