0%

PHP扩展开发 - Part 2

PHP扩展开发第二章,主要介绍函数参数获取、zval数据类型、数组及数组遍历以及$GLOBALS全局变量访问

函数参数

扩展自定义函数不需要声明形参,Zend Engine会给每个函数传递一个参数列表zend_execute_data *execute_data,函数参数通过宏块ZEND_PARSE_PARAMETERS_STARTZEND_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)
{
// 参数1
zend_string *name;
// 参数2
zend_long t_param = 0;
int times = 1;

// 最低可接收1个参数,最多2个
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
// php7 zval变量结构
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
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; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type, /* active type */
zend_uchar type_flags,
union {
uint16_t extra; /* not further specified */
} u)
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* cache slot (for RECV_INIT) */
uint32_t opline_num; /* opline number (for FAST_CALL) */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t constant_flags; /* constant flags */
uint32_t extra; /* not further specified */
} 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)
{
// $arr = [];
array_init(return_value);
// $arr[3] = 123;
add_index_long(return_value, 3, 123);
// $arr[] = "example";
add_next_index_string(return_value, "example");

char *mystr = estrdup("five");
add_next_index_string(return_value, mystr);
efree(mystr);

// $arr["pi"] = 3.1415926;
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);

/*
* 可使用宏ZEND_HASH_FOREACH_VAL、ZEND_HASH_FOREACH_END
*/
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)
) {
// example 1
// 如果是字符串就打印
// if (Z_TYPE_P(data) == IS_STRING) {
// PHPWRITE(Z_STRVAL_P(data), Z_STRLEN_P(data));
// php_printf("\n");
// }

// example 2
// 将参数转为字符串打印
// 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);

// example 3
// 打印数组key以及value
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) {
// 字符串类型key
PHPWRITE(ZSTR_VAL(str_index), ZSTR_LEN(str_index));
} else if (HASH_KEY_IS_LONG == keytype) {
// 数字key
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
// 打印数组val
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;
}

// 打印prefix以及数组val
static int test_array_walk_arg(zval *pDest, void *argument)
{
php_printf("%s", (char *)argument);
test_array_walk(pDest);

return ZEND_HASH_APPLY_KEEP;
}

// 打印prefix、数组val以及suffix
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_maptest_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
// 返回数组指定key的value
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();

// 对非integer/string的key进行转换
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
// 访问全局变量$GLOBALS
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();

// 全局变量符号表:EG(symbol_table)
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