PHP扩展开发第二章,主要介绍函数参数获取、zval数据类型、数组及数组遍历以及$GLOBALS全局变量访问
函数参数 扩展自定义函数不需要声明形参,Zend Engine会给每个函数传递一个参数列表zend_execute_data *execute_data
,函数参数通过宏块ZEND_PARSE_PARAMETERS_START
、ZEND_PARSE_PARAMETERS_END
获取,当然,旧的方法zend_parse_parameters
也可以使用,只是会更麻烦点
下面示例函数将接收参数name以及打印的次数t_param并打印name
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PHP_FUNCTION(test) { zend_string *name; zend_long t_param = 0 ; int times = 1 ; ZEND_PARSE_PARAMETERS_START(1 , 2 ) Z_PARAM_STR(name) Z_PARAM_OPTIONAL Z_PARAM_LONG (t_param) ZEND_PARSE_PARAMETERS_END () ; if (ZEND_NUM_ARGS() == 2 ) { times = (int ) (t_param < 1 ? 1 : t_param); } for (int i = 0 ; i < times; i++) { php_printf("Hello %s" , ZSTR_VAL(name)); } RETURN_TRUE; }
常用的有数据类型以及参数获取方法有下
Variable Type
Macro
zend_bool
Z_PARAM_BOOL
zend_long
Z_PARAM_LONG
double
Z_PARAM_DOUBLE
zend_string *
Z_PARAM_STR
char *
Z_PARAM_STRING
zval *
Z_PARAM_RESOURCE
zval *
Z_PARAM_ARRAY
zval *
Z_PARAM_OBJECT
zval *
Z_PARAM_ZVAL
ZVAL结构 PHP7的zval相对PHP5做了比较大的改动,zval、zend_string、zend_array重构,bool类型分成了true、false两种类型直接存储在(zval).u1.type_info等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 typedef union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } zend_value; struct _zval_struct { zend_value value; union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, zend_uchar type_flags, union { uint16_t extra; } u) } v; uint32_t type_info; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t opline_num; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; uint32_t constant_flags; uint32_t extra; } u2; };
zval类型判断,一般情况下,如果参数与Z_PARAM_*
的类型不一致,Zend Engine会进行转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 PHP_FUNCTION(test_type) { zval *uservar; ZEND_PARSE_PARAMETERS_START(1 , 1 ) Z_PARAM_ZVAL(uservar); ZEND_PARSE_PARAMETERS_END(); switch (Z_TYPE_P(uservar)) { case IS_NULL: php_printf("NULL" ); break ; case IS_TRUE: php_printf("Boolean: TRUE" ); break ; case IS_FALSE: php_printf("Boolean: FALSE" ); break ; case IS_LONG: php_printf("Long: %ld" , Z_LVAL_P(uservar)); break ; case IS_DOUBLE: php_printf("Double: %f" , Z_DVAL_P(uservar)); break ; case IS_STRING: php_printf("String: " ); PHPWRITE(Z_STRVAL_P(uservar), Z_STRLEN_P (uservar)); break ; case IS_RESOURCE: php_printf("Resource" ); break ; case IS_ARRAY: php_printf("Array" ); break ; case IS_OBJECT: php_printf("Object" ); break ; default : php_printf("Unknown" ); } }
打印数组内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 PHP_FUNCTION(test_again) { zval *zname; ZEND_PARSE_PARAMETERS_START(1 , 1 ) Z_PARAM_ZVAL(zname); ZEND_PARSE_PARAMETERS_END(); convert_to_string(zname); php_printf("Hello " ); PHPWRITE(Z_STRVAL_P(zname), Z_STRLEN_P(zname)); RETURN_TRUE; }
convert_to_*
会将参数转换成指定的类型,即改变原始参数类型、数据,在php代码中,原本以传参的形式传递的变量值会被修改。要解决这个问题,可以使用临时变量,也可以使用convert_to_*_ex
,该类函数在转换类型前会先调用SEPARATE_ZVAL_IF_NOT_REF
,避免修改原始变量
数组实现 PHP的数组用途非常广泛,类对象属性的存储也依赖数组,数组的底层实现结构为HashTable
下面函数创建一个数据并返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 PHP_FUNCTION(test_array) { array_init(return_value); add_index_long(return_value, 3 , 123 ); add_next_index_string(return_value, "example" ); char *mystr = estrdup("five" ); add_next_index_string(return_value, mystr); efree(mystr); add_assoc_double(return_value, "pi" , 3.1415926 ); zval subarr; array_init(&subarr); add_next_index_string(&subarr, "hello" ); add_assoc_zval(return_value, "subarr" , &subarr); }
add_next_index_*
添加元素进数组,由系统分配一个递增的数字key
add_index_*
添加元素进数组,由用户指定一个数字类型的key
add_assoc_*
添加元素进数组,由用户指定的字符串类型key
注意:add_assoc_*
非二进制安全
扩展函数返回值不使用return
语句,配置zval *return_value
即可,该变量由宏PHP_FUNCTION
提供,返回值RETURN_TRUE
也是通过设置return_value
完成返回值赋值,默认设置为IS_NULL
类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 PHP_FUNCTION(test_array_strings) { zval *arr, *data; HashTable *arr_hash; HashPosition pointer; int array_count; ZEND_PARSE_PARAMETERS_START(1 ,1 ) Z_PARAM_ARRAY(arr); ZEND_PARSE_PARAMETERS_END(); arr_hash = Z_ARRVAL_P(arr); array_count = zend_hash_num_elements(arr_hash); php_printf("The array passed contains %d elements\n" , array_count); for ( zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); data = zend_hash_get_current_data_ex(arr_hash, &pointer); zend_hash_move_forward_ex(arr_hash, &pointer) ) { int keytype; zend_string * str_index; zend_ulong num_index; keytype = zend_hash_get_current_key_ex(arr_hash, &str_index, &num_index, &pointer); if (HASH_KEY_IS_STRING == keytype) { PHPWRITE(ZSTR_VAL(str_index), ZSTR_LEN(str_index)); } else if (HASH_KEY_IS_LONG == keytype) { php_printf("%ld" , num_index); } php_printf(" => " ); zval tmp; tmp = *data; zval_copy_ctor(&tmp); convert_to_string(&tmp); PHPWRITE(Z_STRVAL(tmp), Z_STRLEN(tmp)); php_printf("\n" ); zval_dtor(&tmp); } RETURN_TRUE; }
zend_hash_get_current_key_ex
返回的类型有
HASH_KEY_IS_LONG
:整型key
HASH_KEY_IS_STRING
:字符串型key
HASH_KEY_NON_EXISTENT
:遍历完整个数组,没有更多元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static int test_array_walk (zval *pDest) { zval tmp; tmp = *pDest; zval_copy_ctor(&tmp); convert_to_string(&tmp); PHPWRITE(Z_STRVAL(tmp), Z_STRLEN(tmp)); php_printf("\n" ); zval_dtor(&tmp); return ZEND_HASH_APPLY_KEEP; } static int test_array_walk_arg (zval *pDest, void *argument) { php_printf("%s" , (char *)argument); test_array_walk(pDest); return ZEND_HASH_APPLY_KEEP; } static int test_array_walk_args (zval *pDest, int num_args, va_list args, zend_hash_key *hash_key) { php_printf("%s" , va_arg(args, char *)); test_array_walk(pDest); php_printf("%s\n" , va_arg(args, char *)); return ZEND_HASH_APPLY_KEEP; } PHP_FUNCTION(test_array_walk) { zval *arr; ZEND_PARSE_PARAMETERS_START(1 ,1 ) Z_PARAM_ARRAY(arr); ZEND_PARSE_PARAMETERS_END(); zend_hash_apply(Z_ARRVAL_P(arr), (apply_func_t ) test_array_walk); zend_hash_apply_with_argument(Z_ARRVAL_P(arr), (apply_func_arg_t ) test_array_walk_arg, "Hello" ); zend_hash_apply_with_arguments(Z_ARRVAL_P(arr), (apply_func_args_t ) test_array_walk_args, 2 , "Hello " , "Welcome to my extension!" ); }
上面test_array_walk
类似array_map
,test_array_walk
简单遍历数组,test_array_walk_arg
遍历数组,可接收一个任意类型的额外参数,下面代码中用作数组元素的前缀,test_array_walk_args
遍历数组,可接收任意多个参数,下面代码中用作数组元素的前缀、后缀
返回值类型:
ZEND_HASH_APPLY_KEEP
:维持原有元素,继续遍历数组剩余元素
ZEND_HASH_APPLY_REMOVE
:删除原有元素,继续遍历数组剩余元素
ZEND_HASH_APPLY_STOP
:维持原有元素,停止遍历数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 PHP_FUNCTION(test_array_value) { zval *arr, *offset, *val; char * tmp; zend_string *str_index = NULL ; zend_ulong num_index; ZEND_PARSE_PARAMETERS_START(2 ,2 ) Z_PARAM_ARRAY(arr); Z_PARAM_ZVAL(offset); ZEND_PARSE_PARAMETERS_END(); switch (Z_TYPE_P(offset)) { case IS_NULL: case IS_FALSE: num_index = 0 ; break ; case IS_TRUE: num_index = 1 ; break ; case IS_DOUBLE: num_index = (long ) Z_DVAL_P(offset); break ; case IS_LONG: case IS_RESOURCE: num_index = Z_LVAL_P(offset); break ; case IS_STRING: str_index = zval_get_string(offset); break ; case IS_ARRAY: tmp = "Array" ; str_index = zend_string_init(tmp, sizeof (tmp) - 1 , 0 ); break ; case IS_OBJECT: tmp = "Object" ; str_index = zend_string_init(tmp, sizeof (tmp) - 1 , 0 ); break ; default : tmp = "Unknown" ; str_index = zend_string_init(tmp, sizeof (tmp) - 1 , 0 ); } if (str_index && (val = zend_hash_find(Z_ARRVAL_P(arr), str_index)) == NULL ) { RETURN_NULL(); } else if (!str_index && (val = zend_hash_index_find(Z_ARRVAL_P(arr), num_index)) == NULL ) { RETURN_NULL(); } *return_value = *val; zval_copy_ctor(return_value); }
PHP7中zval在栈分配,函数执行完清理,需要深入复制一份给return_value
Symbol Tables 全局变量$GLOBALS的底层存储结构也是HashTable,存储在一个全局结构体Executor Globals(_zend_executor_globals)里,通过EG(symbol_table)访问相关数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PHP_FUNCTION(test_get_global_var) { zval *val; zend_string *varname; ZEND_PARSE_PARAMETERS_START(1 ,1 ) Z_PARAM_STR(varname); ZEND_PARSE_PARAMETERS_END(); if ((val = zend_hash_find(&EG(symbol_table), varname)) == NULL ) { php_error_docref(NULL , E_NOTICE, "Undefined variable: %s" , ZSTR_VAL(varname)); RETURN_NULL(); } *return_value = *val; zval_copy_ctor(return_value); }
完整代码 php_ext_tutorial
参考文档 Extension Writing Part II: Parameters, Arrays, and ZVALs (Unable To Access) Extension Writing Part II: Parameters, Arrays, and ZVALs [continued] (Unable To Access) Extension Writing Part II: Parameters, Arrays, and ZVALs Upgrading PHP extensions from PHP5 to NG PHP Internals Book References about Maintaining and Extending PHP Internal value representation in PHP 7 - Part 1 Internal value representation in PHP 7 - Part 2 PHP’s new hashtable implementation