php嵌入扩展,php的扩展和嵌入--php内部变量

之前对于php的内部生命周期和Zend引擎的线程安全机制做了一个介绍,这里这篇文章则是主要介绍php的内部变量是如何实现的。

了解了这些实现的方法之后,对于写php,尤其是进行php扩展开发感觉相当有帮助。

php是一种类型比较松散的语言,与C相比不需要在使用变量前给出类型,直接用就可以。为了实现这一点,php必须在数据类型的定义上做一些工作。

数据类型:

最基本的类型被称为是zval或者说Zend Value,定义在Zend/zend.h头文件中。

typedef struct _zval_struct {

zvalue_value value;

zend_uint refcount;

zend_uchar type;

zend_uchar is_ref;

} zval;

其中zvalue_value按照如下定义:

typedef union _zvalue_value {

long lval;

double dval;

struct {

char *val;

int len;

} str;

HashTable *ht;

zend_object_value obj;

} zvalue_value;

这个是一个union,使用union的时候两种可能,一种是所有的变量共享一片内存空间,一种是要对其中的类型进行n选1的时候。

Zend定义了8种基本的数据类型,这八种基本上在别的语言中也都见过,所以只对比较特殊的类型进行说明:

IS_NULL:non-value

IS_BOOL:

IS_LONG

IS_DOUBLE

IS_STRING:分配的空间需要是长度+1

IS_ARRAY: php的数组其实是hashtable,其中包括label和data

IS_OBJECT:在数组的基础上加上了 方法、访问修改符、有域的常量以及特殊的事件处理器

IS_RESOURCE:比如文件的句柄或是mysql的句柄

这些存储在zval的type中,并且与zvalue_value有着分别的对应关系

注意下面有两个类型判断函数的对比:

void describe_zval(zval *foo)

{

if (foo->type == IS_NULL) {

php_printf("The variable is NULL");

} else {

php_printf("The variable is of type %d", foo->type);

}

}

void describe_zval(zval *foo)

{

if (Z_TYPE_P(foo) == IS_NULL) {

php_printf("The variable is NULL");

} else {

php_printf("The variable is of type %d",

Z_TYPE_P(foo));

}

}

第一段代码中采用的是c的写法,第二段代码是带有php特色的写法。

注意到Zend头文件中提供了很多对zval处理的宏,最好用它们,这里就是用了Z_TYPE_P(foo)。同样还有Z_TYPE()和Z_TYPE_PP()分别对应zval和zval**

php_printf()则是在printf的基础上做了些针对SAPI和php输出机制的优化。

数据值

通过一些宏可以获取不同类型的zval的值:

BVAL():  Boolean

LVAL(): long

DVAL(): double

这个函数针对三种不同的zval类型,分别利用Z_TYPE进行了类型判断。然后利用相应的值提取的宏进行取值。

void display_values(zval boolzv, zval *longpzv,

zval **doubleppzv)

{

if (Z_TYPE(boolzv) == IS_BOOL) {

php_printf("The value of the boolean is: %s\n",

Z_BVAL(boolzv) ? "true" : "false");

}

if (Z_TYPE_P(longpzv) == IS_LONG) {

php_printf("The value of the long is: %ld\n",

Z_LVAL_P(longpzv));

}

if (Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {

php_printf("The value of the double is: %f\n",

Z_DVAL_PP(doubleppzv));

}

}

对于string的处理则要稍微特殊一些:需要两个宏Z_STRVA和Z_STRLEN分别读取值和长度,这个从string类型的定义中也可以看到,它是由字符和长度组成的。

void display_string(zval *zstr)

{

if (Z_TYPE_P(zstr) != IS_STRING) {

php_printf("The wrong datatype was passed!\n");

return;

}

PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr));

}

对数组的访问使用的是ARRVAL系列:Z_ARRVAL(zv), Z_ARRVAL_P(pzv), Z_ARRVAL_PP(ppzv).

有一些版本的php源码中HASH_OF()等同于Z_ARRVAL_P,但是这个宏已经渐渐的用的少了.

对于Object:

