PHP7/5扩展开发函数手册(2) - zval操作

本文详细介绍了在PHP扩展开发中zval的操作,包括zval的创建、销毁、类型获取、引用计数管理以及常量的注册等关键知识点,帮助开发者深入理解PHP内部机制。

目录

  • String 的两个宏定义
  • 在扩展中打印zval
  • zval 的销毁
  • zval 类型获取
  • 获取 zval 的 value 值
  • zval变量赋值
  • 获取zval引用计数
  • 增加引用计数?
  • 传入参数分离
  • 获取常量的值
  • 注册常量

String 的两个宏定义

在Zend 内核中,针对字符串,定义了两个宏, 这个在后续的字符串处理中经常需要用到,大家留意

----zend_portability.h----
#define ZEND_STRL(str)		(str), (sizeof(str)-1)
#define ZEND_STRS(str)		(str), (sizeof(str))

 

在扩展中打印 zval

我们在写扩展过程中,可以用 php_debug_zval_dump 打印 zval, 以便于调试,该函数会打印出引用计数 ref_count

zval *matches;

SW_ALLOC_INIT_ZVAL(matches);
SW_ZVAL_STRING(matches, "/([a-zA-Z0-9_\\-]*)\\s*\\(([a-zA-Z0-9_\\-]*)\\)/i", 1);
php_debug_zval_dump(matches, 1);

zval 的创建

MAKE_STD_ZVAL(pzv). 这个宏将会以一种优化的方式为zval分配空间, 自动的处理超出内存错误,并初始化新zval的refcount和is_ref属性,除此之外,还有宏 ALLOC_INIT_ZVAL(). 这个宏和MAKE_STD_ZVAL唯一的区别是它会将zval *的数据类型初始化为IS_NULL。

MAKE_STD_ZVAL例子(注意:在PHP7下,已经不允许我们在堆上去分配 zval 空间,我们通常的做法是, 定义一个临时变量(栈上),然后将 p 的指针指向这个临时变量的地址,注意在使用完之后销毁zval,通常,我们可以在.h文件中做一个php5和php7版本的适配, 让你的代码能同时在php5 和 php7 上编译通过):

----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
	#define SW_MAKE_STD_ZVAL(p)               MAKE_STD_ZVAL(p)
	#define SW_ALLOC_INIT_ZVAL(p)             ALLOC_INIT_ZVAL(p)
	#define sw_zval_ptr_dtor(p)	          zval_ptr_dtor(*p) //zval销毁

#else /* PHP Version 7 */
        //栈上分配空间
	#define SW_MAKE_STD_ZVAL(p)             zval _stack_zval_##p; p = &(_stack_zval_##p)
	#define SW_ALLOC_INIT_ZVAL(p)           do{p = (zval *)emalloc(sizeof(zval)); bzero(p, sizeof(zval));}while(0)
	#define sw_zval_ptr_dtor(p)	        zval_ptr_dtor(*p) //zval销毁

#endif

----swoole_server.c----

static PHP_METHOD(swoole_http_client, __construct)
{
	...
	
    //SW_MAKE_STD_ZVAL分配的zval内存必须在使用后手动释放
    zval *headers;
    SW_MAKE_STD_ZVAL(headers);
    array_init(headers); //初始化为数组
    zend_update_property(swoole_http_client_class_entry_ptr, getThis(), ZEND_STRL("headers"), headers TSRMLS_CC);
    sw_zval_ptr_dtor(&headers);
    
    ...
    
    //SW_ALLOC_INIT_ZVAL 分配的 zval 内存由 php 回收
    zval *ports;
    SW_ALLOC_INIT_ZVAL(ports);
    array_init(ports); //初始化为数组
    zend_update_property(swoole_http_client_class_entry_ptr, getThis(), ZEND_STRL("ports"), ports TSRMLS_CC);
    
    ...
}

 

zval 的销毁

zval 的销毁有两个函数,zval_dtor 和 zval_ptr_dtor ,两个是不同的

//zval_ptr_dtor 和 zval_dtor 都是宏函数,最终展开后

  • ZEND_API  void  _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC)
  • ZEND_API   void  _zval_dtor_func(zval *zvalue ZEND_FILE_LINE_DC)

两者的工作都与释放zval有关,但又有很大的区别。
比如我们有一个zval *tmp,而且我们已经对它进行了MAKE_STD_ZVAL等一系列操作了。

zval_dtor会直接把我们的tmp的value部分即tmp->value所指的内存释放掉。当然也可能不是直接释放掉,而是重新交给ZendMM,由ZendMM进行重新分配或者释放。

