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
| 12
 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等
| 12
 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会进行转换
| 12
 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");
 }
 }
 
 | 
打印数组内容
| 12
 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
下面函数创建一个数据并返回
| 12
 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类型
| 12
 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:遍历完整个数组,没有更多元素
| 12
 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:维持原有元素,停止遍历数组
| 12
 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)访问相关数据
| 12
 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