使用PHP扩展的原因:
①如果应用注重效率,使用非常复杂的算法,推荐使用PHP扩展。
②有些系统调用PHP不能直接访问(如Linux的fork()函数创建进程),需要编写成PHP扩展。
③应用不想暴露关键代码,可以创建扩展使用。
准备工作
一:了解PHP源码目录
网上下载下来PHP 5.4版本源代码,目录结构如下:
php-5.4.30
|____build --和编译有关的目录,里面包括wk,awk和sh脚本用于编译处理,其中m4文件是linux下编译程序自动生成的文件,可以使用buildconf命令操作具体的配置文件。
|____ext --扩展库代码,例如Mysql,gd,zlib,xml,iconv 等我们熟悉的扩展库,ext_skel是linux下扩展生成脚本,windows下使用ext_skel_win32.php。
|____main --主目录,包含PHP的主要宏定义文件,php.h包含绝大部分PHP宏及PHP API定义。
|____netware --网络目录,只有sendmail_nw.h和start.c,分别定义SOCK通信所需要的头文件和具体实现。
|____pear --扩展包目录,PHP Extension and Application Repository。
|____sapi --各种服务器的接口调用,如Apache,IIS等。
|____scripts --linux下的脚本目录。
|____tests --测试脚本目录,主要是phpt脚本,由--TEST--,--POST--,--FILE--,--EXPECT--组成,需要初始化可添加--INI--部分。
|____TSRM --线程安全资源管理器,Thread Safe Resource Manager保证在单线程和多线程模型下的线程安全和代码一致性。
|____win32 --Windows下编译PHP 有关的脚本。
|____Zend --包含Zend引擎的所有文件,包括PHP的生命周期,内存管理,变量定义和赋值以及函数宏定义等等。
二:自动构建工具
本篇针对Linux环境下创建PHP扩展,使用扩展自动构建工具为ext_skel,Windows下使用ext_skel_win32.php,构建方式略有不同,其余开发无差别。
构建PHP扩展的步骤如下(不唯一):
①cd php_src/ext
②./ext_skel --extname=XXX
此时当前目录下会生成一个名为XXX的文件夹
③cd XXX/
④vim config.m4
会有这段文字:
dnl If your extension references something external, use with:
dnl PHP_ARG_WITH(say, for say support,
dnl Make sure that the comment is aligned:
dnl [ --with-say Include say support])
dnl Otherwise use enable:
dnl PHP_ARG_ENABLE(say, whether to enable say support,
dnl Make sure that the comment is aligned:
dnl [ --enable-say Enable say support])
其中,dnl 是注释符号。上面的代码说,如果你所编写的扩展如果依赖其它的扩展或者lib库,需要去掉PHP_ARG_WITH相关代码的注释。否则,去掉 PHP_ARG_ENABLE 相关代码段的注释。本篇的扩展不依赖其他扩展,故修改为:
dnl If your extension references something external, use with:
dnl PHP_ARG_WITH(say, for say support,
dnl Make sure that the comment is aligned:
dnl [ --with-say Include say support])
dnl Otherwise use enable:
PHP_ARG_ENABLE(say, whether to enable say support,
Make sure that the comment is aligned:
[ --enable-XXX Enable say support])
⑤在XXX.c中具体实现
⑥编译安装
phpize
./configure --with-php-config=php_path/bin/php-config
make && make install
⑦修改php.ini文件
增加
[XXX]
extension = XXX.so
三:了解PHP生命周期
任何一个PHP实例都会经过Module init、Request init、Request shutdown和Module shutdown四个过程。
1.Module init
在所有请求到达前发生,例如启动Apache服务器,PHP解释器随之启动,相关的各个模块(Redis、Mysql等)的MINIT方法被调用。仅被调用一次。创建XXX扩展后,相应的XXX.c文件中将自动生成该方法:
PHP_MINIT_FUNCTION(XXX) {
return SUCCESS;
}
2.Request init
每个请求达到时都被触发。SAPI层将控制权交由PHP层,PHP初始化本次请求执行脚本所需的环境变量,函数列表等,调用所有模块的RINIT函数。XXX.c中对应函数如下:
PHP_RINIT_FUNCTION(XXX){
return SUCCESS;
}
3.Request shutdown
每个请求结束,PHP就会自动清理程序,顺序调用各个模块的RSHUTDOWN方法,清除程序运行期间的符号表。典型的RSHUTDOWN方法如:
PHP_RSHUTDOWN_FUNCTION(XXX){
return SUCCESS;
}
4.Module shutdown
所有请求处理完毕后,SAPI也关闭了(即服务器关闭),PHP调用各个模块的MSHUTDOWN方法释放内存。
PHP_MSHUTDOWN_FUNCTION(XXX){
return SUCCESS;
}
PHP的生命周期常见如下几种
①单进程SAPI生命周期
②多进程SAPI生命周期
③多线程SAPI声明周期
这与PHP的运行模式有很大关系,常见的运行模式有CLI、CGI、FastCGI和mod_php。
①CLI模式——单进程SAPI生命周期
所谓CLI模式,即在终端界面通过php+文件名的方式执行PHP文件
输入命令后,依次调用MINIT,RINIT,RSHUTDOWN,MSHUTDOWN即完成生命周期,一次只处理一个请求。
②CGI模式——单进程SAPI生命周期
和CLI模式一样,请求到达时,为每个请求fork一个进程,一个进程只对一个请求做出响应,请求结束后,进程也就结束了。
与CLI模式不同的是,CGI可以看作是规定了Web Server与PHP的交流规则,相当于执行response = exec("php -f xxx.php -url=xxx -cookie=xxx -xxx=xxx")。
③FastCGI模式——多进程SAPI生命周期
CGI模式存在明显缺点,每个进程处理一个请求及结束,新请求过来需要重新加载php.ini,调用MINIT等函数。FastCGI相当于可以执行多个请求的CGI,处理完一个请求后进程不结束,等待下一个请求到来。
服务启动时,FastCGI先启动多个子进程等待处理请求,避免了CGI模式请求到来时fork()进程(即fork-and-execute),提高效率。
④mod_php模式——多进程SAPI生命周期
该模式将PHP嵌入到Apache中,相当于给Apache增加了解析PHP的功能。PHP随服务器的启动而启动,两者之间存在从属关系。
证明:
CGI模式下,修改php.ini无需重启服务器,每个请求结束后,进程自动结束,新请求到来时会重新读取php.ini文件创建新进程。
mod_php下,进程是启动即创建,只有结束现有进程,重新启动服务器读取PHP配置创建新进程,修改才有效。
多线程SAPI模式
多线程模式和多进程模式的某个进程类似,在整个生命周期中会并行重复着请求开始,请求结束的环节。
只有一个服务器进程运行,但同时运行多个线程,优点是节省资源开销,MINIT和MSHUTDOWN只需在Web Server启动和结束时执行一次。由于线程的特质,使得各个请求之间共享数据成为可能。
四:PHP内核中的变量
PHP变量的弱类型实现在之前的文章中讲到,可以参读:https://www.jianshu.com/p/ef0c91be06a0 PHP的实现方式即PHP变量在内核中的存储。
PHP提供了一系列内核变量的访问宏。推荐使用它们设置和访问PHP的变量类型和值。
变量类型:
Z_TYPE(zval) 可以获取和设置变量类型
Z_TYPE(zval)函数返回变量的类型,PHP变量类型有:
IS_NULL(空类型),IS_LONG(整型),IS_DOUBLE(浮点型),IS_STRING(字符串),
IS_ARRAY(数组类型),IS_OBJECT(对象类型),IS_BOOL(布尔类型),IS_RESOURCE(资源类型)
可以通过
Z_TYPE(zval) = IS_STRING的方式直接设置变量类型
变量值对应的访问宏:
整数类型 Z_LVAL(zval) 对应zval的实体;Z_VAL_P(&zval) 对应结构体的指针;Z_VAL_PP(&&zval) 对应结构体的二级指针
浮点数类型 Z_DVAL(zval) Z_DVAL_P(&zval) Z_DVAL_PP(&&zval)
布尔类型 Z_BVAL(zval) Z_BVAL_P(&zval) Z_BVAL_PP(&&zval)
字符串类型
获取值:Z_STRVAL(zval) Z_STRVAL_P(&zval) Z_STRVAL_PP(&&zval)
获取长度:Z_STRLEN(zval) Z_STRLEN_P(&zval) Z_STRLEN_PP(&&zval)
数组类型 Z_ARRVAL(zval) Z_ARRVAL_P(&zval) Z_ARRVAL_PP(&&zval)
资源类型 Z_RESVAL(zval) Z_RESVAL_P(&zval) Z_RESVAL_PP(&&zval)
五:了解Zend API
1.Zend引擎
Zend引擎就是脚本语言引擎(解释器+虚拟机),负责解析、翻译和执行PHP脚本。其工作流程大致如下:
①Zend Engine Compiler编译PHP脚本为Opcode
②Opcode由Zend Engine Executor解析执行,期间Zend Engine Executor负责调用使用到的PHP extension
2.Zend内存管理
使用C语言开发PHP扩展,需要注意内存管理。忘记释放内存将造成内存泄漏,释放多次则产生系统错误。Zend引擎提供了一些内存管理的接口,使用这些接口申请内存交由Zend管理。
常见接口:
emalloc(size_t size) 申请size大小的内存
efree(void *ptr) 释放ptr指向的内存块
estrdup(char *str) 申请str大小的内存,并将str内容复制进去
estrndup(char *str, int slen) 同上,但指定长度复制
ecalloc(size_t numOfElem, size_t sizeOfElem) 复制numOfElem个sizeOfElem大小的内存块
erealloc(void *ptr, size_t nsize) ptr指向内存块的大小扩大到nsize
内存管理申请的所有内存,将在脚本执行完毕和处理请求终止时被释放。
3.PHP扩展架构
使用准备工作(二)中命令生成基本架构后,生成的对应目录中会有两个文件,php_XXX.h和XXX.c,其中php_XXX.h文件用于声明扩展的一些基本信息和实现的函数,注意,只是声明。具体的实现在XXX.c中。
php_XXX.h大致结构如下:
#ifndef PHP_XXX_H
#define PHP_XXX_H
extern zend_module_entry php_list_module_entry;
#define phpext_php_list_ptr &php_list_module_entry
#define PHP_XXX_VERSION "0.1.0"
#ifdef PHP_WIN32
# define PHP_XXX_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
# define PHP_XXX_API __attribute__ ((visibility("default")))
#else
# define PHP_XXX_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif
PHP_MINIT_FUNCTION(XXX);
PHP_MSHUTDOWN_FUNCTION(XXX);
PHP_RINIT_FUNCTION(XXX);
PHP_RSHUTDOWN_FUNCTION(XXX);
PHP_MINFO_FUNCTION(XXX);
PHP_FUNCTION(confirm_XXX_compiled);
#ifdef ZTS
#define PHP_LIST_G(v) TSRMG(php_list_globals_id, zend_php_list_globals *, v)
#else
#define PHP_LIST_G(v) (php_list_globals.v)
#endif
#endif
大致信息有版本号,MINIT、RINIT、RSHUTDOWN、MSHUTDOWN函数等,如果声明自定义函数,可以在之后以PHP_FUNCTION(XXX);的方式声明函数,并在XXX.c中具体实现。
XXX.c内容如下:
---------------------------------------头文件--------------------------------------
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_XXX.h"
static int le_XXX;
---------------------------------------Zend函数快--------------------------------------
const zend_function_entry XXX_functions[] = {
PHP_FE(confirm_php_list_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in php_list_functions[] */
};
---------------------------------------Zend模块--------------------------------------
zend_module_entry XXX_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"XXX",
XXX_functions,
PHP_MINIT(XXX),
PHP_MSHUTDOWN(XXX),
PHP_RINIT(XXX), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(XXX), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(XXX),
#if ZEND_MODULE_API_NO >= 20010901
PHP_XXX_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
---------------------------------------实现get_module函数--------------------------------------
#ifdef COMPILE_DL_XXX
ZEND_GET_MODULE(XXX)
#endif
---------------------------------------生命周期函数--------------------------------------
PHP_MINIT_FUNCTION(XXX)
{
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(XXX)
{
return SUCCESS;
}
PHP_RINIT_FUNCTION(XXX)
{
return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(XXX)
{
return SUCCESS;
}
---------------------------------------导出函数--------------------------------------
PHP_FUNCTION(confirm_XXX_compiled)
{
char *arg = NULL;
int arg_len, len;
char *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
return;
}
len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "php_list", arg);
RETURN_STRINGL(strg, len, 0);
}
---------------------------------------负责扩展info显示--------------------------------------
PHP_MINFO_FUNCTION(XXX)
{
php_info_print_table_start();
php_info_print_table_header(2, "XXX support", "enabled");
php_info_print_table_end();
}
①扩展头文件
所有扩展务必包含的头文件有且只有一个——php.h,以使用PHP定义的各种宏和API。如果在php_XXX.h中声明了扩展相关的宏和函数,需要将其引入。
②导出函数
先说导出函数,方便理解Zend函数块。PHP能够调用扩展中的类和方法都是通过导出函数实现。导出函数就是按照PHP内核要求编写的函数,形式如下:
void zif_extfunction(){ //extfunction即为扩展中实现的函数
int ht; //函数参数的个数
zval *return_value; //保存函数的返回值
zval *this_ptr; //指向函数所在对象
int return_value_used; //函数返回值脚本是否使用,0——不使用;1——使用
zend_executor_globals *executor_globals; //指向Zend引擎的全局设置
}
有如上定义,PHP脚本中即可使用扩展函数
由于导出函数格式固定,Zend引擎通过PHP_FUNCTION()宏声明
PHP_FUNCTION(extfunction);
即可产生之前代码块中的导出函数结构体。
③Zend函数块
作用是将编写的函数引入Zend引擎,通过zend_function_entry结构体引入。zend_function_entry结构体声明如下:
typedef struct _zend_function_entry{
char *fname; //指定在PHP脚本里定义的函数名
void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //指向导出函数的句柄
unsigned char *func_arg_types; //标示一些参数是否强制按引用方式传递,通常设为NULL
} zend_function_entry;
Zend引擎通过zend_function_entry数组将导出函数引入内部。方式:
zend_function_entry XXX_functions[] = {
PHP_FE(confirm_php_list_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in php_list_functions[] */
};
PHP_FE宏会把zend_function_entry结构补充完整。
PHP_FE(extfunction); ===> ("extfunction", zif_extfunction, NULL);
PHP_FE_END是告知Zend引擎Zend函数块到此为止,有的版本可以使用{NULL, NULL, NULL}的方式。但推荐使用本文方式以兼容。
④Zend模块声明
Zend模块包含所有需要向Zend引擎提供的扩展模块信息,底层由zend_module_entry结构体大体实现
typedef struct _zend_module_entry{
unsigned short size; ---|
unsigned int zend_api; |
unsigned char zend_debug; |--> 通常用STANDARD_MODULE_HEADER填充
unsigned char zts; ---|
char *name; //模块名
zend_function_entry *functions; //函数声明
int (*module_start_func)(INIT_FUNC_ARGS); //MINIT函数
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); //MSHUTDOWN函数
int (*request_start_func)(INIT_FUNC_ARGS); //RINIT函数
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); //RSHUTDOWN函数
char *version; //版本号
... //其余信息不做讨论
} zend_module_entry;
对比生成代码中的模块声明和①②③中所讲,Zend通过模块声明将所有信息读取并加入到引擎中。
⑤get_module函数
当扩展被加载时,调用get_module函数,该函数返回一个指向扩展模块声明的zend_module_entry指针。是PHP内核与扩展通信的渠道。
get_module函数被条件宏包围,故有些情况下不会执行get_module方法,当扩展被编译为PHP内建模块时get_module方法不被实现。
⑥实现导出函数
在php_XXX.h中声明的函数在XXX.c中具体实现,实现方式如自动生成的confirm_XXX_compiled导出函数形式一致
PHP_FUNCTION(extfunction){
...具体实现...
}
在函数中获取参数和返回结果,后续讲解。
⑦模块信息函数
PHP通过phpinfo查看PHP及其扩展信息,PHP_MINFO_FUNCTION负责实现。生成代码函数体是最基本的模块信息,可自行设置显示内容。
4.导出函数实现须知
这部分主要讲解函数具体实现过程中对参数和变量的处理。
(1)获取参数个数
通过ZEND_NUM_ARGS宏获取参数个数,这个宏实际上是获取zif_extfunction的ht字段,定义在Zend/zned_API.h下
#define ZEND_NUM_ARGS() (ht)
(2)取得参数实体
Zend引擎提供获取参数实体的API,声明如下:
int zend_parse_parameters(int num_args TSRMLS_CC, char *type_spec)
num_args:传入的参数个数
type_spec:参数的类型,每种类型对应一个字符,当num_args>1时,接收参数通过字符串依次指明类型接收。
该函数成功将返回SUCCESS,失败返回FAILURE。
可以接受的参数类型如下:
普通:
l : 长整型
d : 双精度浮点类型
s : 字符串类型及其长度(需要两个变量保存!!!)
b : 布尔类型
r : 资源类型,保存在zva *l中
a : 数组,保存在zval *中
o : 对象(任何类型),保存在zval *中
O : 对象(class entry指定类型),保存在zval *中
z : zval *
特殊:
| : 当前及之后的参数为可选参数,有传即获取,否则设为默认值
/ : 当前及之后的参数将以SEPARATE_IF_NOT_REF的方式进行拷贝,除非是引用
! : 当前及之后的参数允许为NULL,仅用在r,a,o,O,z类型时
例:获取一个字符串和一个布尔型参数
char *str;
int strlen;
zend_bool b;
if(zend_parse_paramsters(ZEND_NUM_ARGS() TSRMLS_CC, "sb", &str, &strlen, &b) == FAILURE){ //字符串需要接收内容和长度
return ;
}
例:获取一个数组和一个可选的长整型参数
zval *arr;
long l;
if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &arr, &l) == FAILURE){
return ;
}
例:获取一个对象或NULL
zval *obj;
if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &obj) == FAILURE){
return ;
}
(3)获取可变参数
开发过程中会遇到方法可接受可变参数的情况,不指定类型的情况下接收参数的方式:
int num_arg = ZEND_NUM_ARGS();
zval **parameters[num_args];
if(zend_get_parameters_array_ex(num_arg, parameters) == FAILURE){
return ;
}
(4)参数类型转换
(3)中PHP可以接受任意类型的参数,可能会导致在具体实现过程中出问题,因此Zend提供了一系列参数类型转换的API。
(5)处理通过引用传递的参数
"z"代表的zval类型的传参即为引用传递。PHP规定修改非引用传递的参数值不会影响原来变量的值,但PHP内核采用引用传递方式传参。PHP内核使用"zval分离"的方式避免这一问题。"zval分离"即写时复制,修改非引用类型的参数时,先复制一份新值,然后将引用指向新值,修改参数时不会影响原值。
判断参数是否为引用,通过PZVAL_IS_REF(zval *),其定义为:
#define PZVAL_IS_REF(z) ((z)->is_ref)
使用宏SEPARATE_ZVAL(zval **)实现zval分离。
(6)扩展中创建变量
PHP扩展中创建变量需要以下三步:
①创建一个zval容器
②对zval容器进行填充
③引入到Zend引擎内部符号表中
//创建zval容器
zval *new_var;
//初始化和填充
MAKE_STD_ZVAL(new_var);
//引入符号表
ZEND_SET_SYMBOL(EG(active_symbol_table), "new_var", new_var);
MAKE_STD_ZVAL()宏作用是通过ALLOC_ZVAL()申请一个zval空间,之后通过INIT_ZVAL()进行初始化。
#define MAKE_STD_ZVAL(zv) \
ALLOC_ZVAL(zv); \
INIT_ZVAL(zv);
INIT_ZVAL()宏定义如下:
#DEFINE INIT_ZVAL(z) \
(z) -> refcount = 1; \
(z) -> is_ref = 0;
MAKE_STD_ZVAL()只是为变量分配了内存,设置了refcount和is_ref两个属性。
ZEND_SET_SYMBOL()宏将变量引入到符号表中,引入时先检查变量是否已经存在于表中,如果已经存在,销毁原有的zval并替换。
如果创建的是全局变量,前两步不变,只对引入操作做调整。局部变量引入active_symbol_table中,全局变量引入symbol_table中,通过
ZEND_SET_SYMBOL(&EG(symbol_table), "new_var", new_var);
注意:active_symbol_table是个指针,symbol_table不是指针,需要增加&取地址。
···
扩展中:
PHP_FUNCTION(extfunction){
zval *new_var;
ZEND_STD_ZVAL(new_var);
ZVAL_LONG(new_var, 10);
ZEND_SET_SYMBOL(&EG(symbol_table), "new_var", new_var);
}
PHP脚本中:
extfunction();
var_dump($new_var);
?>
结果输出:10
···
(7)变量赋值
①长整型(整型)赋值
PHP中所有整型都是保存在zval的value字段中,整数保存在value联合体的lval字段中,type为IS_LONG,赋值通过宏操作进行:
ZVAL_LONG(zval, 10);
②双精度浮点数类型赋值
浮点数保存在value的dval中,type对应IS_DOUBLE,通过宏操作
ZVAL_DOUBLE(zval, 3.14);
③字符串类型
value联合体的str结构体保存字符串值,val保存字符串,len保存长度,type为IS_STRING。
char *str = "hello world";
ZVAL_STRING(zval, str, 1); //结尾参数表示字符串是否需要被复制。
④布尔类型
值存放在value.lval中,TRUE——1;FALSE——0,type对应IS_BOOL。
赋值为真:ZVAL_BOOL(zval, 1);
赋值为假:ZVAL_BOOL(zval, 0);
⑤数组类型变量
PHP数组基于HashTable实现,变量赋值为数组类型时先要创建一个HashTable,保存在value的ht字段中。Zend提供array_init()实现赋值。
array_init(zval);
同时Zend提供了一套完整的关联数组、索引数组API用于添加元素,这里不一一列举。
⑥对象类型变量
对象和数组类似,PHP中对象可以转换成数组,但数组无法转换成对象,会丢失方法。Zend通过object_init()函数初始化一个对象。
if(object_init(zval) != SUCCESS){
RETURN_NULL();
}
Zend也提供了对象设置属性所需的API,和数组设置元素类似,用到时候找即可。
⑦资源类型
严格而言,资源不是数据类型,而是一个可以维护任何数据类型的抽象,类似C语言的指针。所有资源都保存在一个Zend内部的资源列表中,每份资源都有一个指向实际数据的指针。
为了及时回收无用的资源,Zend引擎会自动回收引用数为0的资源的析构函数,析构函数需要在扩展中自己定义。
Zend使用统一的zend_register_list_destructors_ex()为资源注册析构函数,该函数返回一个句柄,将资源与析构函数相关联。定义如下:
ZEND_ZPI int zend_register_list_destructors_ex(rsrc_dtor_func_t ld,
rsrc_dtor_func_t pld, char *type_name, int module_number);
参数描述:
ld : 普通资源的析构函数
pld : 持久化资源的析构函数
type_name : 为资源类型起的名字,如:fopen()创建的资源名称为stream
module_number : PHP_MINIT_FUNCTION函数会定义,可忽略
两种析构函数至少提供一个,为空可用NULL指定。
资源的析构函数必须如下定义:(resource_destruction_handler)函数名随意。
void resource_destruction_handler(zend_rsrc_entry *rsrc TSRMLS_DC){
-----------具体实现代码------------
}
其中,rsrc是指向zend_rsrc_entry的指针,结构体结构为:
typedef struct _zend_rsrc_entry{
void *ptr; //资源的实际地址,析构时释放
int type;
int refcount;
} zend_rsrc_entry ;
通过zend_register_list_destructors_ex()函数返回的资源句柄,通过一个全局变量保存,ext_skel生成的扩展架构中,自动生成了一个'le_'为前缀的int型变量,zend_register_list_destructors_ex()在MINIT函数中使用并完成注册。如实现链表的析构:
---------------------------------phplist扩展-----------------------------------
static le_phplist; //架构自动生成,保存资源句柄
//定义链表节点
struct ListNode{
struct ListNode *next;
void *data;
}
//析构函数具体实现
void phplist_destruction_handler(zend_rsrc_entry *rsrc TSRMLS_DC){
ListNode *pre, *next;
pre = (ListNode *)rsrc->ptr;
while(pre){
next = pre -> next;
free(pre);
pre = next;
}
}
//MINIT中注册析构函数
PHP_MINIT_FUNCTION(phplist){
//完成注册
le_phplist = zend_register_list_destructors_ex(phplist_destruction_handler,
NULL, "php_list", module_number);
return SUCCESS;
}
注册完析构函数,需要把资源和句柄关联起来,Zend提供zend_register_resource()函数或者ZEND_REGISTER_RESOURCE()宏完成这一操作。
int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type);
参数解释:
rsrc_result : 存储zend_register_resource返回的结果
rsrc_pointer : 指向保存的资源
rsrc_type : 资源类型
该函数返回int型结果,该结果为资源的id。函数定义源码:
int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type){
int rsrc_id;
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type); //该函数将资源加入资源列表,并返回资源在列表中的位置(即id)
if(rsrc_result){
rsrc_result -> value.lval = rsrc_id;
rsrc_result -> type = IS_RESOURCE;
}
return rsrc_id;
}
用户根据资源的id冲资源列表中获取资源,Zend定义了ZEND_FETCH_RESOURCE()宏获取指定的资源。
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, rsrc_id, default_rsrc_id, resource_type_name, resource_type);
其中
rsrc : 保存返回的资源
rsrc_type : 表明想要的资源类型,如 ListNode *等
rsrc_id : 用户通过PHP脚本传来的资源id
default_rsrc_id : 没有获取到资源时的标识符,一般用-1指定
resource_type_name : 请求资源类的名称,用于找不到时抛出错误信息使用
resource_type : 注册析构函数时的句柄,即le_phplist
例如获取用户指定的list
zval *lrc;
ListNode *list;
//获取用户参数
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
RETURN_FALSE;
}
//获取对应资源
ZEND_FETCH_RESOURCE(list, ListNode *, &lrc, -1, "php list", le_phplist);
此时list即为所要获取的资源。
资源用完需要析构,当引用数为0时,Zend对资源进行回收,很多扩展对资源有相应的析构函数,比如mysql_connect()的mysql_close(),fopen()对应fclose()。PHP的unset()也可以直接释放一个资源。
如果想显示的定义函数释放资源,在自定义函数中调用zend_list_delete()函数即可
ZEND_API int zend_list_delete(int id TSRMLS_DC);
该函数的作用是根据id将资源的引用数-1,然后判断引用数是否大于0,是则触发析构函数清除资源。
(8)错误输出API
Zend推荐使用zend_error()函数输出错误信息,该函数定义如下:
ZEND_API void zend_error(int type, char *format, ...)
参数:
type : PHP的6钟错误信息类型
①E_ERROR:抛出一个错误,脚本将停止运行
②E_WARNING : 抛出警告,脚本继续执行
③E_NOTICE : 抛出通知,脚本继续执行,一般情况下php.ini设置不显示
④E_CORE_ERROR : 抛出PHP内核错误
⑤E_COMPILE_ERROR : 抛出编译器内部错误
⑥E_COMPILE_WARNING : 抛出编译器警告
注意:后三种错误不应由自定义扩展模块抛出!!!
format : 错误输出格式
(9)运行时信息函数
执行PHP脚本出错时,经常会有相关的运行信息,指出哪个文件,哪个函数,具体哪行有执行错误,Zend引擎有相关的实现接口。
查看当前执行的函数名
get_active_function_name(TSRMLS_C);
查看当前执行的文件名
zend_get_executed_filename(TSRMLS_C);
查看所在行
zend_get_executed_lineno(TSRMLS_C);
三个函数都需要以TSRMLS_C为参数,作为访问执行器(Executor)全局变量。TSRM_C是TSRM存储器,与线程安全相关,之后专门写篇博客讲讲。
(10)扩展调用脚本中用户自定义函数
这种情况比较少,但Zend功能全面,支持这类操作。
在扩展中使用用户自定义函数,通过call_user_function_ex()函数实现,函数原型:
int call_user_function_ex(HashTable *function_table, //要访问的函数表指针
zval **object_pp, //调用方法的对象,没有设为NULL
zval *function_name, //函数名
zval **retval_ptr_ptr, //保存返回值的指针
zend_uint param_count, //参数个数
zval **params[], //参数
int no_separation, //是否禁止zcal分离操作
HashTable symbol_table //符号表,一般设为NULL
TSRMLS_DC
);
其中no_separation为1会禁止zval分离,节省内存,但任何参数分离将导致操作失败,通常设为0。
脚本中定义用户函数
function userfunc(){
return "call user function success";
}
-------------------------调用扩展方法----------------------------
$ret = call_user_function_in_ext();
var_dump($ret);
扩展中需要实现call_user_function_in_ext()函数
PHP_FUNCTION(call_user_function_in_ext){
zval **funcName;
zval *retval;
if(ZEND_NUM_ARGS() != 1 ||
zend_get_parameters_ex(1, &function_name) == FAILURE){
zend_error(E_ERROR, "function %s call in extension fail", (*function_name)->value->str->val);
}
if((*function_name)->type != IS_STRING){
zend_error(E_ERROR, "function name must be string");
}
if(call_user_function_ex(CG(function_table), NULL, *function_name, &retval, 0, NULL, 0, NULL TSRMLS_DC) != SUCCESS){
zend_error(E_ERROR, "function call fail");
}
zval *ret_val = *retval;
zval_copy_ctor(ret_val);
zval_ptr_dtor(&retval);
}
此外Zend还有提供显示phpinfo的函数,比较简单,不做讲解。
创建扩展
创建一个链表操作的扩展,扩展名为phplist,生成架构先
cd php_src/ext
./ext_skel --extname=phplist
此时ext目录下生成phplist/,本例不依赖其他扩展或lib库,按准备工作(二)修改config.m4文件。
之后实现扩展的函数。在php_phplist.h中声明,具体实现在phplist.c中。
php_phplist.h如下:
#ifndef PHP_PHPLIST_H
#define PHP_PHPLIST_H
extern zend_module_entry phplist_module_entry;
#define phpext_phplist_ptr &phplist_module_entry
#define PHP_PHPLIST_VERSION "0.1.0" /* Replace with version number for your extension */
#ifdef PHP_WIN32
# define PHP_PHPLIST_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
# define PHP_PHPLIST_API __attribute__ ((visibility("default")))
#else
# define PHP_PHPLIST_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif
PHP_MINIT_FUNCTION(phplist);
PHP_MSHUTDOWN_FUNCTION(phplist);
PHP_RINIT_FUNCTION(phplist);
PHP_RSHUTDOWN_FUNCTION(phplist);
PHP_MINFO_FUNCTION(phplist);
PHP_FUNCTION(confirm_phplist_compiled); /* For testing, remove later. */
/*声明扩展函数*/
PHP_FUNCTION(list_create); //创建链表
PHP_FUNCTION(list_add_head); //添加到链表头
PHP_FUNCTION(list_add_tail); //添加到链表尾
PHP_FUNCTION(list_get_index); //获取节点
PHP_FUNCTION(list_get_length); //获取链表长度
PHP_FUNCTION(list_remove_index); //移除节点
#ifdef ZTS
#define PHPLIST_G(v) TSRMG(phplist_globals_id, zend_phplist_globals *, v)
#else
#define PHPLIST_G(v) (phplist_globals.v)
#endif
#endif /* PHP_PHPLIST_H */
在phplist.c中具体实现
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_phplist.h"
static int le_phplist;
static int isFree = 0;
const zend_function_entry phplist_functions[] = {
PHP_FE(confirm_phplist_compiled, NULL) /* For testing, remove later. */
PHP_FE(list_create, NULL)
PHP_FE(list_add_head, NULL)
PHP_FE(list_add_tail, NULL)
PHP_FE(list_get_index, NULL)
PHP_FE(list_get_length, NULL)
PHP_FE(list_remove_index, NULL)
PHP_FE(list_destroy, NULL)
PHP_FE(list_get_head, NULL)
PHP_FE_END /* Must be the last line in phplist_functions[] */
};
zend_module_entry phplist_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"phplist",
phplist_functions,
PHP_MINIT(phplist),
PHP_MSHUTDOWN(phplist),
PHP_RINIT(phplist), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(phplist), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(phplist),
#if ZEND_MODULE_API_NO >= 20010901
PHP_PHPLIST_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_PHPLIST
ZEND_GET_MODULE(phplist)
#endif
//定义链表节点和链表头
typedef struct _ListNode{
struct _ListNode *prev;
struct _ListNode *next;
zval *value;
}ListNode;
typedef struct _ListHead{
struct _ListNode *head;
struct _ListNode *tail;
int size;
}ListHead;
//创建链表具体实现
ListHead * list_create(){
ListHead *head;
head = (ListHead *)malloc(sizeof(ListHead));
if (head){
head->size = 0;
head->head = NULL;
head->tail = NULL;
}
return head;
}
//向头部添加
int list_add_head(ListHead *head, zval *value){
ListNode *node;
node = (ListNode *)malloc(sizeof(*node));
if (!node){
return 0;
}
node->value = value;
node->prev = NULL;
node->next = head->head;
if (head->head){
head->head->prev = node;
}
head->head = node;
if(!head->tail){
head->tail = head->head;
}
head->size++;
return 1;
}
//链表尾添加
int list_add_tail(ListHead *list, zval *value){
ListNode *node;
node = (ListNode *)malloc(sizeof(*node));
if(!node){
return 0;
}
node->value = value;
node->next = NULL;
node->prev = list->tail;
if (list->tail){
list->tail->next = node;
}
list->tail = node;
if (!list->head){
list->head = list->tail;
}
list->size++;
return 1;
}
//获取指定元素
int list_get_index(ListHead *list, int index, zval **retval){
ListNode *node;
if(!list){
return 0;
}
if (index <= 0 || list->size == 0 || index > list->size){
return 0;
}
if (index < list->size / 2){
node = list->head;
while(node && index > 0){
node = node->next;
--index;
}
}else{
node = list->tail;
while(node && index > 0){
node = node->prev;
--index;
}
}
*retval = node->value;
return 1;
}
//获取链表长度
int list_get_length(ListHead *list){
if (list){
return list->size;
}else{
return 0;
}
}
//删除节点
int list_remove_index(ListHead *list, int index){
ListNode *node;
if(!list){
return 0;
}
if (index <= 0 || list->size == 0 || index > list->size){
return 0;
}
if (index < list->size / 2){
node = list->head;
while(node && index > 0){
node = node->next;
--index;
}
}else{
node = list->tail;
while(node && index > 0){
node = node->prev;
--index;
}
}
if (!node){
return 0;
}
if (node->prev){
node->prev->next = node->next;
}else{
list->head = node->next;
}
if(node->next){
node->next->prev = node->prev;
}else{
list->tail = node->prev;
}
list->size--;
return 1;
}
//释放链表
void list_destroy(ListHead *list){
ListNode *pre, *next;
pre = list->head;
while(pre){
next = pre->next;
free(pre);
pre = next;
}
free(list);
}
int list_get_head(ListHead *list, zval **retval){
if (!list || !list->head){
return 0;
}
*retval = list->head->value;
return 1;
}
//析构函数实现
void phplist_destructor_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC){
if (!isFree){
ListHead *list;
list = (ListHead *)rsrc->ptr;
list_destroy(list);
isFree = 1;
}
}
PHP_MINIT_FUNCTION(phplist)
{
le_phplist = zend_register_list_destructors_ex(phplist_destructor_handler, NULL, "phplist", module_number);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(phplist)
{
return SUCCESS;
}
PHP_RINIT_FUNCTION(phplist)
{
return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(phplist)
{
return SUCCESS;
}
PHP_MINFO_FUNCTION(phplist)
{
php_info_print_table_start();
php_info_print_table_header(2, "phplist support", "enabled");
php_info_print_table_end();
}
PHP_FUNCTION(confirm_phplist_compiled)
{
char *arg = NULL;
int arg_len, len;
char *strg;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
return;
}
len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "phplist", arg);
RETURN_STRINGL(strg, len, 0);
}
PHP_FUNCTION(list_create){
ListHead *list;
list = list_create();
if (!list){
RETURN_NULL();
}
ZEND_REGISTER_RESOURCE(return_value, list, le_phplist);
}
PHP_FUNCTION(list_add_head){
zval *value;
zval *lrc;
ListHead *list;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &lrc, &value) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
int ret = list_add_head(list, value);
if(ret){
RETURN_TRUE;
}
RETURN_FALSE;
}
PHP_FUNCTION(list_add_tail){
zval *value;
zval *lrc;
ListHead *list;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &lrc, &value) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
int ret = list_add_tail(list, value);
if(ret){
RETURN_TRUE;
}
RETURN_FALSE;
}
PHP_FUNCTION(list_get_index){
zval *lrc;
ListHead *list;
long index;
zval *retval;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &lrc, &index) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
int ret = list_get_index(list, index, &retval);
if (ret){
RETURN_ZVAL(retval, 1, 0);
}
RETURN_NULL();
}
PHP_FUNCTION(list_get_length){
zval *lrc;
ListHead *list;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
RETURN_LONG(list_get_length(list));
}
PHP_FUNCTION(list_remove_index){
zval *lrc;
ListHead *list;
long index;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &lrc, &index) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
int ret = list_remove_index(list, index);
if (ret){
RETURN_TRUE;
}
RETURN_FALSE;
}
PHP_FUNCTION(list_destroy){
zval *lrc;
ListHead *list;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
list_destroy(list);
}
PHP_FUNCTION(list_get_head){
zval *lrc;
zval *retval;
ListHead *list;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &lrc) == FAILURE){
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(list, ListHead *, &lrc, -1, "php list", le_phplist);
int ret = list_get_head(list, &retval);
if (ret){
RETURN_ZVAL(retval, 1, 0);
}
RETURN_NULL();
}
之后按照步骤在php.ini中引入扩展即可使用。