您的位置:首页 > 编程语言 > PHP开发

Extending and Embedding PHP-扩展和移植PHP(九)

2012-12-23 23:09 393 查看
数据创建

现在你已经学会怎么从zval里面取数据了,是时候创建自己的数据了。zval可以在方法前面声明严格的变量,可以存在本地,也可以被复制离开这个方法,进入用户空间。

因为你总是想以某种形势让你创建的zval变量进入用户空间,想分配一块内容给这个变量,并把一个zval指针指向它。再次声明,使用malloc(sizeof(zval))并不是一个好的方式,而应该使用MAKE_STD_ZVAL(pzv)宏代替。该宏会挨着其它zval变量分配最佳的内存空间,迅速的捕获内存溢出错误,初始化refcount和is_ref属性。

  注意:除了MAKE_STD_ZVAL()宏,你还会经常使用ALLOC_INIT_ZVAL()宏,与MAKE_STD_ZVAL()不同的地方是,ALLOC_INIT_ZVAL()会初始化数据类型为IS_NULL。

一但存储空间分配完,就可以把信息放到zval变量里了,在学习了本章前面知识,你可能使用Z_TYPE_P()或者Z_SOMEVAL_P()来建立变量,这样做真的正确吗?

实际zend有另外一组宏来建立变量,下面就是这些宏,看下它们怎么延伸你熟悉的那些宏。

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_BOOL()的任何非零值,都是true,这样做没问题,因为任何给布尔的非零的值在用户空间都会表达相同。在内部代码里要硬编码时,用1做为true是最好的。ZAVL_TRUE()和ZVAL_FALSE()宏使代码可读性更好。

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

字符串,数组,资源,需要分类额外的空间,在下一章我们讨论内存管理的陷阱。现在你只要知道,重复的1会分配新的内存,并复制字符串的值。然而0值会简单的指向zval。

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

               Z_RESVAL_P(pzv)=res;   

回想一下,资源类型存储在zval里,以一个简单的整形存储,通过Zend搜索注册的资源表,ZVAL_RESOURCE()宏看起来和ZVAL_LONG()宏差不多,只是类型不一样。

数据存储

 你已经使用过PHP了,应该知道任何变量都可以存储到数组里面,用一个数字或者字符串做key.

在PHP脚本里每个变量都可以在数组里找到,当你创建一个变量,给经付值,Zend会把这个值存到一个叫做符号表的内部数组里。这个符的号表,是全局范围的,在请求之前的RINIT方法里被初始化,在RSHUTDOWN里释放。

当一个对象的方法被调用时,会为该方法的生命周期分配一个新的符号表并激活,如果不是在方法内执行代码,则使用全局符号表。

看下globals的结构体,在Zend/zend_globals.h里面定义的,可以找到下面两个元素:

struct_zend_execution_globals{
...
HashTablesymbol_table;
HashTable*active_symbol_table;
...
};

symbol_table总是存取全局范围的变量,类似PHP脚本里的$GLOBALS变量,实际上,从内部看$GLOBALS变量就是symbol_table的封装。active_symbol_table变量和symbol_table类似,代表当前哪个范围被激活。

看下面的代码,功能相同:

InPHP:

<?php$foo='bar';?>


InC:

{
zval*fooval;

MAKE_STD_ZVAL(fooval);
ZVAL_STRING(fooval,"bar",1);
ZEND_SET_SYMBOL(EG(active_symbol_table),"foo",fooval);
}


首先,使用MAKE_STD_ZVAL()分配一个新的zval,初始化一个值"bar",然后用一个新的宏给他付值,绑定一个"foo"的标签,放到activesymboltable里,因为用户空间没有方法被激活,也就意味着当前定义在全局范围。

数据回收

要回收用户空间的数据,首先要先找到在哪个符号表里,使用zend_hash_find方法

