把扩展从php5升级到php7(翻译)

目录:

  1. 概述
  2. 建议
  3. zval
  4. reference
  5. Boolean
  6. string
  7. zend_string API
  8. smart_str and smart_string
  9. strpprintf
  10. arrays
  11. HashTable API
  12. HashTable Iteration API
  13. object
  14. custom object
  15. zend_object_handlers
  16. resource
  17. parameters Parsing API
  18. call frame (zend_execute_data)
  19. executor globals
  20. opcodes
  21. temp variable
  22. pcre

概述

很多常用的ZEND API已经发生了变化,例如HashTable API。这篇文章会尽可能多的记录这些变化。强烈建议你先阅读PHP7的实现(我这里有个现成的翻译)。

当然,这肯定不会一篇完整的指南,它不可能涵盖所有的内容,但它收集了那些最常用的案例。

建议

  • 在php7下编译你的扩展,编译错误与警告会告诉你绝大部分需要修改的地方。
  • 在DEBUG模式下编译与调试你的扩展,在run-time你可以通过断言捕捉一些错误。你还可以看到内存泄露的情况。

zval

  • PHP7不再需要指针的指针,绝大部分zval**需要修改成zval*。Z_*_PP()宏也需要修改成 Z_*_P().
  • 如果PHP7直接操作zval,那么zval*也需要改成zval,Z_*P()也要改成Z_*(),ZVAL_*(var, …) 需要改成 ZVAL_*(&var, …).一定要谨慎使用&符号,因为PHP7几乎不要求使用zval*,那么很多地方的&也是要去掉的。
  • ALLOC_ZVAL, ALLOC_INIT_ZVAL, MAKE_STD_ZVAL 这几个分配内存的宏已经被移除了。大多数情况下,zval*应该修改为zval,而 INIT_PZVAL宏也被移除了。

    //初始化的一些示例代码
    -  zval *zv;
    -  ALLOC_INIT_ZVAL();
    -  ZVAL_LONG(zv, 0);
    +  zval zv;
    +  ZVAL_LONG(&zv, 0);

zval 结构体也发生变化,它如今的定义如下

struct _zval_struct {
    zend_value        value;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* various IS_VAR flags */
        } v;
        zend_uint type_info;
    } u1;
    union {
        zend_uint     var_flags;
        zend_uint     next;                 /* hash collision chain */
        zend_uint     str_offset;           /* string offset */
        zend_uint     cache_slot;           /* literal cache slot */
    } u2;
};

typedef union _zend_value {
    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;
} zend_value;

主要的区别是,现在处理基本类型和复杂类型有所不同。基本类型是在VM栈上分配的,而不是堆,包括HashTable与Object,并且他们不支持引用计数与垃圾回收。基本类型没有引用计数,也就不再支持Z_ADDREF*(), Z_DELREF*(), Z_REFCOUNT*(),Z_SET_REFCOUNT*()这些宏了。你扩展中的基本类型使用了这些宏,那么程序会得到一个asset或者直接崩溃。

- Z_ADDREF_P(zv)
+ if (Z_REFCOUNTED_P(zv)) {Z_ADDREF_P(zv);}
# or equivalently
+ Z_TRY_ADDREF_P(zv);

一下是几点注意事项:

  • 应该使用ZVAL_COPY_VALUE()进行值复制
  • 使用ZVAL_COPY()拷贝是增加引用计数
  • 可以使用ZVAL_DUP() 替代zval_copy_ctor进行复制
  • 原本的NULL被替换成IS_UNDEF类型了,可以使用Z_ISUNDEF(zv)进行读取,ZVAL_UNDEF(&zv)进行初始化
  • 可以使用zval_get_long(zv), zval_get_double(zv), zval_get_string(zv)等函数获取zval的值,这样不会改变原始的zval

    //一些示例代码
    - zval tmp;
    - ZVAL_COPY_VALUE(&tmp, zv);
    - zval_copy_ctor(&tmp);
    - convert_to_string(&tmp);
    - // ...
    - zval_dtor(&tmp);
    + zend_string *str = zval_get_string(zv);
    + // ...
    + zend_string_release(str);

更多请查看zend_types.h

reference

