创建一个自己的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

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