什么情况下需要创建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