{
zval**fooval;

if(zend_hash_find(EG(active_symbol_table),
"foo",sizeof("foo"),
(void**)&fooval)==SUCCESS){
php_printf("Gotthevalueof$foo!");
}else{
php_printf("$fooisnotdefined.");
}
}

代码看起来很有意思,为什么使用fooval两层指针,为什么使用sizeof来取长度,为什么要使用&fooval,怎样用zval***匹配void**?如果这三个问题你都想到了,你很不错。

首先,值得注意的是,HashTable不仅存储用户空间的变量,还用于所有引擎,甚至某些情况下,存储无指针的变量。HashTable是固定大小的,然而为了存储任意大小的数据,HashTable会配置一个内存块去封闭存储的数据。对于变量,存储的是zval*。所以HashTable会配置一个足够大的内存块存指针,并用新的指针来指向zval*,这样你在HashTable里看到的就是zval**,存储一个zval*的原因在下一章里讲解。

当回收数据时,HashTable也只知道一个指向某些东西的指针,为了把一个指针填充到一个正在调用方法的本地存储,调用方法会废弃本地指针,返回一个忽略变量类型的双层指针,不定的变量类型实际就是zval*的类型,你所看到的传递给zend_hash_find()方法的实际上是三层指针,而不是两层,目的在于让编译器进行类型匹配时不报警告。

使用sizeof()的原因是在"foo"变量名后加上NULL结束符,在上直接写4也一样,不过不鼓励这样做,如果变量名变了,这就失效了,(strlen("foo")+1)也可以解决该问题,不过有些编译器不支持,导致出现问题。

如果zend_hash_find()方法定位到你要找的数据,会返回指针和成功标识,否则,返回失败。

在用户空间的变量被存储到符号表时,成功或失败标识是否正确被设置。

数据转换

现在,你可以从符号表取数据了,你还想做些其它事。比如根据变量类型执行特定操作,简单的一个switch代码如下:

voiddisplay_zval(zval*value)
{
switch(Z_TYPE_P(value)){
caseIS_NULL:
/*NULLsareechoedasnothing*/
break;
caseIS_BOOL:
if(Z_BVAL_P(value)){
php_printf("1");
}
break;
caseIS_LONG:
php_printf("%ld",Z_LVAL_P(value));
break;
caseIS_DOUBLE:
php_printf("%f",Z_DVAL_P(value));
break;
caseIS_STRING:
PHPWRITE(Z_STRVAL_P(value),Z_STRLEN_P(value));
break;
caseIS_RESOURCE:
php_printf("Resource#%ld",Z_RESVAL_P(value));
break;
caseIS_ARRAY:
php_printf("Array");
break;
caseIS_OBJECT:
php_printf("Object");
break;
default:
/*Shouldneverhappeninpractice,
*butit'sdangeroustomakeassumptions
*/
php_printf("Unknown");
break;
}
}

代码很简单,和<?echo$value;?>相比很容易发现,上面的代码会变得不可控。很幸运,Zend使用convert_to_*()方法,代码如下:

voiddisplay_zval(zval*value)
{
convert_to_string(value);
PHPWRITE(Z_STRVAL_P(value),Z_STRLEN_P(value));
}

很容易想到,对于大多的类型应该有一组这样的方法,convert_to_resource()除外,因为根据定义,资源类型无法映射为了个实际值。

如果你担心convert_to_string会不可逆的改变一些变量的值,那你想的很周全,在实际的代码里面这样做确实不好,而且引擎在输出变量时也的确不是这么做的。下一章讲解如何安全的输出变量,而不改变变量原有的值。


小节

在本章,你学习了PHP的一些内部原理。学习了怎样区分类型,设置和取变量值,把变量放到符号表,以及怎样取出来。下一章,你将学到怎样复制一个zval,在不需要时注销它们,更重要的,当你不需要时,应避免复制它们。你还会看到Zend在处理请求时内存管理,检测持久和非持久的配置。到下一章结束,你才能学习到写一个扩展所必须的基础并尝试写自己的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: