3

PHP扩展开发 - Part 2

 2 years ago
source link: https://y2k38.github.io/posts/php-extension-writing-part2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PHP扩展开发 - Part 2

发表于

2020-02-01 更新于 2020-02-10

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

扩展自定义函数不需要声明形参,Zend Engine会给每个函数传递一个参数列表zend_execute_data *execute_data,函数参数通过宏块ZEND_PARSE_PARAMETERS_STARTZEND_PARSE_PARAMETERS_END获取,当然,旧的方法zend_parse_parameters也可以使用,只是会更麻烦点

下面示例函数将接收参数name以及打印的次数t_param并打印name

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

PHP7的zval相对PHP5做了比较大的改动,zval、zend_string、zend_array重构,bool类型分成了true、false两种类型直接存储在(zval).u1.type_info等

// 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会进行转换

// 接收任意类型参数,最后输出其类型
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");
}
}

打印数组内容

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

下面函数创建一个数据并返回

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类型

// 遍历并打印数组字符串值
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:遍历完整个数组,没有更多元素
// 打印数组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:维持原有元素,停止遍历数组
// 返回数组指定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)访问相关数据

// 访问全局变量$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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK