美文网首页
PHP扩展开发(三)

PHP扩展开发(三)

作者: PurelightMe | 来源:发表于2020-08-19 22:15 被阅读0次

本章内容

本章主要包含php7的新版zval结构,以及一些基础的数据结构和数据类型转化,zval常用宏等。

数据结构

上一节我们在研究 php 扩展如何接受函数参数以及如何返回值后,得出结论:要想全面掌握扩展开发,必须得从zval下手,开干。

我在三四年前研究过 php5.6 的 zval ,那时候 C 基础还属于完全小白的阶段,看得一知半解,现在 php7 都出来这么久了,就懒得再去看老版本的了。

新版 zval (php几乎所有数据类型都是以此为基础):

//这个在 src/Zend/zend_type.h文件
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;
};

zend_value 自然是这里面最重要的成员了,变量的值在里面。u1 看名字就知道大概是类型信息(type_info),u2其实是一些辅助信息。

整个结构体是一个union,其大小是里面成员size多大的那个size,由于需要字节对齐的原因,这个zval占用16字节,比以前节省了不少空间,另外 is_ref 和 ref_count 都不直接在 zval 维护了,转移到了 zend_value 里面。

so,看看 zend_value 吧:

// from src/Zend/zend_types.h
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;

字段名称见名知意,整形和浮点型直接用标量存了,其他的复杂类型各自有对应的结构体,乍一看,怎么没有bool,其实bool,null等都不需要值,php内核里面直接用类型标志了。

zend_types.h 里面定义了很多类型:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT_AST             11

/* internal types */
#define IS_INDIRECT                 13
#define IS_PTR                      14
#define IS_ALIAS_PTR                15
#define _IS_ERROR                   15

/* fake types used only for type hinting (Z_TYPE(zv) can not use them) */
#define _IS_BOOL                    16
#define IS_CALLABLE                 17
#define IS_ITERABLE                 18
#define IS_VOID                     19
#define _IS_NUMBER                  20

zend_string,zend_array,zend_object……我就不一一列举了,直接翻源码 zend_types.h 里面看。

常用宏

关于 zval 的常用宏基本也在 zend_types.h 里面,比如第一个:Z_TYPE(z),用于获取 zval 的类型;Z_LVAL(z),用于获取 zval 的整型值;类似的还有 Z_STRVAL(z),Z_ARRVAL(z)等等。

跟上面那种风格相似,但作用完全不同的是:ZVAL_LONG(z,l),将整形值赋给 zval,还有 ZVAL_DOUBLE,ZVAL_ARR……

其余还有很多有用的,就不列举了。

在开发扩展过程中尽量用这些宏去操作数据,而不是直接操作 zval 结构体。

类型转换

关于数据类型转换,文件 Zend/zend_operators.c 里面有详细的任意每种类型转换成其他类型的转换方式。举个例子:

任意类型转换成整形:

ZEND_API void ZEND_FASTCALL convert_to_long(zval *op) /* {{{ */
{
    if (Z_TYPE_P(op) != IS_LONG) {
        convert_to_long_base(op, 10);
    }
}
//...
ZEND_API void ZEND_FASTCALL convert_to_long_base(zval *op, int base) /* {{{ */
{
    zend_long tmp;

try_again:
    switch (Z_TYPE_P(op)) {
        case IS_NULL:
        case IS_FALSE:
            ZVAL_LONG(op, 0);
            break;
        case IS_TRUE:
            ZVAL_LONG(op, 1);
            break;
        case IS_RESOURCE:
            tmp = Z_RES_HANDLE_P(op);
            zval_ptr_dtor(op);
            ZVAL_LONG(op, tmp);
            break;
        case IS_LONG:
            break;
        case IS_DOUBLE:
            ZVAL_LONG(op, zend_dval_to_lval(Z_DVAL_P(op)));
            break;
        case IS_STRING:
            {
                zend_string *str = Z_STR_P(op);
                if (base == 10) {
                    ZVAL_LONG(op, zval_get_long(op));
                } else {
                    ZVAL_LONG(op, ZEND_STRTOL(ZSTR_VAL(str), NULL, base));
                }
                zend_string_release_ex(str, 0);
            }
            break;
        case IS_ARRAY:
            tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
            zval_ptr_dtor(op);
            ZVAL_LONG(op, tmp);
            break;
        case IS_OBJECT:
            {
                zval dst;

                convert_object_to_type(op, &dst, IS_LONG, convert_to_long);
                zval_ptr_dtor(op);

                if (Z_TYPE(dst) == IS_LONG) {
                    ZVAL_LONG(op, Z_LVAL(dst));
                } else {
                    ZVAL_LONG(op, 1);
                }
                return;
            }
        case IS_REFERENCE:
            zend_unwrap_reference(op);
            goto try_again;
        EMPTY_SWITCH_DEFAULT_CASE()
    }
}

可以很清晰看到,null,false直接转成0,true转成1,其他类型也有具体的转换方式,如果不清楚对php中某种写法结果不确定,可以来源码找一下对应的实现。

补充

其实看了 zval 的结构和这些宏之后,就可以直接用php7新出的接收参数的方式接收参数了,来写个简单的demo吧:

//file: structure.c
//
// Created by purelightme on 2020/8/20.
//

#include <zend_types.h>

PHP_FUNCTION (ptest) {
    zval *para;
    ZEND_PARSE_PARAMETERS_START(1,1)
        Z_PARAM_ZVAL(para)
    ZEND_PARSE_PARAMETERS_END();
    php_printf("类型1:%d",para->u1.type_info);
    php_printf("类型2:%d",Z_TYPE_P(para));
    if (para->u1.type_info == 4){
        RETURN_LONG(para->value.lval);
    }
    if (para->u1.type_info == 6){
        RETURN_STR(para->value.str);
    }
    php_printf("不支持的参数类型");
    RETURN_NULL();
}
// file: purelightme.c
#include "structure.c"
//...
static const zend_function_entry purelightme_functions[] = {
    PHP_FE(purelightme_test1,       arginfo_purelightme_test1)
    PHP_FE(purelightme_test2,       arginfo_purelightme_test2)
    PHP_FE(now,NULL)
    PHP_FE(ptest,NULL)
    PHP_FE_END
};

测试一下

重新编译扩展,记得 make clean。

<?php
$p = ptest('ee');
var_dump($p);
$r = ptest(22);
var_dump($r);
$t = ptest([]);
var_dump($t);
17.1.png

ps:偏重于记录学习成果,过程还是挺繁琐的~总之,源码多看

2020-08-20

相关文章

网友评论

      本文标题:PHP扩展开发(三)

      本文链接:https://www.haomeiwen.com/subject/qdxyjktx.html