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