许多经常使用的API函数已经更改,例如HashTable API; 这个页面致力于记录尽可能多的实际影响扩展和核心代码的更改。 强烈建议在阅读本指南之前阅读phpng-int中有关PHPNG实现的一般信息。
这不是一个涵盖所有可能情况的完整指南。 这是一个在大多数情况下有用的汇总。 我希望它对大多数用户级扩展来说是足够的。 然而,如果你没有在这里找到一些信息,发现一个解决方案,因为它可能对其他人有用 - 随时完善您的方法。
一般建议
尝试使用PHPNG编译扩展。 查看编译错误和警告。 他们可以显示出75%需要修改的地方。
在调试模式下编译和测试扩展(使用 -enable-debug 来配置PHP)。它将在运行时使用 assert() 函数捕获一些错误。 您还将看到有关内存泄漏的信息。
zval
PHPNG不需要任何指向指向zval的指针的参与。大多数zval**变量和参数必须更改为zval*。 使用这些变量的相应Z_*_ PP()宏应该更改为Z_*_P()。
在许多地方PHPNG直接使用zval(消除了分配和释放的需求)。 在这些情况下,应将相应的zval *变量转换为纯zval,使用此变量从Z_*_P()到Z_*()和相应的创建宏从ZVAL_*(var,...)到ZVAL_*(&var,...)。 一定要小心传递zval和&运算的地址。 PHPNG几乎从 不需要 传递 zval * 的地址。 在某些地方应该删除 & 运算。
有关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;
};
zend_value如下:
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;
主要的区别是,现在我们处理标量和复杂类型不同。 PHP不在堆中分配标量值,而是直接在VM堆栈上,在HashTables和对象内部。 它们 不再 是引用计数和垃圾收集的主体。 标量值没有引用计数器,不再支持 Z_ADDREF *() , Z_DELREF *() , Z_REFCOUNT *() 和 Z_SET_REFCOUNT *() 宏。 在大多数情况下,你应该判断zval是否支持这些宏,然后再调用它们。 否则你会得到一个assert()或崩溃。
- Z_ADDREF_P(zv)
+ if (Z_REFCOUNTED_P(zv)) {Z_ADDREF_P(zv);}
# or equivalently
+ Z_TRY_ADDREF_P(zv);
- 应使用
ZVAL_COPY_VALUE()宏复制zval值。 - 如果需要,可以使用
ZVAL_COPY()宏复制和增加引用计数器。 - 可以使用
ZVAL_DUP()宏来完成zval(zval_copy_ctor)的复制。 - 如果将
zval *转换为zval并且提前使用NULL来指示未定义的值,那么现在可以改用IS_UNDEF类型。 它可以使用ZVAL_UNDEF(&zv)设置并可以使用if(Z_ISUNDEF(zv))进行检查。 - 如果要使用cast-semantics而不修改原始zval来获取zval的long/double/string值,现在可以使用
zval_get_long(zv),zval_get_double(zv)和zval_get_string(zv)API简化代码:- 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代码获取更多详细信息: https://github.com/php/php-src/blob/master/Zend/zend_types.h
参考
PHPNG中的 zval 不再有 is_ref 标志。 引用是使用单独的复数引用计数类型 IS_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) {
Booleans
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))) {
将删除 Z_BVAL *() 宏。 注意, IS_FALSE/IS_TRUE 在 Z_LVAL *() 的返回值里是没有定义的。
Strings
可以使用相同的宏 Z_STRVAL *() 和 Z_STRLEN *() 来访问字符串的值/长度。 但是现在字符串表示的下划线数据结构是 zend_string (在单独的部分中描述)。 zend_string可以通过 Z_STR *() 宏从zval中检索。 它也可以通过 Z_STRHASH *() 获取字符串的哈希值。
如果代码需要检查给定的字符串是否是可转为int,现在应该使用zend_string(不是char *):
- if (IS_INTERNED(Z_STRVAL_P(zv))) {
+ if (IS_INTERNED(Z_STR_P(zv))) {
创建字符串zvals有点改变。 以前的宏,如 ZVAL_STRING() 有一个额外的参数,告诉是否应该复制给定的字符。 现在这些宏总是必须创建 zend_string 结构,所以这个参数变得没用了。 但是,如果它的实际值为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_STRINGS() 等等和一些内部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);
可以直接使用 zend_string API并直接从zend_string创建zval来避免双重新分配。
- char * str = estrdup("Hello");
- RETURN_STRING(str);
+ zend_string *str = zend_string_init("Hello", sizeof("Hello")-1, 0);
+ RETURN_STR(str);
Z_STRVAL *() 现在应该用作只读对象。 它不可能分配任何东西。 它可以修改单独的字符,但在做之前,你必须确保这个字符串没有被引用到其他地方(它不是interned,它的reference-counter是1)。 此外,在字符串修改后,可能需要重置计算的哈希值。
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有一个新的 zend_string API,除了zend_string是在zval中的字符串表示的下划线结构,这些结构也被用于以前使用 char * 和 int 的大部分代码库。
可以使用 zend_string_init(char * val,size_t len,int persistent) 函数创建zend_strings(不是IS_STRING zvals)。 实际字符可以作为 str→val 和字符串长度作为 str→len 访问。 字符串的哈希值应通过 zend_string_hash_val 函数访问。 如果需要,它将重新计算哈希值。
字符串应该使用 zend_string_release() 函数释放,这不需要空闲内存,因为相同的字符串可能从几个地方引用。
如果你打算在某个地方保持 zend_string 指针,你应该增加它的reference-counter或使用 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);
如果复制的字符串要更改,您可以使用 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);
具有旧宏的代码也是支持的,因此无需切换到新宏。
在某些情况下,在实际字符串数据已知之前分配字符串缓冲区是有意义的。 您可以使用 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);
不是所有的扩展代码都必须将 char * 转换为 zend_string 。 由扩展维护者决定哪种类型在每种特定情况下更合适。
查看 zend_string.h 代码了解更多详细信息:https://github.com/php/php-src/blob/master/Zend/zend_string.h
smart_str 和 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);
此外,引入了一个新的 zend_str API,它直接与 zend_string 一起工作:
- 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();
+ }
smart_str 定义如下:
typedef struct {
zend_string *s;
size_t a;
} smart_str;
smart_str和smart_string的API非常相似,实际上它们重复PHP5中使用的API。 所以采用代码不是一个大问题。 最大的问题是自动为每个特定情况选择什么,但它取决于最终结果的使用方式。
请注意,可能需要更改先前检查的空 smart_str :
- if (smart_str->c) {
+ if (smart_str->s) {
strprintf
除了 sprintf() 和 vsprintf() 函数,我们引入了类似的函数,产生zend_string ,而不是 char * 。 它取决于您决定何时应该更改为新的变体。
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
数组实现或多或少相同,但是,如果以前的下划线结构被实现为指向 HashTable 的指针,现在我们在这里有一个指向 zend_array 的内部保持 HashTable 。 HashTable 可以像之前一样使用 Z_ARRVAL *() 宏读取,但现在不可能将指针更改为HashTable。 它只能通过宏Z_ARR *()获取或设置指向整个zend_array的指针。
创建数组的最好方法是使用旧的 array_init() 函数,但也可以使用 ZVAL_NEW_ARR() 创建新的未初始化数组,或者通过 ZVAL_ARR() 使用 zend_array 结构初始化数组。
一些数组可能是不可变的(可以使用 Z_IMMUTABLE() 宏来检查)。 如果代码需要修改它们,它们必须首先复制。 使用内部位置指针通过不可变数组迭代也是不可能的。 可以使用带有外部位置指针的旧迭代API或使用在单独部分中描述的新的HashTable迭代API来遍历这些数组。
HashTable API
HashTable API 明显的改变,它可能会导致扩展兼容中的一些麻烦。
首先,现在HashTables总是使用zval。 即使我们存储一个任意指针,它被打包到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函数直接返回请求的值(而不是通过引用参数使用附加参数并返回SUCCESS / FAILURE):
- 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) {
键表示为zend_string。 大多数函数有两种形式。 一个以zend_string作为键,另一个以char *作为键,长度对。
重要说明:当键值字符串的长度不包括尾随零(