OBJ_HANDLE 返回对象句柄标识

OBJ_HT 句柄表

OBJCE 类定义

OBJPROP 属性哈希表

OBJ_HANDLER 在OBJ_HT中操作特定处理方法

对于资源Resource就直接用宏

RESVAL

数据创建:

想要创造一个变量并分配空间的malloc(sizeof(zval))在php这里并不可行。应该使用MAKE_STD_ZVAL(pzv),它对空间的分配进行了优化,并且会自动的初始化refCount(表示这个变量被引用的次数)和is_ref(是否是强制引用)这两个性质。注意它的输入是一个指针.

ALLOC_INIT_ZVAL()也可以进行初始化,不同之处在于把zval*的值设为了NULL.

在设置不同类型的值的时候有很多形式,左边是比较简略的形式,右侧则是展开的形式:

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;

对于字符串的处理要特殊一些,提供了一个单独的参数dup. 这个参数决定了是否创建一个字符串的副本.举个例子

zval * pzva;

ZVAL_STRING(pzval,"hello world",1);

由于“hello_world”是一个常量字符串,直接对它进行操作显然不合适,所以把dup设为1的话,会自动的给它创建一个副本,然后再赋给pzval. 这使得整个过程更加简洁。

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);

注意dup如果设为1的话就是申请新的空间并且拷贝内容,而不是一个shaddow copy。

ZVAL_RESOURCE(pzv, res);          Z_TYPE_P(pzv) = IS_RESOURCE;

Z_RESVAL_P(pzv) = res;

数据的存储

数据的存储都在符号表中。

symbol table,每当创建一个新的变量的时候,Zend都保存这个值到这个内部的数组中去。

符号表在RINIT之前创建,在RSHUTDOWN之后销毁。

当用户空间的函数或对象方法被调用的时候,会创建一个新的符号表,生命与函数执行时间相同。

在Zend/zend_gblobals.h中定义了两个元素:

struct _zend_executor_globals {

...

HashTable symbol_table;

HashTable *active_symbol_table;

...

};

通过EG(symbol_table) 的方式可以访问符号表。感觉跟$GLOBALS似的。

注意到EG(symbol_table)这个宏返回的不是指针,必须加上&。

下面的这个对比非常的有趣:

In PHP:

针对这段php的代码,C中一共做了如下这些事:

In C:

{

zval *fooval;

MAKE_STD_ZVAL(fooval); //首先分配空间,设置变量

ZVAL_STRING(fooval, "bar", 1); //然后赋值,创建一个copy,你不能直接操作常字符串

ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval); // 在符号表中注册,foo是一个label

}所谓active_symbol_table指的是程序执行当前的符号表,在进入一个函数之后,会有它自己对应的符号表,就类似于C中针对一个函数自己的栈空间。而当退出了函数之后,它的符号表会被销毁,这时候又回到了下面这个状态:

EG(active_symbol_table) == &EG(symbol_table), 这个时候并没有进入函数。

数据的获取: 在获取数据的时候,比较多的是使用zend_hash_find()函数:

{

zval **fooval;

if (zend_hash_find(EG(active_symbol_table),

"foo", sizeof("foo"),

(void**)&fooval) == SUCCESS) {

php_printf("Got the value of $foo!");

} else {

php_printf("$foo is not defined.");

}

}这个函数首先查找符号表,找到名字为“foo”的变量,然后返回到fooval中。下面着重解释两个问题:

为什么要声明一个zval ** fooval 然后还要通过&fooval并且转换为(void **)的形式?

为什么要用sizeof("foo")

对第一个问题,要考虑到我们寻找的目标是一个zval*,所以要把它看作一个整体。利用这种写法可以避免编译告警。

第二个问题,使用sizeof(label)主要是为了表示字符串常量label的尾部,这里使用4也是可以的,但是通用性不够。

数据转换: 仅仅是说一下有这个功能,比如convert_to_string(zval *value)可以把zval转换为字符串。

以上就是php内部变量的一些介绍,为了能够区分不同的类型、设置获取变量值以及在符号表中增加和查找变量,这些知识必不可少。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值