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

数据创建

现在你已经学会怎么从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 {
    ...
    HashTable symbol_table;
    HashTable *active_symbol_table;
    ...
};

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

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

In PHP:

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


 

In C:

{
    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"的标签,放到active 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.");
    }
}

代码看起来很有意思,为什么使用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代码如下:

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;
        default:
            /* Should never happen in practice,
             * but it's dangerous to make assumptions
             */
             php_printf("Unknown");
             break;
    }
}

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

void display_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在处理请求时内存管理,检测持久和非持久的配置。到下一章结束,你才能学习到写一个扩展所必须的基础并尝试写自己的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值