如果我们对它使用zval_ptr_dtor会发生什么事情呢?
zval_ptr_dtor首先会将它的refcount减一,如果减一后refcount为0了,便会再调用 zval_dtor 把tmp->value给释放掉,然后再调用efree_rel()函数把自己tmp所指的zval类型结构体所占的内存空间给释放掉。
如果减一后不为0呢?那zval_ptr_dtor便不会释放tmp->value和tmp本身,而是通知一下GC垃圾回收器,然后返回而已。

 zval_ptr_dtor 例子:

----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
    #define SW_MAKE_STD_ZVAL(p)    MAKE_STD_ZVAL(p)
    #define sw_zval_ptr_dtor       zval_ptr_dtor

#else /* PHP Version 7 */
    #define SW_MAKE_STD_ZVAL(p)    zval _stack_zval_##p; p = &(_stack_zval_##p)
    #define sw_zval_ptr_dtor(p)    zval_ptr_dtor(*p)
	
#endif

------test.c----

PHP_METHOD(delete_zval)
{
    zval *headers;

    SW_MAKE_STD_ZVAL(headers);
    array_init(headers);
    
    ...

    sw_zval_ptr_dtor(&headers);
}

zval_dtor例子:

----swoole_serialize.c----

static void* swoole_unserialize_object(void *buffer, zval *return_value, zend_uchar bucket_len, zval *args)
{
    zval property;
    
    ....

    buffer = swoole_unserialize_arr(buffer, &property, arr_num);

    ....

    zval_dtor(&property);
    RETURN_TRUE;
}

zval 类型获取

利用宏 Z_TYPE_P 可以获取 zval 的类型, 这个宏针对的是指针 zval ,当然还有,Z_TYPE 和 Z_TYPE_PP

例子:

void display_zval(zval *value) {
  switch (Z_TYPE_P(value)) {
	case IS_NULL:
		/* NULLs are echoed as nothing */
		break;
	case IS_BOOL:
		if (Z_BVAL_P(value)) {
		php_printf("1");
		}
		break;
	case IS_LONG:
		php_printf("%ld", Z_LVAL_P(value));
		break;
	case IS_DOUBLE:
		php_printf("%f", Z_DVAL_P(value));
		break;
	case IS_STRING:
		PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));
		break;
	case IS_RESOURCE:
		php_printf("Resource #%ld", Z_RESVAL_P(value));
		break;
	case IS_ARRAY:
		php_printf("Array");
		break;
	case IS_OBJECT:
		php_printf("Object");
		break;
	...
  }
}

获取 zval 的 value 值

 

Z_BVAL_P(pzv) boolean类型值
Z_LVAL_P(pzv) 获取整型类型
Z_DVAL_P(pzv) = d;获取double类型

Z_STRLEN_P(pzv)

Z_STRVAL_P(pzv)

获取字符串长度/字符串类型
ZVAL _STRINGL(pzv, str, strlen(str), dup);获取指定长度字符串长度/字符串类型
Z_RESVAL_P(pzv)获取资源类型
Z_ARRVAL_P(pzv)获取数组的 HashTable

 

为基本类型的变量赋值

操作等价
ZVAL_NULL(pvz); Z_TYPE_P(pzv) = IS_NULL;
ZVAL_BOOL(pzv, b); 

Z_TYPE_P(pzv) = IS_BOOL;

Z_BVAL_P(pzv) = b ? 1 : 0;

ZVAL_TRUE(pzv); ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pzv);ZVAL_BOOL(pzv, 0);
ZVAL_LONG(pzv, l);

Z_TYPE_P(pzv) = IS_LONG;

Z_LVAL_P(pzv) = l;

ZVAL_DOUBLE(pzv, d);

Z_TYPE_P(pzv) = IS_DOUBLE;

Z_DVAL_P(pzv) = d;

ZVAL_STRINGL(pzv,str,len,dup);

Z_TYPE_P(pzv) = IS_STRING;

Z_STRLEN_P(pzv) = len;
if (dup) {
       Z_STRVAL_P(pzv) =
       estrndup(str, len + 1);
} else {
        Z_STRVAL_P(pzv) = str;
}

ZVAL_STRING(pzv, str, dup);ZVAL _STRINGL(pzv, str, strlen(str), dup);
ZVAL_RESOURCE(pzv, res);Z_TYPE_P(pzv) = IS_RESOURCE;
Z_RESVAL_P(pzv) = res;

 


获取zval引用计数

我们用宏Z_REFCOUNT_P(p)来获取 zval 的引用计数,参数 p 为 zval 指针。

例子:

PHP_METHOD(print_refcount)
{
	zval *zset = NULL;
	
	//参数传入数组
	if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "z", &zset) == FAILURE)
	{
		return;
	}
	if (Z_TYPE_P(zset) != IS_ARRAY)
	{
		RETURN_FALSE;
	}
    
	//下面两个函数获取的值是一样的
	php_printf("refcount=%d --\n", zset->value.arr->gc.refcount);? //php数组的引用计数值存储位置
	php_printf("refcount=%d --\n", Z_REFCOUNT_P(zset));
}

增加引用计数 

增加引用计数在 PHP5 和 PHP7 的函数是不一样的,在 PHP 5 是 zval_add_ref(zval **ppzval),而PHP7 是一个宏 Z_TRY_ADDREF_P(zval* v) , 通常,我们可以在.h文件中做一个php5和php7版本的适配, 让你的代码能同时在php5 和 php7 上编译通过, 如下:

----php7_wrapper.h----

#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
	#define sw_zval_add_ref	zval_add_ref

#else /* PHP Version 7 */
	#define sw_zval_add_ref(p)   Z_TRY_ADDREF_P(*p)

#endif

----test.c----

PHP_METHOD(add_refcount)
{
    zval *zset = NULL;
    
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zset) == FAILURE)
    {
        return;
    }
    
    php_printf("refcount=%d --\n", Z_REFCOUNT_P(zset));
    sw_zval_add_ref(&zset); //增加 zset 的引用计数
    php_printf("refcount=%d --\n", Z_REFCOUNT_P(zset));
    RETURN_TRUE;
}

传入参数分离

当一个变量传递到一个函数中后, 无论是否是引用传值, 它的refcount至少是2; 一个是变量自身, 另外一个是传递给函数的拷贝. 因此在修改zval之前(如果直接在参数传递进来的zval上), 将它从它所属的非引用集合中分离出来非常重要,也是为了防止他的变更会影响到用户空间传入的参数,下面我们自己定义参数分离php_ch_array_separate(arr),来将用户空间与扩展空间的 zval 隔离开来:

----php_swoole.h----

#define php_swoole_array_separate(arr)       zval *_new_##arr;\
    SW_MAKE_STD_ZVAL(_new_##arr);\
    array_init(_new_##arr);\
    php_array_merge(Z_ARRVAL_P(_new_##arr), Z_ARRVAL_P(arr));\
    arr = _new_##arr;

----test.c----

PHP_METHOD(php_array_separate)
{
	zval *zset = NULL;

    
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zset) == FAILURE)
    {
        return;
    }
    
    if (Z_TYPE_P(zset) != IS_ARRAY)
    {
        RETURN_FALSE;
    }
    
    php_swoole_array_separate(zset); //经过隔离之后, zset 的引用计数变成 1
    
    ...
    
    RETURN_TRUE;
}

获取常量的值

int zend_get_constant(char *name, uint name_len, zval *result TSRMLS_DC);
参数用途
name以NULL结尾的常量名称,用于获取。 也可以采用CLASSNAME :: CONSTANT形式来获取类常量。
name_len常量名称的长度,不包含NULL结束符
result查到的常量的值将被拷贝到 result 指针

注册常量

使用传递的值注册指定类型的常量。 某些其他类型(不包括Arrays和Objects)的常量也可以通过手动构造zend_constant并将其传递给zend_register_constant()来注册。

void zend_register_long_constant(char *name, uint name_len, long value, int flags, int module_number TSRMLS_DC);
void zend_register_double_constant(char *name, uint name_len, double value, int flags, int module_number TSRMLS_DC);
void zend_register_string_constant(char *name, uint name_len, char *value, int flags, int module_number TSRMLS_DC);
void zend_register_stringl_constant(char *name, uint name_len, char *value, uint value_len, int flags, int module_number TSRMLS_DC);
int zend_register_constant(zend_constant *c TSRMLS_DC);
参数用途
name以NULL结尾的常量名称。
name_len常量名称的长度,包含NULL结束符。
value常量初始值
value_lenzend_register_stringl_constant 函数中指定 value 的长度,不包含NULL结束符
flagsCONST_CS 或 CONST_PERSISTENT的任意组合。
module_number来自于 MINIT or RINIT 函数参数。
c初始化的zend_constant结构

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值