1 创建扩展
##本例用的是php7.1.1
cd ext
./ext_skel --extname=helloworld
把下面几行注释打开,config.m4中 dnl 为注释的意思
##动态编译选项,通过.so的方式链接,去掉dnl注释:
PHP_ARG_WITH(helloworld, for helloworld support,
Make sure that the comment is aligned:
[ --with-helloworld Include helloworld support])
##静态编译选项,通过enable来启用,去掉dnl注释:
PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
Make sure that the comment is aligned:
[ --enable-helloworld Enable helloworld support])
修改完成编译下
phpize
#这里用自己的php-config文件位置
./configure --with-php-config=./configure --with-php-config=/Applications/MAMP/bin/php/php7.1.1/bin/php-config
make && make install
make test #测试
#编辑php.ini,加上helloworld扩展
extension=helloworld.so
(3)重启扩展并使用里面的函数
2 全局资源
如果扩展中需要使用全局资源来实现函数之间的数据共享,则可以像内核中的EG,CG那样,向TSRM 注册全局资源,扩展中,通常定义一个全局资源结构体.然后把这个节后提注册到TSRM,这个结构可以使用ZEND_BEGIN_MODULE_GLOBALS(extension_name) ,ZEND_END_MODULE_GLOBALES(extension_name)两个宏定义,这两个宏必须成对出现,中间定义扩展用到的"全局变量" 即可 ,比如
1 定义全局变量
ZEND_BEGIN_MODULE_GLOBALS(mytest)
zend_long open_cache;
HashTable class_table;
ZEND_END_MODULE_GLOBALS(mytest)
展开后是个结构体
typedef struct _zend_mytest_globals{
zend_long open_cache;
HashTable class_table;
}zend_mytest_globals;
2 注册全局变量: ZEND_DECLARE_MODULE_GLOBALS(mytest)
3 最后,定义一个像EG,CG 那样的宏用于访问扩展的全局资源结构体,这一步将使用ZEND_MODULE_GLOBALS_ACCESSOR(module_name,v) 来完成
一般定义一个宏来做这个事情
#define MYTEST_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(mytest,v)
这样就可以直接使用 MYTEST_G(open_cache) 对结构体进行读写了
3 函数参数解析
用户自动以函数在编译时会为每个参数创建一个zend_arg_info 结构. 这个结构用来记录参数的名称,是否引用传参,是否可变参数等信息.在存储上函数参数
与函数内部的局部变量并没有差别,可以把参数当做局部变量来对待,他们分配在zend_execute_data上,调用函数时首先会进行参数传递,这个过程是按参数次序,
一次将参数的value从函数调用空间传递到被调函数的zend_execute_data,函数内部像访问普通局部变量一样通过存储位置访问参数.
内部函数和用户自定义函数最大的不同在于:内部函数就是一个C语言定义的函数,它的局部变量是C语言中的变量,并不会分配到zend_execute_data上,因此
除函数参数以外在zend_execute_data上没有其他变量的分配,函数参数是从PHP用户空间传到内部函数中的,他们与用户自定义函数完全相同,包括参数的分配
方式,传参过程,也是按照参数次序一次分配在zend_execute_data上,在内部函数中,可以按照参数顺序从zend_execute_data 上读取到对应的参数,当然,实际
使用时不需要我们自己去取,PHP提供了一个方法将zend_execute_data上的参数机械到指定变量上
zend_parse_parameters(int num_args,const char *type_sepc)
下面是用法 用来获取第一个是字符串 第二个是整型的参数
zend_string *strg;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"sl",&arg,&arg_len,&l_value) == FAILURE){
return;
}
//字符串连接函数
strg = strpprintf(0, "the module is %s,the num of params is %d, and the str is %s,the int value is %d.", "myext", arg_size,arg,l_value);
RETURN_STR(strg);
引用传参
如果函数需要使用引用类型的参数或者返回引用,就需要创建函数的参数数组,这个数组通过ZEND_BEGIN_ARG_INFO() 或者 ZEND_BEGIN_ARG_INFO_EX(),
ZEND_END_ARG_INFO()宏定义.
//name 参数数组名,注册函数PHP_FE(function,arg_info) 会用到
//unused 保留值,暂时没用
// return_reference 返回值是否为引用,一般很少会用到
// required_num_args: required 参数数
ZEND_BEGIN_ARG_INFO_EX(arginfo_my_func_1, 0, 0, 1)
ZEND_ARG_INFO(1,a)
ZEND_END_ARG_INFO()
//定义完后这个数组后就需要把这个数组告诉函数
PHP_FE(my_func_1,arginfo_my_func_1) /* 测试内部修改返回的参数 */
//引用参数通过zend_parse_parameters()解析时只能使用z解析,不能直接解析为zend_value,否则引用将失效
PHP_FUNCTION(my_func_1){
zval *lval; //必须为zval 定义成zend_long 也能解析出,但不是引用
if(zend_parse_parameters(ZEND_NUM_ARGS(),"z",&lval) == FAILURE){
return;
}
zval *real_val = Z_REFVAL_P(lval); //lval 的类型为IS_REFERENCE ,获取实际用用的zval 地址: &(lval.value->ref.val)
Z_LVAL_P(real_val) = 100; //设置实际引用的类型
php_printf("you are in");
}
函数返回值
PHP_FUNCTION(test_return_arr){
zval *arr;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"a",&arr) == FAILURE){
RETURN_FALSE;
}
Z_ADDREF_P(arr); //增加用一用计数
ZVAL_ARR(return_value,Z_ARR_P(arr));
}
此函数接收一个数组,然后直接返回该数组,数组多了一个来自返回值得引用,因此需要增加数组的引用计数.
虽然可以直接设置return_value,但实际使用时并不建议这么做,PHP提供了很多专门用于设置返回值的宏,这些宏定义在zend_API.h中.
RETURN_BOOL() //返回bool值
RETURN_NULL()
RETURN_LONG(l) //zend_long
RETURN_DOUBLE(d) //double
RETURN_STR(s) //zend_string
RETURN_INTERNED_STR(s) // 返回内部字符串,这种变量将不会被回收
RETURN_NEW_STR(s) //返回普通字符串
RETURN_STR_COPY(s)
RETURN_STRING(s)
RETURN_STRINGL(s,l)
RETURN_EMPTY_STRING()
RETURN_RES(r)
RETURN_ARR(r)
RETURN_OBJ(r)
RETURN_ZVAL(zv,copy,dtor)
RETURN_FALSE()
RETURN_TRUE()
函数调用(调用其他扩展的函数或者用户自定义的函数)
/* 调用自定义函数*/
PHP_FUNCTION(use_user_func){
zend_long i;
zval call_func_name,call_func_ret,call_func_params[1];
uint32_t call_func_param_cnt = 1;
zend_string *call_func_str;
char *func_name = "mySum";
//获取传进来的参数
if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&i) == FAILURE){
RETURN_FALSE;
}
//分配 zend_string : 调用完需要释放
call_func_str = zend_string_init(func_name,strlen(func_name),0);
//设置 zval
ZVAL_STR(&call_func_name,call_func_str);
//设置参数
ZVAL_LONG(&call_func_params[0],i);
//调用
if(SUCCESS != call_user_function(EG(function_table),
NULL, //调用成员方法的对象
&call_func_name, // 调用的函数名名称
&call_func_ret, // 函数返回值地址
call_func_param_cnt, //实际传参数量
call_func_params //参数数组
)){
zend_string_release(call_func_str);
RETURN_FALSE;
}
zend_string_release(call_func_str);
RETURN_LONG(Z_LVAL(call_func_ret));
}
函数调用(调用系统函数)
/* 调用系统函数 */
PHP_FUNCTION(use_system_func){
zend_array *arr1,*arr2;
zval call_func_name,call_func_ret,call_func_params[2];
uint64_t call_func_param_cnt = 2;
zend_string *call_func_str;
char *func_name = "array_merge";
if(zend_parse_parameters(ZEND_NUM_ARGS(),"hh",&arr1,&arr2)==FAILURE){
RETURN_FALSE;
}
call_func_str = zend_string_init(func_name,strlen(func_name),0);
ZVAL_STR(&call_func_name,call_func_str);
ZVAL_ARR(&call_func_params[0],arr1);
ZVAL_ARR(&call_func_params[1],arr2);
if(call_user_function(EG(function_table),NULL,&call_func_name,&call_func_ret,call_func_param_cnt,
call_func_params) != SUCCESS){
zend_string_release(call_func_str);
RETURN_FALSE;
}
zend_string_release(call_func_str);
RETURN_ARR(Z_ARRVAL(call_func_ret));
}
ZVAL操作
1 ZVAL 的创建和获取
PHP7将变量的引用计数转移到了具体的value上,所以zval更多的是作为统一的传输格式,很多情况下只是临时性的使用,比如函数调用时的传参,最终需要的数据是zval写到的zend_value,
函数从zval取得zend_value后就不在关心zval了,这种就可以直接再栈上分配zval,分配完zval需要设置其类型,value等信息,php中定义的zval_xxx()系列的宏就是用来设置不同类型的zval
的,这些宏的第一个参数z均为要设置的zval的指针,后面为要设置的zend_value,对应的,内核提供了Z_XXX(zval),Z_XXX_P(zval_p) 系列的宏,用于获取不同类型zval 的value.
zval的类型可以通过Z_TYPE(zval),Z_TYPE_P(zval_p)两个宏来获取,这个类型实际获取的就是zval.u1.v.type,但是设置时不能只修改这个type,而要设置typeinfo.因为zval还有其他的
表示需要社会,比如是否使用引用计数,是否可以被垃圾回收,是否可以被复制等.
(1) 获取 zval 的类型 Z_TYPE(zval) Z_TYPE_P(zval_p)
(2) UNDEF 和 NULL. 通过ZVAL_UNDEF(z) 将zval 设置为 UNDEF,ZVAL_NULL(z) 将设置为NULL.
Z_ISUNDEF(zval) ,Z_ISUNDEF_P(zval_p) ,判断zval是否为 UNDEF,
Z_ISNULL(zval),Z_ISNULL_P(zval) 判断zval是否为NULL
(3)布尔值
ZVAL_BOOL(z,b) b为 IS_TRUE,IS_FALSE 判读对应的bool值
ZVAL_FALSE(z) 设置为false,ZVAL_TRUE(z) 设置为true
(4)整型,浮点型
设置 : ZVAL_LONG(z,l),ZVAL_DOUBLE(z,d) 设置 l,d 为zend_long,zend_double
获取 : Z_LVAL(zval) / Z_LVAL_P(zval_p), Z_DVAL(zval)/Z_DVAL_P(zval_p) 来获取对应的值
(5)字符串
ZVAL_STR(z,s) ,s为zend_string 类型的地址,这里不会讲s的字符串复制一份,只是会将s地址设置到新的zval,s的引用计数不会自动增加,如果需要增加引用计数,需要抖动增加,这个宏支持
内部字符串,如果s为内部字符串则只会设置zval 的type类型
ZVAL_NEW_STR(z,s),除了不支持内部字符串,与ZVAL_STR(z,s) 完全一样.这里说的不支持内部字符串是指:如果把内部支付穿赋给zval,这个宏会把该字符串当做不同字符串对待.
ZVAL_STR_COPY(z,s),与ZVAL_STR 操作一直,只是会根据s的类型决定是否需要增加引用计数器
Z_STR(zval) ,Z_STR_P(zval_p) 获取zend_string 的地址
Z_STRVAL(zval),Z_STRVAL_P(zval_p) 获取的是char 字符串即 zend_string->val
Z_STRLEN(zval) ,Z_STRLEN_P(zval_p) 获取字符串长度
Z_STRHASH(zval),Z_STRHASH_P(zval_p) 获取字符串的哈希值 即 zend_string->h
//字符串范例
PHP_FUNCTION(test_zval){
zval test_str;
zend_string *test_zend_string;
char *test = "I used another way to init zend string";
// test_zend_string = zend_string_init(test,strlen(test),0);
test_zend_string = strpprintf(0,"I used another way to init zend string");
ZVAL_STR(&test_str,test_zend_string); //赋值给zval
php_printf("the value is %s\n",Z_STRVAL(test_str));
php_printf("the value len is %d\n",Z_STRLEN(test_str));
php_printf("the value hash is %d\n",Z_STRHASH(test_str));
// php_printf("the value by p is %s\n",Z_STRVAL_P(&test_str));
RETURN_STR(test_zend_string);
}
(6) 数组
如果已经分配了zend_array 结构,则可以通过ZVAL_ARR(z,a) 赋给 zval,a 就是zend_arry地址,该宏不会增加a的引用计数;如果是新分配一个空数组,则可以通过ZVAL_NEW_ARR(z) 完成,
需要注意的是,这个宏这是分配了zend_array 内存,但是并没有进行数组的初始化,也就是说此时数组还不能使用,关于数组初始化的操作接下来会单独说明
可以通过Z_ARR(zval),Z_ARR_P(zval_p) 来获取zend_arry 的地址. Z_ARRVAL(zval) ,Z_ARRVAL_P(zval_p) 获取数组的值
(7)对象
对象可以通过ZVAL_OBJ(z,o) 宏设置,o 为zend_object 的地址,该宏不会增加o的引用计数,获取对象的操作比较多
可以通过
Z_OBJ(zval) ,Z_OBJ_P(zval_p) 取到,另外以下几个宏用于获取对象的其他信息:
Z_OBJ_HT(zval)/ZEND_OBJ_HT_P(zval_p) //获取对象的zend_object_handlers 即 zend_object -> handlers
Z_OBJ_HANDLER(zval)ZEND_OBJ_HANDLER_P(zval_p) //获取对象对应操作的handler 的指针 write_property,read_property 等.注意这个宏取到的值为只读,不要试图修改这个值
Z_OBJ_HANDLE(zval)/ZEND_OBJ_HANDLE_P(zval_p) //获取对象打得handle 即 zend_objcect->handle
Z_OBJECT(zval)/Z_OBJECT_P(zval_p) //获取对象实例化的类,即zend_object->ce
Z_OBJPROP(zval)/Z_OBJPROP_P(zval_p) //获取对象的成员数组,返回的是hashtable 的地址
(8) 资源
资源可以通过ZVAL_RES(z,r) 宏设置,r为zend_resource 地址,该宏不会增加r的引用计数,对应的,可以通过Z_RES(zval),Z_RES_P(zval_p) 宏来获取zend_resource地址,除了这俩宏
还可以通过Z_RES_HANDLE(zval),Z_RES_HANDLE_P(zval_p) 来获取资源的handle,即zend_resource->handle
(9)引用
如果已经有zend_reference 结构了,则可以通过ZVAL_REF(z,r) 宏直接设置zval,r为zend_reference 的地址,该宏不会增加r的引用计数;
如果没有zend_reference结构可以通过ZVAL_NEW_REF(z,r) 创建一个引用,r为也要引用的值
可以通过Z_REF(zval),Z_REF_P(zval_p)获取zend_reference 的地址,如果要获取引用实际value 可以使用Z_REFVAL(zval),Z_REFVAL_P(zval_p) 获取
除了上面这些与PHP变量类型相关的宏,还有一些内核自己使用类型的宏,因为zval是一个通用的结构,可以用它保存任意类型,比如类,函数等
Z_INDIRECT(zval) / Z_INDIRECT_P(zval_p) // 获取indirect 的zval,指向另外一个zval
Z_CE(zval)/Z_CE_P(zval_p) //获取zval 的zend_class_entry
Z_FUNC(zval)/Z_FUNC_P(zval_p) //获取zval的zend_function
Z_PTR(zval)/Z_PTR_P(zval_p) //获取zval的ptr
变量赋值
扩展中经常会用到将一个变量复制到另外一个变量的场景,这个操作可以通过ZVAL_COPY(z,v) ,z为目标zval,复制后这个宏会增加对用value 的引用计数,
除这个宏之外,还有一个功能与之相同但不会主动增加引用计数的宏:ZVAL_COPY_VAL(z,v).
引用计数
在扩展中操作PHP用户控件相关变量时,需要谨慎考虑是否需要引用计数相关操作.比如下面的例子
function test($arr){
return $arr;
}
$a = [1,2];
$b = test($a);
如果把函数test()用内部函数实现,这个函数接受了一个PHP用户控件传入的数组参数,然后又返回并赋值给了PHP用户空间的另外一个变量,这个时候就需要增加
传入数组的refcount.数组由PHP用户控件分配,函数调用前refcount = 1 ,传到内部函数时相当于赋值给了函数的参数,因此refcount 变成2,这次增加在函数执行
完释放参数是会减掉,等返回并赋值给$b后,此时共有两个变量指向这个数组,所以内部函数需要增加refcount,增加的引用是给返回值的.test()翻译成内部函数如下.
PHP_FUNCTION(test){
zval *arr;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"a",&arr) == FAILURE){
RETURN_FALSE;
}
Z_ADDREF_P(arr); //增加引用计数 如果注释掉下面这个将导致core dumped(核心错误)
RETURN_ARR(Z_ARR_P(arr))
}
那么在哪些情况下需要考虑设置引用计数呢?一个关键条件就是:操作的PHP用户空间的相关变量,包括对用户控件变量的修改,赋值,要明确的一点是引用计数是用来
解决多个变量指向同一个value问题的,所以在PHP中来回传递zval的时候需要考虑是不是要修改引用计数.下面总结PHP中常见的会对引用计数进行操作的情况.
(1) 变量赋值:变量赋值是最常见的情况,一个引用计数的变量类型在初始赋值时其refcount=1,如果后面把此变量又赋值给了其它变量,那么久需要增加引用计数.
(2)数组操作:如果把一个变量插入数组中,那么久需要增加这个变量的引用计数,如果要删除一个数组元素,要相应的减少其引用.
(3)函数调用:传参世界可以当做普通的变量赋值,将调用空间的变量赋值给被调用空间的变量,函数返回时会销毁函数空间的变量,这是有会减少传参的引用,
这两个过程由内核完成,不需要扩展自己处理
(4)成员属性:当把一个变量赋值给对象的成员属性的时候需要增加引用计数
PHP 中定义了以下宏用于引用计数的操作,包括获取变量的引用计数以及增加,减少变量的引用计数:
//pz 类型为zval*
Z_REFCOUNT_P(pz) //获取引用数
Z_SET_REFCOUNT_P(pz) //设置引用数
Z_ADDREF_P(pz) //增加引用数
Z_DELREF_P(pz) //减少引用数
Z_REFCOUNT(z) //获取引用数
Z_SET_REFCOUNT(z) //设置引用数
Z_ADDREF(z) //增加引用数
Z_DELREF(z) //减少引用数
只对使用了引用计数的变量类型增加/减少引用建议使用下面的
Z_TRY_ADDREF_P(pz)/ Z_TRY_ADDREF(z)
Z_TRY_DELREF_P(pz) / Z_TRY_DELREF(z)
上面的这些宏的类型都是zval 或zval* 如果需要操作具体value的引用计数可以使用一下宏.
直接获取zend_value 的引用,可以直接通过这个宏修改value的refcount
GC_REFCOUNT(p) //暂时还不知道如何使用
Z_REFCOUNTED(zval) / Z_REFCOUNTED_P(pz) //判断是否用到引用计数机制
Z_COUNTED(zval) / Z_COUNTED_P(pz) //根据zval 获取value 的zend_refcounted 头部
字符串操作
zend_string 是PHP自己封装的一个用于表示字符串的结构,也是使用比较频繁的一种类型
数组操作
(1)创建数组
创建一个新的hashtable 分为两步:首先分配zend_array 内存,这个可以通过ZVAL_NEW_ARR()宏分配,也可以自己直接分配;然后初始化数组,通过zend_hash_init()
宏完成,如果不进行初始化操作将无法使用.
(2)插入更新元素
zend_hash_add() //如果键值不存在则插入,存在的话则不管
zend_hahs_update() //如果键值存在的话进行覆盖,不存在的话插入
zend_hash_str_update //插入键值为普通字符串的 键值存在会进行覆盖
zend_hash_str_add //插入键值为普通字符串的 如果键值存在则不操作
zend_hash_next_index_insert //使用能自动索引
下面范例包含数组的增删改查 和遍历
/* 数组测试 */
PHP_FUNCTION(arr_test){
zval array;
zval first_value,second_value,third_value;
zval *find_key;
// zend_string *key_str;
uint32_t size;
HashTable *ht;
ZVAL_NEW_ARR(&array); //分配内存
zend_hash_init(Z_ARRVAL(array),size,NULL,ZVAL_PTR_DTOR,0); //进行初始化
// key_str = strpprintf(0,"first");
ZVAL_STR(&first_value,strpprintf(0,"I am first value 呵呵")); //zend_string字符串
ZVAL_DOUBLE(&second_value,99.99); //设置为浮点型
ZVAL_LONG(&third_value,1000); //设置为整型
ht = Z_ARRVAL(array); //hashtable
/*---------------------------插入元素----------------------------------------------- */
// zend_hash_add(Z_ARRVAL(array),strpprintf(0,"first"),&first_value); // 如果键值存在则不操作
zend_hash_update(ht,strpprintf(0,"first"),&first_value); // 如果键值存在进行覆盖
// zend_hash_update(Z_ARRVAL(array),strpprintf(0,"first"),&second_value); // 数组插入
zend_hash_update(ht,strpprintf(0,"second"),&second_value); // 数组插入
zend_hash_str_update(ht,"third",5,&third_value); // 数组插入 针对键值为字符串的
zend_hash_index_update(ht,6,&third_value); // 数组插入 针对键值为数值的
zend_hash_next_index_insert(ht,&third_value); // 数组插入 针对键值为数值的
zend_hash_next_index_insert(ht,&third_value); // 数组插入 使用自动索引
zend_hash_next_index_insert(ht,&third_value); // 数组插入 使用自动索引
zend_hash_update(ht,strpprintf(0,"last"),&second_value); // 数组插入
/*---------------------------/插入元素----------------------------------------------- */
/*---------------------------查找元素----------------------------------------------- */
// find_key = zend_hash_find(ht,strpprintf(0,"first")); //根据zend_string 查找
// find_key = zend_hash_str_find(ht,"third",5); //根据普通字符串查找
find_key = zend_hash_index_find(ht,6); //根据数值元素来查找
php_printf("the value I found is %d \n",Z_LVAL_P(find_key));
// zend_bool key_exist = zend_hash_exists(ht,strpprintf(0,"first")); //根据zend_string字符串来查找
// zend_bool key_exist = zend_hash_str_exists(ht,"third",5); //根据普通字符串来查找
zend_bool key_exist = zend_hash_index_exists(ht,100); //根据索引来查找
if(key_exist==TRUE){
php_printf("I found the key\n");
}else{
php_printf("I did not find the key\n");
}
/*---------------------------/查找元素----------------------------------------------- */
php_printf("the numer of arr is %d \n",zend_array_count(ht)); //获取数组元素个数
/*---------------------------删除元素----------------------------------------------- */
// zend_hash_del() // 根据zend_String 来删除
// zend_hash_str_del() // 根据普通字符串来删除
// zend_hash_index_del() // 根据数字索引来删除
/*---------------------------/删除元素----------------------------------------------- */
/*---------------------------遍历数组----------------------------------------------- */
zval *val;
//遍历过程中把数组元素赋值给val,除了上面的这个宏
ZEND_HASH_FOREACH_VAL(ht,val){
zend_long value_type = Z_TYPE_P(val);
switch (value_type)
{
case IS_LONG:
php_printf("the value is %d \n",Z_LVAL_P(val));
break;
case IS_DOUBLE:
php_printf("the value is %f\n",Z_DVAL_P(val));
break;
case IS_STRING:
php_printf("the value is %s\n",Z_STRVAL_P(val));
default:
break;
}
}ZEND_HASH_FOREACH_END();
//遍历获得所有的数值索引
zend_ulong h;
ZEND_HASH_FOREACH_NUM_KEY(ht,h){
php_printf("the ZEND_HASH_FOREACH_NUM_KEY KEY is %d \n",h);
}ZEND_HASH_FOREACH_END();
//遍历获得所有的zend_String索引
zend_string *str_key;
ZEND_HASH_FOREACH_STR_KEY(ht,str_key){
if(str_key){
//只有这样赋值了才能打印出来 或者使用 RETURN_STR(str_key)
zval call_func_name;
ZVAL_STR(&call_func_name,str_key);
php_printf("the ZEND_HASH_FOREACH_STR_KEY KEY IS %s \n",Z_STRVAL(call_func_name));
}
}ZEND_HASH_FOREACH_END();
//遍历获得所有的 数字 和 string key
zend_string *str_key2;
zend_ulong index;
ZEND_HASH_FOREACH_KEY(ht,index,str_key2){
if(str_key2){
zval call_func_name;
ZVAL_STR(&call_func_name,str_key2);
php_printf("the ZEND_HASH_FOREACH_KEY KEY is %s \n",Z_STRVAL(call_func_name));
}else if(index){
php_printf("the ZEND_HASH_FOREACH_KEY KEY is %d \n",index);
}
}ZEND_HASH_FOREACH_END();
//遍历key 和 value
zend_string *str_key3;
zend_ulong index3;
zval *val3,key_val3;
ZEND_HASH_FOREACH_KEY_VAL(ht,index3,str_key3,val3){
if(str_key3){ //一定要字符串在前才行
ZVAL_STR(&key_val3,str_key3);
php_printf("the ZEND_HASH_FOREACH_KEY_VAL KEY is (%s)",Z_STRVAL(key_val3));
}else if(index3){
php_printf("the ZEND_HASH_FOREACH_KEY_VAL KEY is (%d)",index3);
}
//根据类型打印value的值
zend_long value_type = Z_TYPE_P(val3);
switch (value_type)
{
case IS_LONG:
php_printf("and the value is %d \n",Z_LVAL_P(val3));
break;
case IS_DOUBLE:
php_printf("and the value is %f\n",Z_DVAL_P(val3));
break;
case IS_STRING:
php_printf("and the value is %s\n",Z_STRVAL_P(val3));
default:
break;
}
}ZEND_HASH_FOREACH_END();
/*---------------------------/遍历数组----------------------------------------------- */
// zend_array_destroy(ht); //销毁数组
RETURN_ARR(ht); //返回数组
}
常量定义
PHP_MINIT_FUNCTION(myext)
{
/* If you have INI entries, uncomment these lines
REGISTER_INI_ENTRIES();
*/
/* 常量定义 一般在 MINIT function 中定义 */
REGISTER_STRING_CONSTANT("MY_EXT_STRING_CONSTANT","Hello World",CONST_CS | CONST_PERSISTENT);
REGISTER_NULL_CONSTANT("MY_EXT_NULL_CONSTANT",CONST_CS | CONST_PERSISTENT);
REGISTER_BOOL_CONSTANT("MY_EXT_BOOL_CONSTANT",TRUE,CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("MY_EXT_LONG_CONSTANT",100,CONST_CS | CONST_PERSISTENT);
REGISTER_DOUBLE_CONSTANT("MY_EXT_DOUBLE_CONSTANT",100.99,CONST_CS | CONST_PERSISTENT);
REGISTER_STRINGL_CONSTANT("MY_EXT_STRLEN_CONSTANT","Hello World",5,CONST_CS | CONST_PERSISTENT);
return SUCCESS;
}
面向对象
面向对象是PHP重要的特性之一,通过扩展可以对类实现更灵活的,更抢到的控制,同时与PHP脚本中的类相比.通过扩展实现的类不需要编译,有更高的性能.
内部类注册
在扩展中定义一个内部类的方式和函数类似,函数最终注册到EG(function_table) ,而类则最终注册到EG(class_table) 符号表中.注册的过程首先是魏类创建一个
zend_class_entry结构.然后把这个结构插入EG(class_table),当然这个过程不需要我们手动操作,PHP提供了现成的方法和宏帮我们对zend_class_entry 进行初始化
和注册,通常情况下会把内部类的注册放到 module startup阶段. 也就是定义在扩展PHP_MINIT_FUNCTION()中.一个简单的类的注册只需要下面几行.
//类注册额
zend_class_entry ce; //分配一个zend_class_entry 这个结构只在注册的时候使用,所以分配在栈上即可
INIT_CLASS_ENTRY(ce,"MyClass",NULL);//对 zend_class_entry 进行初始化
zend_register_internal_class(&ce); //注册
PHP_CODE
$obj = new MyClass();
var_dump($obj);
上面那样就成功定义了一个内部类,类名为MyClass 只是这个类还没有任何的成员属性,成员方法.
成员属性
1 .声明成员属性
zval myname;
// ZVAL_STR(&myname,strpprintf(0,"see you again222")); //zend_string字符串
ZVAL_DOUBLE(&myname,20.00);
// ZEND_ACC_PUBLIC ZEND_ACC_PROTECTED ZEND_ACC_PRIVATE
zend_declare_property(myclass_ce,"myname",6,&myname,ZEND_ACC_PUBLIC); //声明一个常规属性
// php_printf("the len is %d ",ZEND_STRL("str"));
zend_declare_property_string(myclass_ce,ZEND_STRL("str"),"Hello World222",ZEND_ACC_PUBLIC); //声明一个常规属性
// 等价下面这一条 相当于展开了
// = zend_declare_property_string(myclass_ce,"str",sizeof("str")-1,"Hello World222",ZEND_ACC_PUBLIC); //声明一个常规属性
内部声明的属性不能是数组,对象,资源. 如果是这几种类型在声明的时候将会报错,单着这并不是指内部类不能使用这些类型,只是默认是值不能是这些类型,所以实际使用时通常会把他们
定义为NULL,运行时在通过对象将其修改为其他类型.
2.修改成员属性
修改成员属性需要在类的成员方法中修改
//使用成员方法修改成员属性
ZEND_METHOD(MyClass, updatemyname)
{
zval myname;
ZVAL_DOUBLE(&myname,50.00);
zend_update_property(myclass_ce,getThis(),ZEND_STRL("myname"),&myname);
RETURN_TRUE;
}
3.获取成员属性
//读取属性
ZEND_METHOD(MyClass, getmyname)
{
zval myname,*res;
zend_bool nowtrue = 1;
//不知道 myname 这个参数有啥用....
res = zend_read_property(myclass_ce,getThis(),ZEND_STRL("myname"),nowtrue,&myname);
//使用这种方法可以返回任何类型 不错
ZVAL_DEREF(res);
ZVAL_COPY(return_value, res);
}
成员方法
//1 定义要使用的方法集
zend_function_entry myclass_methods[]={
PHP_ME(MyClass,updatemyname,NULL,ZEND_ACC_PUBLIC)
PHP_ME(MyClass,getmyname,NULL,ZEND_ACC_PUBLIC)
PHP_ME(MyClass,getConstant,NULL,ZEND_ACC_PUBLIC)
PHP_FE_END
};
//INIT 的时候加入上面定义的
//对 zend_class_entry 进行初始化 后面测参数是类的方法
INIT_CLASS_ENTRY(ce,"MyClass",myclass_methods);
//2 在.h中像普通方法一样进行声明
ZEND_METHOD(MyClass, updatemyname);
ZEND_METHOD(MyClass, getmyname);
ZEND_METHOD(MyClass, getConstant);
//3具体实现
ZEND_METHOD(MyClass, getmyname)
{
zval myname,*res;
zend_bool nowtrue = 1;
//不知道 myname 这个参数有啥用....
res = zend_read_property(myclass_ce,getThis(),ZEND_STRL("myname"),nowtrue,&myname);
//使用这种方法可以返回任何类型 不错
ZVAL_DEREF(res);
ZVAL_COPY(return_value, res);
}
常量
1 声明常量
zend_declare_class_constant_string(myclass_ce,ZEND_STRL("CONSTANT_VALUE"),"WORLD22");
zend_declare_class_constant(myclass_ce,ZEND_STRL("CONSTANT_VAL"),&next_myname);
2获取常量(目前调试还有些问题) ,鉴于日常开发中 使用得非常少,先不管
zend_get_constant_ex(strpprintf(0, "CONSTANT_VAL"),myclass_ce,ZEND_ACC_PUBLIC);
类的实例化
/* 返回类实例*/
PHP_FUNCTION(return_myclass){
object_init_ex(return_value, myclass_ce);
}
这样就可以通过外部方法返回实例了.
注意: 如果自定义了create_object 方法,则需要考虑是否定义对象销毁的handler:zend_object->handlers->free_obj. 默认情况下,当销毁一个对象时会调用默认的方法处理
如果你的create_object 方法里处理zend_object 外还分配了其他内存,则需要同时自定义free_obj,否则就会导致 zend_object 之外的内存得不到释放.自定义对象的handlers
方法比较简单,直接再创建对象时直接将handlers指定为自定义即可
资源
除了PHP中定义的那些布尔型,整型,浮点型数组等类型外,实际应用中还会经常用到socket 连接,文件句柄,那么这些类型在PHP中是如何实现的呢? 答案就是资源.资源是PHP中比较
特殊的一种类型,资源类型只能通过扩展,内核定义,无法在用户控件自定义资源类型,只能使用资源.资源也是PHP中最为灵活的一种类型,可以把任何数据定义为资源类型.
注册新的资源类型时,PHP内核并不关心资源的具体数据是什么,它只分配给资源一个唯一编号作为区分资源类型的标识,剩下的事情都需要扩展自己控制.
struct _zend_resource {
zend_refcounted_h gc;
int handle; //资源的序号
int type; //资源的类型
void *ptr; //资源的数据
};
创建资源有两种含义: 一种是创建实际的资源,并不是zend_resource结构,这种完全由资源注册方自己完成;另外一种就是创建zend_resource结构.比如我们创建的资源需要传递
到用户PHP用户空间,这个时候就需要创建一个zend_resource结构,然后把实际的资源保存到zend_resource->ptr.如果要创建一个资源类型的变量,即zend_resource,可以通过
zend_register_resource()方法申请,该方法只有两个参数:一个为资源的数据指针,另一个为注册资源时返回的资源类型,申请成功后返回 zend_resource地址.分配得所有资源变量
保存在EG(regular_list) 哈希表中.请求结束后清理这个哈希表.
ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
范例:
//定义资源结构
typedef struct{
zend_string *filename;
FILE *fp;
} my_file_t;
static int le_myfile;
//销毁资源的函数
void myfile_dtor(zend_resource *res){
my_file_t *myresource;
myresource = (my_file_t *)(res->ptr);
//关闭文件
fclose(myresource->fp);
//释放资源
efree(myresource);
}
PHP_MINIT_FUNCTION(myext)
{
//注册资源
le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"myfile",module_number);
}
//实现方法
//返回资源
PHP_FUNCTION(my_fopen){
zend_string *path;
FILE *fp;
my_file_t *myresource;
zend_resource *var_resource;
//解析参数
if(zend_parse_parameters(ZEND_NUM_ARGS(),"S",&path) == FAILURE){
RETURN_FALSE;
}
//打开文件
fp = fopen(ZSTR_VAL(path),"w+");
//创建资源结构
myresource = emalloc(sizeof(my_file_t));
myresource->filename = path;
myresource->fp = fp;
var_resource = zend_register_resource(myresource,le_myfile);
RETURN_RES(var_resource);
}
//写入数据
PHP_FUNCTION(my_fwrite){
zval *res;
zend_string *content;
my_file_t *myresource;
if(zend_parse_parameters(ZEND_NUM_ARGS(),"rS",&res,&content) == FAILURE){
RETURN_FALSE;
}
myresource = Z_RES_VAL_P(res);
fwrite(ZSTR_VAL(content),sizeof(char),content->len,myresource->fp);
}
PHPCODE
$myfp = my_fopen("/tmp/log.log");
my_fwrite($myfp,"Hello World");
上面的是一个资源的基本用法,从上面的例子我们看到,通过资源类型可以灵活的存储任意类型,在PHP扩展中.很多RPC 客户端是通过资源类型来存储socket 连接的.大家可以到ext目录搜索查看
销毁资源
PHP 的变量是自动回收的,资源类型不同于其他数据,因为资源不仅仅是释放内存,它还需要进行其他的一些清理,比如上面的示例,销毁资源时需要关闭文件,在比如保存socket连接的资源,在释放是也需要关闭连接,在注册资源类型时可以提供一个析构函数,当资源销毁时,资源管理器会回调该函数进行清理,这个函数类型如下
持久化资源
普通资源的生命周期在request 结束时也随之结束,持久化资源则可以使资源在request 结束时任然保留,下次请求时还可以继续使用,持久化资源通过EG(persistent_list) 保存.直接将
资源插入这个哈希表即可.持久化资源有着非常重要的意义.利用持久化资源可以实现长连接,从而提高系统的性能.持久化资源在module shutdown 阶段才被释放
总结
本章介绍了PHP扩展开发中用到的一些知识,主要是一些借口,宏的使用.内容更多的倾向于应用性,原理性的东西介绍的比较少.实际PHP扩展的开发并不复杂,尽管扩展开发并不需要对
ZEND_VM 有很深入的了解,但是如果对ZEND_VM 的实现比较熟悉,那么对扩展开发的帮助将非常大.你可以更容易想到从哪些地方入手实现扩展的功能.debug的时候也就更加得心应手.总之一句话,如果你想真正理解PHP 的内部实现,仅仅通过扩展是不可能完成的