PHP7中的zval不再有is_ref字段了,而是使用zend_reference复合类型。如果你仍然使用Z_ISREF*()去检验zval是否是引用,实际上你是在判断zval是否是IS_REFERENCE类型。is_ref相关的宏已经被移除了。Z_SET_ISREF*(), Z_UNSET_ISREF*(), Z_SET_ISREF_TO*()这些宏的用法已经发生了改变,请参照下面。

- Z_SET_ISREF_P(zv);
+ ZVAL_MAKE_REF(zv);

- Z_UNSET_ISREF_P(zv);
+ if (Z_ISREF_P(zv)) {ZVAL_UNREF(zv);}

以前我们直接通过引用类型检测引用,但是现在我们需要使用Z_REFVAL*()间接检测了。

- if (Z_ISREF_P(zv) && Z_TYPE_P(zv) == IS_ARRAY) {
+ if (Z_ISREF_P(zv) && Z_TYPE_P(Z_REFVAL_P(zv)) == IS_ARRAY) {

或者手动使用ZVAL_DEREF()来减少引用计数。

- if (Z_ISREF_P(zv)) {...}
- if (Z_TYPE_P(zv) == IS_ARRAY) {
+ if (Z_ISREF_P(zv)) {...}
+ ZVAL_DEREF(zv);
+ if (Z_TYPE_P(zv) == IS_ARRAY) {

Boolean

IS_BOOL已经被IS_TRUE和IS_FALSE取代了。

- if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) && Z_LVAL_PP(item)) {
+ if (Z_TYPE_P(item) == IS_TRUE || (Z_TYPE_P(item) == IS_LONG && Z_LVAL_P(item))) {

The Z_BVAL*() macros are removed. Be careful, the return value of Z_LVAL*() on IS_FALSE/IS_TRUE is undefined.

Z_BVAL*()宏已经被移除,注意,对IS_FALSE/IS_TRUE使用Z_LVAL*()得到的结果将是undefined。

string

string的值、长度可以分别使用Z_STRVAL*()、Z_STRLEN*()进行获取。而且现在使用zend_string来表示字符串,可以使用Z_STR*()从zval中获取zend_string的值,也是用使用Z_STRHASH*()获取字符串的HASH值。

如果现在要检测是否是驻留字符串,参数应该是zend_string而不是char*。

- if (IS_INTERNED(Z_STRVAL_P(zv))) {
+ if (IS_INTERNED(Z_STR_P(zv))) {

创建string类型的zval也发生了一些变化。 之前ZVAL_STRING()有个参数是控制字符串是否被复制的。现在这些宏都是用来创建zend_string的,所以这个参数变得没有必要了。 However if its actual value was 0, 你需要释放原始字符串来避免内存泄露。

- ZVAL_STRING(zv, str, 1);
+ ZVAL_STRING(zv, str);

- ZVAL_STRINGL(zv, str, len, 1);
+ ZVAL_STRINGL(zv, str, len);

- ZVAL_STRING(zv, str, 0);
+ ZVAL_STRING(zv, str);
+ efree(str);

- ZVAL_STRINGL(zv, str, len, 0);
+ ZVAL_STRINGL(zv, str, len);
+ efree(str);

RETURN_STRING(), RETVAL_STRNGL()和一些核心API并没有发生变化。

- add_assoc_string(zv, key, str, 1);
+ add_assoc_string(zv, key, str);

- add_assoc_string(zv, key, str, 0);
+ add_assoc_string(zv, key, str);
+ efree(str);

The double reallocation may be avoided using zend_string API directly and creating zval directly from zend_string.

- char * str = estrdup("Hello");
- RETURN_STRING(str);
+ zend_string *str = zend_string_init("Hello", sizeof("Hello")-1, 0);
+ RETURN_STR(str);

Z_STRVAL*()返回的变量应该当作只读的,它不应该被赋值。但是如果一定要修改,那么你应该确定它并没有在其他地方被引用,也就意味着它不能是驻留字符串并且引用计数是1。还有,如果你修改了字符串的值,那么你需要手动计算并保存其HASH值。

+ SEPARATE_ZVAL(zv);
+ Z_STRVAL_P(zv)[0] = Z_STRVAL_P(zv)[0] + ('A' - 'a');
+ zend_string_forget_hash_val((Z_STR_P(zv))

zend_string API

新引擎有新的zend_string API,以前大量使用char*+int的地方,都替换成了zend_string。

zend_string(not IS_STRING zvals)变量可以使用zend_string_init(char *val, int len, int persistent)进行创建。The actual characters may be accessed as str→val and string length as str→len.可以使用zend_string_hash_val获取hash值,它会再必要的时候重新进行计算。

应该使用zend_string_release()来释放string占用的内存,但是不一定会立即释放,因为这个string可能被多次引用。

如果你想保持一个zend_string的指针,那么你需要增加其引用计数,或者你可以直接使用zend_string_copy()来实现。很多时候拷贝只是为了获取其值,那么请尽量使用该函数。

- ptr->str = estrndup(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
+ ptr->str = zend_string_copy(Z_STR_P(zv));
  ...
- efree(str);
+ zend_string_release(str);

复制string现在使用zend_string_dup()替代了。

- char *str = estrndup(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
+ zend_string *str = zend_string_dup(Z_STR_P(zv));
  ...
- efree(str);
+ zend_string_release(str);

原来string的那些宏还是支持的,所以并不是一定要用新的写法。

如果一些string的长度是知道的但是需要一个缓冲区,那么你可以使用zend_string_alloc()或zend_string_realloc()进行内存分配。

- char *ret = emalloc(16+1);
- md5(something, ret); 
- RETURN_STRINGL(ret, 16, 0);
+ zend_string *ret = zend_string_alloc(16, 0);
+ md5(something, ret->val);
+ RETURN_STR(ret);

并不是所有的扩展都要更新到zend_string来替换char*,主要还是看哪一个更合适。

查看zend_string.h可以找到更详细的用法。

smart_str and smart_string

smart_str相关的API已经被重命名为smart_string了,除了新名字,用法基本没变。

- smart_str str = {0};
- smart_str_appendl(str, " ", sizeof(" ") - 1);
- smart_str_0(str);
- RETURN_STRINGL(implstr.c, implstr.len, 0);
+ smart_string str = {0};
+ smart_string_appendl(str, " ", sizeof(" ") - 1);
+ smart_string_0(str);
+ RETVAL_STRINGL(str.c, str.len);
+ smart_string_free(&str);

- smart_str str = {0};
- smart_str_appendl(str, " ", sizeof(" ") - 1);
- smart_str_0(str);
- RETURN_STRINGL(implstr.c, implstr.len, 0);
+ smart_str str = {0};
+ smart_str_appendl(str, " ", sizeof(" ") - 1);
+ smart_str_0(str);
+ if (str.s) {
+   RETURN_STR(str.s);
+ } else {
+   RETURN_EMPTY_STRING();
+ }

typedef struct {
    zend_string *s;
    size_t a;
} smart_str;

事实上smart_str和smart_string非常类似,在PHP5中它们基本算重复的,所以更新代码并不是必要的。

the biggest question what AI to select for each particular case, but it depends the way the final result is used.

但是检测其是否为空发生了一些变化。

- if (smart_str->c) {
+ if (smart_str->s) {

strpprintf

spprintf()和vspprintf()的返回值从char*变成了zend_string,那么你需要改变你的代码了。

+ PHPAPI zend_string *vstrpprintf(size_t max_len, const char *format, va_list ap);
+ PHPAPI zend_string *strpprintf(size_t max_len, const char *format, ...);

arrays

array的实现或多或少是不变的,但是之前是用一个指针指向HashTable,而现在指向的是zend_array。读取HashTable同样使用Z_ARRVAL*()宏,但是现在不可能改变该HashTable的指针了,现在唯一可以读取和改变zend_array的方法是通过Z_ARR*()宏。

使用array_init()同样是创建array的最好方法,但是也可以使用ZVAL_NEW_ARR()创建一个未初始化的array,用ZVAL_ARR()进行初始化。

可不变数组可以使用Z_IMMUTABLE()进行检测,但是如果想改变该数组,请先复制它。使用internal position pointer迭代不可变数组也是不行的。但是可以使用external position pointer结合原来的迭代API可以遍历数组,或者也可以使用新的HashTable迭代API。

HashTable API

HashTable API的变化很显著,移植扩展中要特别注意这点。

  • HashTable的元素始终是zval,即使存的是指针,也是被封装成IS_PTR类型的zval。

    - zend_hash_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key)+1, (void*)&zv, sizeof(zval**), NULL) == SUCCESS) {
    + if (zend_hash_update(EG(function_table), Z_STR_P(key), zv)) != NULL) {
  • API基本直接返回zval,而不是返回bool+参数返回zval。

    - if (zend_hash_find(ht, Z_STRVAL_P(key), Z_STRLEN_P(key)+1, (void**)&zv_ptr) == SUCCESS) {
    + if ((zv = zend_hash_find(ht, Z_STR_P(key))) != NULL) {
  • 元素的key是用zend_string封装的,但是也同样提供了两类函数:zend_string或者char*+int
  • 注意:key不再包含"\0",一些地方+1/-1会发生变化。

    - if (zend_hash_find(ht, "value", sizeof("value"), (void**)&zv_ptr) == SUCCESS) {
    + if ((zv = zend_hash_str_find(ht, "value", sizeof("value")-1)) != NULL) {

上面的规则同样也适用其他一些API。

- add_assoc_bool_ex(&zv, "valid", sizeof("valid"), 0);
+ add_assoc_bool_ex(&zv, "valid", sizeof("valid") - 1, 0);
  • 同样提供了一组适用指针类型的参数,它们都有_ptr后缀。

    - if (zend_hash_find(EG(class_table), Z_STRVAL_P(key), Z_STRLEN_P(key)+1, (void**)&ce_ptr) == SUCCESS) {
    + if ((ce_ptr = zend_hash_find_ptr(EG(class_table), Z_STR_P(key))) != NULL) {
    
    - zend_hash_update(EG(class_table), Z_STRVAL_P(key), Z_STRLEN_P(key)+1, (void*)&ce, sizeof(zend_class_entry*), NULL) == SUCCESS) {
    + if (zend_hash_update_ptr(EG(class_table), Z_STR_P(key), ce)) != NULL) {
  • API provides a separate group of functions to store memory blocks of arbitrary size. Such functions have the same names with _mem suffix and they implemented as inline wrappers of corresponding _ptr functions. It doesn't mean if something was stored using _mem or _ptr variant. It always may be retrieved back using zend_hash_find_ptr().

    - zend_hash_update(EG(function_table), Z_STRVAL_P(key), Z_STRLEN_P(key)+1, (void*)func, sizeof(zend_function), NULL) == SUCCESS) {
    + if (zend_hash_update_mem(EG(function_table), Z_STR_P(key), func, sizeof(zend_function))) != NULL) {
  • 提供了一些新的添加函数,它们被用在添加新的zval并且当前不存在同样的key。它们都有同样的后缀_new。

    zval* zend_hash_add_new(HashTable *ht, zend_string *key, zval *zv);
    zval* zend_hash_str_add_new(HashTable *ht, char *key, int len, zval *zv);
    zval* zend_hash_index_add_new(HashTable *ht, pzval *zv);
    zval* zend_hash_next_index_insert_new(HashTable *ht, pzval *zv);
    void* zend_hash_add_new_ptr(HashTable *ht, zend_string *key, void *pData);
    ...
  • HashTable destructors 的参数总是zval*类型。(even if we use zend_hash_add_ptr or zend_hash_add_mem to add elements). Z_PTR_P() macro may be used to reach the actual pointer value in destructors. Also, if elements are added using zend_hash_add_mem, destructor is also responsible for deallocation of the pointers themselves.

    - void my_ht_destructor(void *ptr)
    + void my_ht_destructor(zval *zv)
      {
    -    my_ht_el_t *p = (my_ht_el_t*) ptr;
    +    my_ht_el_t *p = (my_ht_el_t*) Z_PTR_P(zv);
         ...
    +    efree(p); // this efree() is not always necessary
      }
    );
  • 像zend_hash_apply_*(),zend_hash_copy(),zend_hash_merge()的参数同样需要用zval*代替void*&&。一些函数可能接收zend_hash_key指针变量作为参数,该结构被定义为下,如果key是字符串,那么h保存hash值,key保存字符串;如果key是数字,那么h就是该数字,而key是NULL。

    typedef struct _zend_hash_key {
        ulong        h;
        zend_string *key;
    } zend_hash_key;

注意:应该使用新的迭代API替换zend_hash_apply*()此类函数,因为效率更高,代码更短。

更多请查看zend_hash.h

HashTable Iteration API

我们提供了一些特别的宏来遍历HashTable,第一个参数是HashTable,剩下的参数变量将在每一步迭代中被复制。

ZEND_HASH_FOREACH_VAL(ht, val)
ZEND_HASH_FOREACH_KEY(ht, h, key)
ZEND_HASH_FOREACH_PTR(ht, ptr)
ZEND_HASH_FOREACH_NUM_KEY(ht, h)
ZEND_HASH_FOREACH_STR_KEY(ht, key)
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)
ZEND_HASH_FOREACH_KEY_VAL(ht, h, key, val)

最好适用新的宏代替原来的那么操作函数。

- HashPosition pos;
  ulong num_key;
- char *key;
- uint key_len;
+ zend_string *key;
- zval **pzv;
+ zval *zv;
-
- zend_hash_internal_pointer_reset_ex(&ht, &pos);
- while (zend_hash_get_current_data_ex(&ht, (void**)&ppzval, &pos) == SUCCESS) {
-   if (zend_hash_get_current_key_ex(&ht, &key, &key_len, &num_key, 0, &pos) == HASH_KEY_IS_STRING){
-   }
+ ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, key, val) {
+   if (key) { //HASH_KEY_IS_STRING
+   }
    ........
-   zend_hash_move_forward_ex(&ht, &pos);
- }
+ } ZEND_HASH_FOREACH_END();

object

TODO: …

custom object

TODO: …

zend_object被定义为:

struct _zend_object {
    zend_refcounted   gc;
    zend_uint         handle; // TODO: may be removed ???
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    HashTable        *guards; /* protects from __get/__set ... recursion */
    zval              properties_table[1];
};

新的结构中properties_table是内联的,这就带来了问题。以前我们是这样自定义对象的:

struct custom_object {
   zend_object std;
   void  *custom_data;
}

zend_object_value custom_object_new(zend_class_entry *ce TSRMLS_DC) {

   zend_object_value retval;
   struct custom_object *intern;

   intern = emalloc(sizeof(struct custom_object));
   zend_object_std_init(&intern->std, ce TSRMLS_CC);
   object_properties_init(&intern->std, ce);
   retval.handle = zend_objects_store_put(intern,
        (zend_objects_store_dtor_t)zend_objects_destroy_object,
        (zend_objects_free_object_storage_t) custom_free_storage, 
        NULL TSRMLC_CC);
   intern->handle = retval.handle;
   retval.handlers = &custom_object_handlers;
   return retval;
}

struct custom_object* obj = (struct custom_object *)zend_objects_get_address(getThis());

但是现在由于内联属性,zend_object的长度是变化的,所以我们要做出下面的改变了。

struct custom_object {
   void  *custom_data;
   zend_object std;
}

zend_object * custom_object_new(zend_class_entry *ce TSRMLS_DC) {
     # Allocate sizeof(custom) + sizeof(properties table requirements)
     struct custom_object *intern = ecalloc(1, 
         sizeof(struct custom_object) + 
         zend_object_properties_size(ce));
     # Allocating:
     # struct custom_object {
     #    void *custom_data;
     #    zend_object std;
     # }
     # zval[ce->default_properties_count-1]
     zend_object_std_init(&intern->std, ce TSRMLS_CC);
     ...
     custom_object_handlers.offset = XtOffsetof(struct custom_obj, std);
     custom_object_handlers.free_obj = custom_free_storage;

     return &intern->std;
}

# Fetching the custom object:

static inline struct custom_object * php_custom_object_fetch_object(zend_object *obj) {
      return (struct custom_object *)((char *)obj - XtOffsetOf(struct custom_object, std));
}

#define Z_CUSTOM_OBJ_P(zv) php_custom_object_fetch_object(Z_OBJ_P(zv));

struct custom_object* obj = Z_CUSTOM_OBJ_P(getThis());

zend_object_handlers

一个新的字段offset被定义进zend_object_handlers,当你使用自定义对象的时候,一定要对它赋值。

请使用zend_objects_store_*来找到分配的地址。

// An example in spl_array
memcpy(&spl_handler_ArrayObject, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
spl_handler_ArrayObject.offset = XtOffsetOf(spl_array_object, std);

对象的内存会自动被zend_objects_store_*释放,所以你不必通过free_obj句柄来释放。

resource

IS_RESOURCE类型zval不再保持resource handle,也不能使用Z_LVAL*()获取resource handle了,现在应该使用Z_RES*()宏获取。It contains type - resource type, ptr - pointer to actual data, handle - numeric resource index (for compatibility) and service fields for reference counter. Actually this zend_resurce structure is a replacement for indirectly referred zend_rsrc_list_entry. 所有与zend_rsrc_list_entry相关的应该被zend_resource代替.

  • zend_list_find()已被移除,因为资源能够被直接获取。

    - long handle = Z_LVAL_P(zv);
    - int  type;
    - void *ptr = zend_list_find(handle, &type);
    + long handle = Z_RES_P(zv)->handle;
    + int  type = Z_RES_P(zv)->type;
    + void *ptr = = Z_RES_P(zv)->ptr;
  • Z_RESVAL_*()已被移除,用 Z_RES*()代替。

    - long handle = Z_RESVAL_P(zv);
    + long handle = Z_RES_P(zv)->handle;
  • ZEND_REGISTER_RESOURCE/ZEND_FETCH_RESOURCE()都被移除了。

    - ZEND_FETCH_RESOURCE2(ib_link, ibase_db_link *, &link_arg, link_id, LE_LINK, le_link, le_plink);
    
    //if you are sure that link_arg is a IS_RESOURCE type, then use :
    +if ((ib_link = (ibase_db_link *)zend_fetch_resource2(Z_RES_P(link_arg), LE_LINK, le_link, le_plink)) == NULL) {
    +    RETURN_FALSE;
    +}
    
    //otherwise, if you know nothing about link_arg's type, use
    +if ((ib_link = (ibase_db_link *)zend_fetch_resource2_ex(link_arg, LE_LINK, le_link, le_plink)) == NULL) {
    +    RETURN_FALSE;
    +}
    
    - REGISTER_RESOURCE(return_value, result, le_result);
    + RETURN_RES(zend_register_resource(result, le_result);
  • zend_list_addref(),zend_list_delref()都被移除。

    - zend_list_addref(Z_LVAL_P(zv));
    + Z_ADDREF_P(zv);
    
    - zend_list_addref(Z_LVAL_P(zv));
    + Z_RES_P(zv)->gc.refcount++;
  • zend_list_delete()需传入 zend_resource 指针变量

    - zend_list_delete(Z_LVAL_P(zv));
    + zend_list_delete(Z_RES_P(zv));
  • 在多数扩展函数中,像mysql_close(),你应该使用zend_list_close()代替zend_list_delete(),因为close只是关闭实际连接与释放扩展特别的结构,但是不会释放zend_reference structure,所以还可以从其他地方引用该zval,close同样也不会减少引用计数。

    - zend_list_delete(Z_LVAL_P(zv));
    + zend_list_close(Z_RES_P(zv));

parameters Parsing API

  • PHP7不再需要zval**,所以请用"z"标识替换"Z"

    - zval **pzv;
    - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &pzv) == FAILURE) {
    + zval *zv;
    + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zv) == FAILURE) {
  • PHP7建议适用"S"标识接收zend_string变量。

    - char *str;
    - int len;
    - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
    + zend_string *str;
    + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
  • "+"/"*"只接收zval数组了

    - zval ***argv = NULL;
    + zval *argv = NULL;
      int argn;
      if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &argv, &argn) == FAILURE) {
  • arguments passed by reference should be assigned into the referenced value. It's possible to separte such arguments, to get referenced value at first place.

    - zval **ret;
    - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &ret) == FAILURE) {
    + zval *ret;
    + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z/", &ret) == FAILURE) {
        return;
      }
    - ZVAL_LONG(*ret, 0);
    + ZVAL_LONG(ret, 0);

call frame (zend_execute_data)

每一次的函数调用都记录在zend_execute_data结构链表中,EG(current_execute_data)指向当前执行函数的调用栈,之前只有PHP用户函数才这样。我会尽量解释清楚新旧调用栈之间的区别。

  • zend_execute_data.opline - 当前执行的用户函数的指针。内核函数它的值是未定义,之前是NULL。

  • zend_execute_data.function_state - 被移除,用 zend_execute_data.call代替。

  • zend_execute_data.call - 旧引擎中是call_slot的指针,现在是当前调用函数的指针,它被初始化为NULL, 然后被ZEND_INIT_FCALL (or similar) opcodes改变,最后被ZEND_FO_FCALL回复. 嵌套函数调用,例如foo($a, bar($c)), 将通过zend_execute_data.prev_nested_call构造一个链表。

  • zend_execute_data.op_array - 已被zend_execute_data.func替代, 因为现在它不仅代表用户函数也代表内核函数。

  • zend_execute_data.func - 当前执行的函数

  • zend_execute_data.object - $this of the currently executed function (previously it was a zval*, now it's a zend_object*)

  • zend_execute_data.symbol_table - current symbol table or NULL

  • zend_execute_data.prev_execute_data - link of backtrace call chain

  • original_return_value, current_scope, current_called_scope, current_this - 这些缓存变量值以便在调用后恢复现场的字段已被移除。

  • zend_execute_data.scope - scope of the currently executed function (this is a new field).

  • zend_execute_data.called_scope - called_scope of the currently executed function (this is a new field).

  • zend_execute_data.run_time_cache - run-time-cache of the currently executed function. this is a new field and actually it's a copy of op_array.run_time_cache.

  • zend_execute_data.num_args - number of arguments passed to the function (this is a new field)

  • zend_execute_data.return_value - pointer to zval* where the currently executed op_array should store the result. 如果不关心返回值它可能就是NULL. (this is a new field).

函数参数都被直接储存在zval插槽中,在内存中紧接着zend_execute_data结构。他们可以通过ZEND_CALL_ARG(execute_data, arg_num)宏获取。如果是用户函数,函数的第一个参数内存位置将会和第一个compiled variable - CV0 重合。In case caller passes more arguments that callee receives, all extra arguments are copied to be after all used by calee CVs and TMP variables.

executor globals

  • EG(symbol_table)已经变成了zend_array了,而非HashTable。

    - symbols = zend_hash_num_elements(&EG(symbol_table));
    + symbols = zend_hash_num_elements(&EG(symbol_table).ht);
  • EG(uninitialized_zval_ptr) and EG(error_zval_ptr) were removed. Use &EG(uninitialized_zval) and &EG(error_zval) instead.
  • EG(current_execute_data) - 此字符发生了一些变化,之前是一个指向最后调用函数的栈帧。现在它指向最后执行的调用栈帧,并不区别是脚本函数还是内核函数。It's possible to get the zend_execute_data structure for the last op_array traversing call chain list.

      zend_execute_data *ex = EG(current_execute_data);
    + while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) {
    +    ex = ex->prev_execute_data;
    + }
      if (ex) {
  • EG(opline_ptr) - 被移除,用execute_data->opline替代

  • EG(return_value_ptr_ptr) - 被移除,用execute_data->return_value替代

  • EG(active_symbol_table) - 被移除,用execute_data->symbol_table替代

  • EG(active_op_array) - 被移除,用execute_data->func替代

  • EG(called_scope) - 被移除,用execute_data->called_scope替代

  • EG(This) - 变成zval, 之前是zval*。不应被修改。

  • EG(in_execution) - 被移除. If EG(current_excute_data) is not NULL, we are executing something.

  • EG(exception) and EG(prev_exception) - 被改成zend_object*,之前是zval*

opcodes

  • ZEND_DO_FCALL_BY_NAME - 已被移除,新增ZEND_INIT_FCALL_BY_NAME.

  • ZEND_BIND_GLOBAL - "global $var"的handler

  • ZEND_STRLEN - 代替了strlen函数

  • ZEND_TYPE_CHECK - 在必要的时候,用来代替is_array/is_int/is_*

  • ZEND_DEFINED - 在必要的时候代替zif_defined(if only one parameter and it's constant string and it's not in namespace style)

  • ZEND_SEND_VAR_EX - was added to do more check than ZEND_SEND_VAR if the condition can not be settled in compiling time

  • ZEND_SEND_VAL_EX - was added to do more check than ZEND_SEND_VAL if the condition can not be settled in compiling time

  • ZEND_INIT_USER_CALL - was added to replace call_user_func(_array) if possible if the function can not be found in compiling time, otherwise it can convert to ZEND_INIT_FCALL

  • ZEND_SEND_ARRAY - was added to send the second parameter, the array of the call_user_func_array after it is converted to opcode

  • ZEND_SEND_USER - was added to send the the parameters of call_user_func after it is converted to opcode

temp variable

TODO: …

pcre

一些正则API使用zend_string作为参数或者返回值了。php_pcre_replace returns a zend_string and takes a zend_string as 1st argument. 仔细检查函数申明与编译错误, 可以发现多数是类型错误。